Vous êtes sur la page 1sur 449

Lart du dveloppement

Android
Mark Murphy

2e dition

Codes sources sur

www.pearson.fr

dition franaise tablie avec le concours de

LE P R O G RAM M E U R

Lart du dveloppement Android 2


2e dition
Mark L. Murphy
Traduit par ric Jacoboni avec la contribution dArnaud Farine

Pearson Education France a apport le plus grand soin la ralisation de ce livre afin de vous fournir une information complte et fiable. Cependant, Pearson Education France nassume de responsabilits, 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/licences/by/3.0/deed.fr.

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-4159-4 Copyright 2010 Pearson Education France Tous droits rservs

Titre original: Beginning Android 2 Traduit par ric Jacoboni, avec la contribution technique dArnaud Farine ISBN original: 978-1-4302-2629-1 Copyright 2010 by Mark L. Murphy All rights reserved dition originale publie par Apress 2855 Telegraph Avenue, Suite 600 Berkeley, CA 94705 USA

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.

Sommaire

propos de lauteur. ........................................ XVII Remerciements. .................................................XIII Prface ldition franaise............................. XV Introduction....................................................... 1 1. Tour dhorizon............................................. 3 2. Projets et cibles............................................ 7

12. Polices de caractres....................................157 13. Intgrer le navigateur de WebKit. ............163 14. Affichage de messages surgissants.............171 15. Utilisation des threads.................................177 16. Gestion des vnements du cycle de vie dune activit................................................191 17. Cration de filtres dintentions...................195

3. Cration dun squelette dapplication....... 19 18. Lancement dactivits et de sous-activits.201 4. Utilisation des layouts XML....................... 27 19. Gestion de la rotation..................................211 5. Utilisation des widgets debase................... 33 20. Utilisation des ressources............................225 6. Conteneurs................................................... 45 7. Widgets de slection.................................... 65 8. Samuser avec les listes. .............................. 83 9. Utiliser de jolis widgets et de beaux conteneurs................................109 10. Le framework des mthodes de saisie. ......135 11. Utilisation des menus. .................................145 21. Utilisation des prfrences..........................245 22. Accs et gestion des bases de donnes locales. ..........................................................259 23. Accs aux fichiers. .......................................273 24. Tirer le meilleur parti des bibliothques Java...............................................................283 25. Communiquer via Internet.........................291

IV

L'art du dveloppement Android 2

26. Utilisation dun fournisseur de contenu (content provider).........................................297 27. Construction dun fournisseur de contenu. ...................................................305 28. Demander et exiger despermissions. ........317 29. Cration dun service..................................323 30. Appel dun service.......................................331 31. Alerter les utilisateurs avec des notifications...................................337 32. Accs aux services de localisation..............343

33. Cartographie avec MapView et MapActivity..............................349 34. Gestion des appels tlphoniques...............361 35. Outils de dveloppement.............................365 36. Gestion des diffrentes tailles dcran.......377 37. Gestion des terminaux. ...............................403 38. Gestion des changements de plateformes..409 39. Pour aller plus loin......................................419 Index...................................................................423

Table des matires

propos de lauteur. ........................................ XVII Remerciements. .................................................XIII Prface ldition franaise............................. XV Introduction....................................................... 1 Bienvenue!...................................................... 1 Prrequis.......................................................... 1 ditions de ce livre ......................................... 2 Termes dutilisation du code source................ 2 1. Tour dhorizon............................................... 3 Dfis de la programmation des smartphones... 3 Contenu dun programme Android ................ 5 Fonctionnalits votre disposition ................. 6 2. Projets et cibles.............................................. 7 Les pices du puzzle........................................ 7 Cration dun projet. ........................................ 8 Structure dun projet........................................ 9 Contenu de la racine . ............................... 9

la sueur de votre front............................ 9 La suite de lhistoire ................................. 10 Le fruit de votre travail ............................. 10 Contenu du manifeste. ..................................... 11 Au dbut, il y avait la racine...................... 11 Permissions, instrumentations et applications . ......................................... 12 Que fait votre application? ...................... 13 Faire le minimum ...................................... 14 Version = contrle..................................... 15 mulateurs et cibles........................................ 15 Cration dun AVD. ................................... 15 Choix dune cible....................................... 17 3. Cration dun squelette dapplication......... 19 Terminaux virtuels et cibles............................ 19 Commencer par le dbut ................................ 20 Dissection de lactivit.................................... 21 Compiler et lancer lactivit............................ 23 4. Utilisation des layouts XML......................... 27 Quest-ce quun positionnement XML? ....... 27

VI

L'art du dveloppement Android 2

Pourquoi utiliser des layouts XML? ............. 28 Contenu dun fichier layout ........................... 29 Identifiants des widgets................................... 30 Utilisation des widgets dans le code Java ...... 30 Fin de lhistoire .............................................. 31 5. Utilisation des widgets debase..................... 33 Labels.............................................................. 33 Boutons........................................................... 35 Images ............................................................ 35 Champs de saisie............................................. 36 Cases cocher . .............................................. 38 Boutons radio ................................................. 40 Rsum ........................................................... 42 Proprits utiles ........................................ 42 Mthodes utiles ......................................... 43 Couleurs. ................................................... 43 6. Conteneurs..................................................... 45 Penser de faon linaire : LinearLayout. ........ 46 Concepts et proprits .............................. 46 Exemple . ................................................... 49 Tout est relatif : RelativeLayout...................... 53 Concepts et proprits .............................. 53 Exemple . ................................................... 55 Tabula Rasa : TableLayout.............................. 58 Concepts et proprits .............................. 58 Exemple . ................................................... 60 ScrollView . .................................................... 62 7. Widgets de slection...................................... 65 Sadapter aux circonstances ........................... 66 Listes des bons et des mchants...................... 67 Contrle du Spinner ....................................... 70 Mettez vos lions en cage ................................ 73

Champs: conomisez 35% de la frappe!...... 77 Galeries .......................................................... 81 8. Samuser avec les listes................................. 83 Premires tapes . ........................................... 83 Prsentation dynamique ................................. 86 Mieux, plus robuste et plus rapide ................. 88 Utilisation de convertView........................ 89 Utilisation du patron de conception "support"............................ 91 Cration dune liste.... ...................................... 94 Et la vrifier deux fois . .............................100 Adapter dautres adaptateurs. ..........................106 9. Utiliser de jolis widgets et de beaux conteneurs..................................109 Choisir.............................................................109 Le temps scoule comme un fleuve ..............114 Mesurer la progression . .................................115 Prendre la bonne rsolution. ............................116 Utilisation donglets . .....................................117 Les pices du puzzle . ................................117 Idiosyncrasies ...........................................118 Code Java .................................................119 Ajouts dynamiques.....................................121 Intent et View . ..........................................124 Tout faire basculer . ........................................124 Basculement manuel..................................126 Ajout de contenu la vole. ......................127 Basculement automatique..........................129 Fouiller dans les tiroirs. ...................................130 Autres conteneurs intressants .......................133 10. Le framework des mthodes de saisie. ......135 Claviers physiques et logiciels........................135


Adaptation vos besoins. ................................136 Dire Android o aller. ...................................140 Mise en place..................................................142 Librez le Dvorak qui est en vous...................144 11. Utilisation des menus..................................145 Menus doptions ............................................145 Cration dun menu doptions ..................146 Ajout de choix et de sous-menus................146 Menus contextuels .........................................147 Illustration rapide............................................148 Encore de linflation . .....................................153 Structure XML dun menu . .......................154 Options des menus et XML........................155 Cration dun menu par inflation .............156 12. Polices de caractres. ..................................157 Sachez apprcier ce que vous avez ................157 Polices supplmentaires..................................159 Le problme des glyphes. ................................161 13. Intgrer le navigateur de WebKit.............163 Un navigateur, et en vitesse!..........................163 Chargement immdiat.....................................166 Navigation au long cours ...............................167 Amuser le client .............................................167 Rglages, prfrences et options ....................169 14. Affichage de messages surgissants. ............171 Les toasts . ......................................................172 Les alertes ......................................................172 Mise en uvre . ..............................................173 15. Utilisation des threads.................................177 Les handlers....................................................178 Les messages . ...........................................178

Table des matires

VII

Les runnables . ..........................................181 Excution sur place ........................................181 O est pass le thread de mon interface utilisateur?......................................................182 Dsynchronisation .........................................182 La thorie . ................................................182 AsyncTask, gnricit et paramtres variables . ...........................183 Les tapes dAsyncTask . ..........................183 Exemple de tche.......................................184 viter les piges..............................................188 16. Gestion des vnements du cycle de vie dune activit. ................................................191 Lactivit de Schroedinger .............................192 Vie et mort dune activit . .............................192 onCreate() et onDestroy() .........................192 onStart(), onRestart() et onStop() .............193 onPause() et onResume() ..........................193 Ltat de grce ................................................194 17. Cration de filtres dintentions. .................195 Quelle est votre intention? ............................196 Composantes des intentions . ....................196 Routage des intentions ..............................197 Dclarer vos intentions ..................................198 Rcepteurs dintention....................................199 Attention la pause . ......................................200 18. Lancement dactivits et de sous-activits.201 Activits paires et sous-activits ....................202 Dmarrage.......................................................202 Cration dune intention ...........................203 Faire appel ................................................203 Navigation avec onglets .................................207

VIII

L'art du dveloppement Android 2

19. Gestion de la rotation..................................211 Philosophie de la destruction..........................211 Tout est pareil, juste diffrent ........................212 Il ny a pas de petites conomies! .................216 Rotation maison..............................................219 Forcer le destin . .............................................221 Tout comprendre.............................................223 20. Utilisation des ressources............................225 Les diffrents types de ressources...................225 Thorie des chanes . ......................................226 Chanes normales .....................................226 Formats de chanes....................................227 Texte styl. .................................................227 Formats styls ...........................................228 Vous voulez gagner une image? ....................231 Les ressources XML.......................................234 Valeurs diverses . ............................................236 Dimensions ...............................................237 Couleurs . ..................................................238 Tableaux ....................................................238 Grer la diffrence. ..........................................239 21. Utilisation des prfrences..........................245 Obtenir ce que vous voulez ............................246 Dfinir vos prfrences ..................................246 Un mot sur le framework ...............................247 Laisser les utilisateurs choisir ........................247 Ajouter un peu de structure.............................251 Botes de dialogue...........................................253 22. Accs et gestion des bases de donnes locales. ............................................................259 Exemple de base de donnes...........................260 Prsentation rapide de SQLite .......................261 Commencer par le dbut ................................262

Mettre la table ................................................265 Ajouter des donnes .......................................266 Le retour de vos requtes................................267 Requtes brutes .........................................267 Requtes normales ....................................268 Utilisation des "builders". .........................268 Utilisation des curseurs ............................270 Des donnes, des donnes, encore des donnes..........................................271 23. Accs aux fichiers. .......................................273 Allons-y!........................................................273 Lire et crire....................................................277 24. Tirer le meilleur parti des bibliothques Java.................................................................283 Limites extrieures . .......................................284 Ant et JAR . ....................................................284 Suivre le script ...............................................285 Tout fonctionne... enfin, presque.....................289 Autres langages de scripts...............................289 25. Communiquer via Internet.........................291 REST et relaxation .........................................292 Oprations HTTP via HttpComponents . ..292 Traitement des rponses . ..........................294 Autres points importants ...........................296 26. Utilisation dun fournisseur de contenu (content provider)...........................................297 Composantes dune Uri .................................298 Obtention dun descripteur ............................298 Cration des requtes......................................299 Sadapter aux circonstances ...........................300 Insertions et suppressions ..............................302 Attention aux BLOB! . ..................................303


27. Construction dun fournisseur de contenu......................................................305 Dabord, une petite dissection .......................306 Puis un peu de saisie.......................................307 Cration dun fournisseur de contenu.............307 tape n1: cration dune classe Provider . ..............................307 tape n2: fournir une Uri.......................313 tape n3: dclarer les proprits...........314 tape n4: modifier le manifeste.............314 Avertissements en cas de modifications..........315 28. Demander et exiger despermissions. ........317 Maman, puis-je? . ..........................................318 Halte! Qui va l? ..........................................319 Imposer les permissions via le manifeste . 320 Imposer les permissions ailleurs ..............321 Papiers, sil vous plat!...................................321 29. Cration dun service..................................323 Service avec classe .........................................324 Il ne peut en rester quun!. ..............................325 Destine du manifeste. .....................................326 Sauter la clture...............................................327 Mthodes de rappel. ..................................327 Intentions diffuses....................................328 30. Appel dun service.......................................331 Transmission manuelle. ...................................332 Capture de lintention. .....................................334 31. Alerter les utilisateurs avec des notifications.....................................337 Types davertissements ..................................337 Notifications matrielles ...........................338 Icnes ........................................................338 Les avertissements en action . ........................339

Table des matires

IX

32. Accs aux services de localisation. .............343 Fournisseurs de localisation: ils savent o vous vous cachez........................344 Se trouver soi-mme ......................................344 Se dplacer .....................................................346 Est-on dj arriv? Est-on dj arriv? Est-on dj arriv?..........................................347 Tester... Tester... . ............................................348 33. Cartographie avec MapView et MapActivity................................349 Termes dutilisation. ........................................350 Empilements ..................................................350 Les composants essentiels. ..............................351 Testez votre contrle.......................................352 Zoom .........................................................352 Centrage. ...................................................353 Terrain accident.............................................353 Couches sur couches ......................................354 Classes Overlay.........................................354 Affichage dItemizedOverlay ....................354 Gestion de lcran tactile . ........................356 Moi et MyLocationOverlay ...........................357 La cl de tout . ................................................358 34. Gestion des appels tlphoniques...............361 Le Manager ....................................................362 Appeler . .........................................................362 35. Outils de dveloppement.............................365 Gestion hirarchique ......................................365 DDMS (Dalvik Debug Monitor Service)........370 Journaux ...................................................371 Stockage et extraction de fichiers. .............371 Copies dcran ..........................................372

L'art du dveloppement Android 2

Mise jour de la position .........................373 Appels tlphoniques et SMS ....................374 Gestion des cartes amovibles..........................375 Cration dune image de carte..................376 36. Gestion des diffrentes tailles dcran.......377 cran par dfaut..............................................378 Tout en un. .......................................................378 Penser en termes de rgles, pas en termes de positions ....................................................379 Utilisez des dimensions physiques ............380 vitez les vrais pixels ................................380 Choisir des images adaptables .................380 Fait maison, rien que pour vous... ..................381 <supports-screens> ..................................381 Ressources et ensembles de ressources . ...382 Trouver sa taille ........................................383 Rien ne vaut la ralit. .....................................384 Diffrences de densit................................384 Ajustement de la densit............................385 Accs aux vritables terminaux ................386 Exploitez sans vergogne la situation ..............386 Remplacer les menus par des boutons . ....386 Remplacer les onglets par une seule activit ................................387 Consolider les activits multiples..............387 Exemple: EU4You .........................................388 Premire version .......................................388 Corriger les polices...................................395 Corriger les icnes....................................397

Utilisation de lespace ..............................397 Et si ce nest pas un navigateur ?..............400 Nos amis ont-ils quelques bogues? ...............401 37. Gestion des terminaux................................403 Cette application contient des instructions explicites . .............................403 Boutons, boutons, qui a des boutons ?............405 Un march garanti . ........................................405 Les dtails scabreux .......................................406 Tablette Internet Android Archos5 ...........406 CLIQ/DEXT de Motorola..........................406 DROID/Milestone de Motorola.................407 Nexus One de Google/HTC . .....................407 BACKFLIP de Motorola............................407 38. Gestion des changements de plateformes..409 Gestion de la marque. ......................................410 Des choses qui risquent de vous rendre nerveux....................................410 Hirarchie des vues. ..................................410 Changement des ressources.......................411 Gestion des modifications de lAPI ...............412 Dtecter la version dAndroid...................412 Envelopper lAPI ......................................412 39. Pour aller plus loin......................................419 Questions avec, parfois, des rponses ............419 Aller la source .............................................420 Lire les journaux.............................................421 Index...................................................................423

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 dveloppements open-source et collaboratifs de trs grosses socits au dveloppement dapplications 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, JeanBaptiste Queru, Jeff Sharkey et Xavier Ducrohet. Les icnes utilises dans les exemples de ce livre proviennent du jeu dicnes Nuvola1.

1.

http://www.icon-king.com/?p=15.

Prface ldition franaise

Novembre 2007, Google, le gant amricain de lInternet annonce quil vient de crer un nouveau systme dexploitation pour appareil mobile. Nomm Android, ce systme est totalement gratuit et open-source, pour les dveloppeurs dapplications, mais galement pour les constructeurs dappareils mobiles (tlphones, smartphones, MID [Multimedia Interface Device], GPS...). Un an auparavant, Apple avait lanc un pav dans la mare en sintroduisant avec brio dans le march des systmes dexploitation mobiles, dtenus plus de 50% par Symbian, grce lapport 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 dune alliance dune quarantaine de constructeurs, diteurs logiciels et socits spcialises dans les applications mobiles (Open Handset Alliance), Google a de quoi inquiter les systmes dexploitation propritaires 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 linitiative de Google avec plus de 1million de dollars de lots. Le but tait bien entendu de faire parler de son nouveau systme: pari russi! En 4mois, alors que la documentation de la plateforme tait quasiment inexistante, les juges du concours ont reu pas moins de 1700applications!

XVI

L'art du dveloppement Android 2

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 larrive de nouveaux constructeurs. Aujourdhui, le portail de vente de Google ddi aux applications Android (Android Market) dispose denviron 80000applications, et 70000nouveaux appareils fonctionnant sur Android sont activs chaque jour. Internet relate quotidiennement larrive de smartphones Android, mais aussi de tablettes internet, autoradios, tlviseurs, box internet... Tous les constructeurs de matriels touchant de prs ou de loin linformation travaillent ou travailleront prochainement avec Android. Mark Murphy, auteur du prsent ouvrage, est lun 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 dinformations. Cest dailleurs dans ce contexte que jai fait sa connaissance. Mark sest attach, dans cette seconde dition de LArt du dveloppement Android, prendre en compte les dernires mises jour du systme dexploitation: Android a sorti 4versions sur lanne 2009, et 3 sont prvues au total sur 2010... Dans sa dernire annonce, lquipe Android a indiqu quil ny aurait plus quune version par an partir de 2011! Cet ouvrage sera donc une trs bonne base pour vos dveloppements actuels et venir. Toutes les bases de la programmation sous Android y sont dcrites, mais pas uniquement. Cet ouvrage regorge dastuces et de conseils qui vous permettront de pouvoir raliser des applications Android convaincantes. 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 fin de 2008 , il ne fait aucun doute quil se rpandra rapidement grce linfluence 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 bibliothque de classes sapparentant un sous-ensemble de la bibliothque de Java SE (avec des extensions spcifiques). 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 informations sur le site web dAndroid1. 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.

L'art du dveloppement Android 2

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 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 Coder's 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 Apache2.01, que nous vous invitons lire avant de rutiliser ces codes.

1. http://www.apache.org/licenses/LICENSE-2.0.html.

1
Tour dhorizon
Les terminaux Android seront essentiellement des tlphones mobiles, mme si de plus en plus de tablettes sont annonces.

Dfis de la programmation des smartphones


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 annes1990, 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).

L'art du dveloppement Android 2

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 limites 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.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. En ne seffaant pas larrire-plan lorsque cela est ncessaire, parce quil ne sintgre pas correctement au systme dexploitation de leur mobile.. Il provoque un plantage de leur tlphone cause dune fuite de mmoire.

Le dveloppement de programmes pour un tlphone portable est donc diffrent de lcriture dapplications pour des machines de bureau, du dveloppement de sites web ou de la cration de programmes serveurs. Vous finirez 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 relati vement 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 suffisamment 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.

Chapitre 1

Tour dhorizon

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 filles les botes de dialogue, par exemple comme il le souhaite. De son point de vue, il est seul au monde; il tire parti des fonctionnalits 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 (Java DataBase Connectivity) ou les frameworks qui reposent sur lu 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. Les composants principaux dune application Android sont :

Les activits (activities). Ce sont les briques de base de linterface utilisateur. Vous pouvez considrer une activit 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. Les fournisseurs de contenus (content providers). Ils offrent un niveau dabstraction pour toutes les donnes stockes 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 fournisseur de contenus permet dobtenir ce rsultat tout en gardant un contrle total sur la faon dont on accdera aux donnes. Les services. Les activits et les fournisseurs de contenus 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 vrifier les mises jour dun flux RSS ou pour jouer de la musique, mme si lactivit de contrle nest plus en cours dexcution. Les intentions (intents). Ce sont des messages systme mis par le terminal pour prvenir les applications de la survenue de diffrents vnements, que ce soit une modification 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 seulement rpondre aux intentions, mais galement crer les vtres afin de lancer dautres activits ou pour vous prvenir quune situation particulire a lieu (vous pouvez, par exemple, mettre lintentionX lorsque lutilisateur est moins de 100mtres dun emplacementY).

L'art du dveloppement Android 2

Fonctionnalits votre disposition


Android fournit un certain nombre de fonctionnalits pour vous aider dvelopper des applications :

Stockage. Vous pouvez assembler des fichiers de donnes dans une application, pour y stocker ce qui ne changera jamais les icnes ou les fichiers daide, par exemple. Vous pouvez galement rserver un petit emplacement sur le terminal lui-mme, pour y stocker une base de donnes ou des fichiers contenant des informations ncessaires votre application et saisies par lutilisateur ou rcupres partir dune autre source. Si lutilisateur fournit un espace de stockage comme une carteSD, 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 spcifiques 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 (Global Positioning System). Les fournisseurs de positionnement, comme GPS, permettent dindiquer aux applications o se trouve le terminal. Il vous est alors possible dafficher des cartes ou dutiliser ces donnes gographiques pour retrouver la trace du terminal sil a t vol, par exemple. Services tlphoniques. Les terminaux Android sont, bien sr, gnralement des tlphones, ce qui permet vos programmes de passer des appels, denvoyer et de recevoir des SMS (Short Message Service) et de raliser tout ce que vous tes en droit dattendre dune technologie tlphonique moderne.

2
Projets et cibles
Aprs avoir tlcharg et install le dernier kit de dveloppement Android (SDK) et, ventuellement, lextension ADT (Android Developper Tools) dEclipse, vous tes prt travailler. Ce chapitre prsente tous les composants impliqus dans la construction dune application Android.

Les pices du puzzle


Pour crer une application Android, vous devez crer un projet, qui peut tre un projet Eclipse si vous utilisez cet environnement. Ce projet contiendra tout le code source, les ressources (les messages traduits dans diffrentes langues, par exemple), les JAR tiers et tout ce qui est associ lapplication. Les outils Android, quils soient intgrs Eclipse ou non, se chargeront ensuite de transformer le contenu de ce projet en un fichier paquetage Android (APK) reprsentant lapplication elle-mme. Ces outils vous aideront galement placer ce fichier APK sur un mulateur ou sur un vritable terminal Android afin de le tester.

L'art du dveloppement Android 2

Lun des lments essentiels dun projet Android est son manifeste (AndroidManifest. xml). Ce fichier contient en effet la "table des matires" de lapplication, il numre ses composants essentiels, il prcise ses permissions, etc. Android sen sert au lancement de lapplication pour intgrer celle-ci au systme dexploitation. Le contenu du manifeste est galement utilis par lAndroid Market (et, ventuellement, par dautres "app stores" indpendantes) pour que les applications qui requirent Android2.0 ne soient proposes ceux qui ont des terminaux Android1.5, par exemple. Pour tester votre application avec lmulateur, vous devrez crer un AVD (Android Virtual Device). Gnralement, on en cre plusieurs afin davoir un terminal virtuel par configu ration matrielle. Vous pouvez donc avoir des AVD pour des tailles dcran diffrentes, des versions dAndroid diffrentes, etc. Lorsque vous crez des projets et des AVD, vous devez indiquer Android le niveau de lAPI avec laquelle vous travaillez. Ce niveau est un simple entier correspondant une version dAndroid : le niveau3, par exemple, correspond Android1.5. Dans le cas dun projet, vous pourrez indiquer Android les niveaux minimum et maximum reconnus par votre application ; dans le cas dun AVD, vous pourrez signaler le niveau dAPI muler: vous saurez ainsi comment se comporte votre application sur diffrents (faux) terminaux utilisant des versions diffrentes dAndroid. Ce chapitre dcrit en dtail tous ces concepts.

Cration dun projet


android create project permet de crer un projet partir de la ligne de commande afin de lutiliser ensuite avec les outils comme ant. Cette commande reconnat un certain nombre doptions permettant dindiquer le paquetage Java dans lequel sera plac le code de lapplication, le niveau voulu pour lapplication, etc. Son excution produit un rpertoire contenant tous les fichiers ncessaires la cration dune application Android "Hello, World !".

Voici un exemple dutilisation :


android create project --target 2 --path ./FirstApp <FL> --activity FirstApp --package apt.tutorial

Si vous comptez vous servir dEclipse pour vos dveloppements Android, vous utiliserez plutt lassistant de cration de projets. Le code source qui accompagne cet ouvrage a t conu pour tre compil avec les outils en ligne de commande. Si vous prfrez utiliser Eclipse, vous pouvez crer des projets Android vides sous Eclipse, puis y importer le code.

Info

Chapitre 2

Projets et cibles

Structure dun projet


Le systme de construction dun programme Android est organis sous la forme dune arborescence de rpertoires spcifique un projet, exactement comme nimporte quel projet Java. Les dtails, cependant, sont spcifiques Android. 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
Lorsque vous crez un projet Android (avec android create project, par exemple), plusieurs lments sont placs dans le rpertoire racine du projet. Parmi eux:

AndroidManifest.xml est un fichier XML qui dcrit lapplication construire et les

composants activits, services, etc. fournis par celle-ci.


build.xml est un script Ant1 permettant de compiler lapplication et de linstaller sur le

terminal.
default.properties et local.properties sont des fichiers de proprits utiliss par le

script prcdent.
assets/ contient les autres fichiers statiques fournis avec lapplication pour son dploie-

ment sur le terminal.


bin/ contient lapplication compile. gen/ contient le code source produit par les outils de compilation dAndroid. libs/ contient les fichiers 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. assembles avec le code Java compil.


tests/ contient un projet Android entirement distinct, utilis pour tester celui que vous

avez cr.

la sueur de votre front


Lors de la cration dun projet Android (avec android create project, par exemple), vousdevez fournir le nom de classe ainsi que le chemin complet (paquetage) de lactivit
1. http://ant.apache.org/.

10

L'art du dveloppement Android 2

"principale" de lapplication (com.commonsware.android.UneDemo, par exemple). Vous constaterez alors que larborescence src/ de ce projet contient la hirarchie des rpertoires dfinis par le paquetage ainsi quun squelette dune sous-classe dActivity reprsentant lactivit principale (src/com/commonsware/android/UneDemo.java). Vous pouvez bien sr modifier ce fichier 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 fichier R.java dans le paquetage de lactivit "principale". Ce fichier contient un certain nombre de dfinitions de constantes lies aux diffrentes ressources de larborescence res/. Vous rencontrerez de nombreuses rfrences R.java dans les exemples de ce livre (par exemple, on dsignera lidentifiant dun layout par R.layout.main). Ne modifiez pas R.java vous-mme, laissez les outils Android le faire pour vous.

Info

La suite de lhistoire
Comme on la dj indiqu, larborescence res/ contient les ressources, cest--dire des fichiers 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 fichiers gnraux (un fichier CSV contenant les informations dun

compte, par exemple);


res/values/ pour les messages, les dimensions, etc.; res/xml/ pour les autres fichiers XML gnraux que vous souhaitez fournir.

Nous prsenterons tous ces rpertoires, et bien dautres, dans la suite de ce livre.

Le fruit de votre travail


Lorsque vous compilez un projet (avec ant ou un IDE), le rsultat est plac dans le rpertoire 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.

Chapitre 2

Projets et cibles

11

bin/votreapp.ap_ (o votreapp est le nom de lapplication) contient les ressources de

celle-ci, sous la forme dun fichier ZIP.


bin/votreapp-debug.apk ou bin/votreapp-unsigned.apk est la vritable application

Android.

Le fichier .apk est une archive ZIP contenant le fichier .dex, la version compile des ressources (resources.arsc), les ventuelles ressources non compiles (celles qui se trouvent sous res/raw/, par exemple) et le fichier AndroidManifest.xml. Cette archive est signe : la partie -debug du nom de fichier 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 officielle.

Contenu du manifeste
Le point de dpart de toute application Android est son fichier manifeste, AndroidManifest.xml, qui se trouve la racine du projet. Cest dans ce fichier 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 exemple, 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 conviendra srement ou ncessitera ventuellement quelques modifications mineures. En revanche, le manifeste de lapplication de dmonstration Android fournie contient plus de 1000lignes. Vos applications se situeront probablement entre ces deux extrmits. La plupart des parties intressantes du manifeste seront dcrites en dtail dans les chapitres consacrs aux fonctionnalits dAndroid qui leur sont associes llment service, par exemple, est dtaill au Chapitre29. Pour le moment, il vous suffit de comprendre le rle de ce fichier et la faon dont il est construit.

Au dbut, il y avait la racine


Tous les fichiers manifestes ont pour racine un lment manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.search"> ... </manifest>

12

L'art du dveloppement Android 2

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, et non android:manifest). Nous vous conseillons de conserver cette convention, sauf si Android la modifie 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 fichier, vous pourrez alors simplement utiliser le symbole point pour dsigner ce paquetage : si vous devez, par exemple, faire rfrence com.commonsware.android.search. Snicklefritz dans le manifeste de cet exemple, il suffira dcrire .Snicklefritz puisque com.commonsware.android.search est dfini 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. 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. 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. Un lment uses-sdk indiquant la version du SDK Android avec laquelle a t construite lapplication. Un lment application qui dfinit le cur de lapplication dcrite par le manifeste.
<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

Chapitre 2

Projets et cibles

13

android:name="android.permission.ACCESS_CELL_ID" /> <application> ... </application> </manifest>

Le manifeste de cet exemple 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, par exemple. Llment application dcrit les activits, les services et tout ce qui constitue lapplication elle-mme. Les permissions seront dcrites en dtail au Chapitre 28.

Que fait votre application?


Le plat de rsistance du fichier manifeste est dcrit par les fils de llment application. Par dfaut, la cration dun nouveau projet Android nindique quun seul lment activity:
<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 lactivity, android:label pour le nom affich de lactivity et (souvent) un lment fils intent-filter dcrivant les conditions sous lesquelles cette activit saffichera. Llment activity de base configure votre activit pour quelle apparaisse dans le lanceur sous forme dune icne et que les utilisateurs puissent lexcuter. Comme nous le verrons plus tard, un mme projet peut dfinir plusieurs activits. Il peut galement y avoir un ou plusieurs lments provider indiquant 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 encapsulent 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.

14

L'art du dveloppement Android 2

Enfin, 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 permanence. Lexemple classique est celui du lecteurMP3, qui permet de continuer couter de la musique, mme si lutilisateur ouvre dautres activits et que linterface utilisateur nest pas affiche au premier plan. La cration et lutilisation des services sont dcrites aux Chapitres29 et 30.

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 suprieure) dun environnement Android, ajoutez un lment uses-sdk comme fils de llment racine manifest du fichier AndroidManifest.xml. uses-sdk dispose de lattribut minSdkVersion, indiquant 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 sept valeurs:

1 pour indiquer le premier SDK dAndroid, la version1.0; 2 pour indiquer la version1.1 du SDK dAndroid; 3 pour indiquer la version1.5; 4 pour indiquer la version1.6; 5 pour indiquer la version2.0; 6 pour indiquer la version2.0.1; 7 pour indiquer la version2.1.

Labsence dun lment uses-sdk revient utiliser1 comme valeur de minSdkVersion. La boutique Android (Android Market) 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.

ntion Atte

Chapitre 2

Projets et cibles

15

Si cet lment est prsent, lapplication ne pourra sinstaller que sur les terminaux compatibles. Vous navez pas besoin de prciser la dernire version du SDK mais, si vous en choisissez une plus ancienne, cest vous de vrifier 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:versionName llment manifest afin 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", "System V", "5000" ou "3.1" en fonction de vos prfrences.
android:versionCode est un entier cens reprsenter le numro de version de lappli

cation. Le systme lutilise pour savoir si votre version est plus rcente quune autre "plus rcent" tant dfini 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.

mulateurs et cibles
Prenons un moment pour prsenter la notion de cibles Android car elle est parfois mal comprise. Les cibles sont importantes pour le dveloppement des applications sur le long terme, notamment lorsque lon utilise lmulateur pour les tester.

Cration dun AVD


Pour utiliser lmulateur, vous devez crer un ou plusieurs AVD. Ces terminaux virtuels sont conus pour imiter les vritables terminaux Android comme le G1 ou le HTC Magic. Il suffit dindiquer lAVD que lon utilise et lmulateur prtendra quil est le terminal dcrit par celui-ci. Vous devez indiquer une cible lorsque vous crez un AVD. Cette cible indique la classe de terminaux que lAVD mulera. lheure o ce livre est crit, il existe sept cibles:

1: terminal Android1.1, comme un G1 non mis jour; 2: terminal Android1.5, sans le support de Google Maps (ce qui peut tre le cas dune version Android fournie pour un terminal particulier);

16

L'art du dveloppement Android 2

3: terminal Android1.5 disposant du support de Google Maps; 4: terminal Android1.6 disposant du support de Google Maps; 5: terminal Android2.0 disposant du support de Google Maps; 6: terminal Android2.0.1 disposant du support de Google Maps; 7: terminal Android2.1 disposant du support de Google Maps. La commande android list targets vous donnera la listes des API disponibles.

Info

Si vous dveloppez des applications utilisant Google Maps, vous devez donc utiliser un AVD ayant une cible suprieure ou gale 3. Vous pouvez crer autant dAVD que vous le souhaitez du moment que vous avez assez despace disque disponible sur votre environnement de dveloppement. Chaque AVD se comporte comme un terminal totalement distinct: linstallation dune application sur un AVD naffecte pas les autres AVD que vous avez crs. Pour crer un AVD, utilisez la commande android create avd, Eclipse ou le gestionnaire dAVD (AVD Manager), un outil graphique qui a t ajout au SDK dAndroid1.6. Pour utiliser ce dernier, lancez simplement la commande android sans aucun paramtre. Comme le montre la Figure2.1, le gestionnaire affiche une liste dAVD prdfinis. Les boutons New et Delete permettent, respectivement, dajouter et de supprimer des AVD; le bouton Start lance un mulateur avec lAVD slectionn, etc.
Figure2.1 Loutil graphique AVD Manager, montrant la liste des AVD disponibles.

Chapitre 2

Projets et cibles

17

Pour ajouter un AVD avec linterface graphique et son bouton New, il faut fournir un nom, une combinaison API/Google, des dtails sur limage dune carte SD et la taille de lcran (le skin) que lon veut muler. La Figure2.2 montre la bote de dialogue de cration dun AVD.
Figure2.2 Ajout dun AVD.

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 cible3 donne les indications suivantes:

Vous avez besoin dAndroid1.5. Vous avez besoin de Google Maps.

Lapplication finale ne sinstallera donc pas sur les terminaux qui ne correspondent pas au minimum ces critres. Voici quelques rgles pour vous aider grer les cibles:

Demandez-vous ce dont vous avez rellement besoin. Si vous vous en tenez aux API1.5, ne compilez votre application que pour Android1.5. Testez autant de cibles que possible. Vous pourriez tre tent par la cible1 pour viser le plus grand nombre de terminaux Android; cependant, vous devrez alors tester votre

18

L'art du dveloppement Android 2

application sur un AVD ayant une cible 1 et un AVD ayant une cible 2 (et il serait ga lement souhaitable de la tester avec un AVD ayant une cible3, au cas o).

Vrifiez quune nouvelle cible na pas t ajoute par une nouvelle version dAndroid. Ildevrait 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 nements tests sur du vrai matriel. Les AVD sont conus pour vous fournir des "environ 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.

3
Cration dun squelette dapplication
Tous les livres consacrs un langage ou un environnement de programmation commencent par prsenter un programme de dmonstration de type "Bonjour tous!": il permet de montrer que lon peut construire quelque chose tout en restant suffisamment 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, ventuellement, le plugin ADT dEclipse). Avant de commencer, nous devons prsenter la notion

20

L'art du dveloppement Android 2

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 afin quil puisse prtendre quil est bien le terminal dcrit par cet AVD.

Commencer par le dbut


Avec Android, comme nous lavons vu au chapitre prcdent, tout commence par la cration dun projet. 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 Java o tout devra se trouver:
android create project \ --target 2 \ --path chemin/vers/mon/projet \ --activity Now \ --package com.commonsware.android.skeleton

Vous pouvez galement tlcharger les rpertoires des projets exemples de ce livre sous la forme de fichiers ZIP partir du site web de Pearson1. Ces projets sont prts tre utiliss: vous navez donc pas besoin dutiliser android create project lorsque vous aurez dcompress ces exemples. 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 produit donc src/com/commonsware/android/). Dans le rpertoire le plus bas, vous trouverez un fichier source nomm Now.java, dans lequel sera stock le code de votre premire activit. Celle-ci sera constitue dun unique bouton qui affichera 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). Si vous avez tlcharg les fichiers partir du site web de Pearson, vous pouvez vous contenter dutiliser directement le projet Skeleton/Now.

Info

1. http://www.pearson.fr.

Chapitre 3

Cration dun squelette dapplication

21

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); } public void onClick(View view) { updateTime(); } private void updateTime() { btn.setText(new Date().toString()); } }

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 rfrence. La plupart de celles qui sont spcifiques Android se trouvent dans le paquetage android:
package com.commonsware.android.skeleton; import android.app.Activity;

22

L'art du dveloppement Android 2

import import import import

android.os.Bundle; android.view.View; android.widget.Button; 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 Android1 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.Activity. Ici, lactivit contient un bouton (btn):
public class Now extends Activity implements View.OnClickListener { Button btn;

Info

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.

Pour rester simple, nous voulons capturer tous les clics de bouton dans lactivit ellemme : 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 afin de raliser linitialisation de lactivit 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 setOnClickListener()). Nous appelons ensuite la mthode prive updateTime() (qui sera prsente plus loin), puis

1. http://code.google.com/android/reference/packages.html.

Chapitre 3

Cration dun squelette dapplication

23

nous configurons 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 gnralement linterface graphique partir dune hirarchie de vues, nous nutiliserons ici quune seule vue.

Info

Nous prsenterons ce Bundle icicle magique au Chapitre16. Pour linstant, considronsle 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 configur pour ce bouton. Avec Android, en revanche, un clic sur un bouton provoque lappel de la mthode onClick() sur linstance OnClickListener configure 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 updateTime():
private void updateTime() { btn.setText(new Date().toString()); }

Louverture de lactivit (onCreate()) ou un clic sur le bouton (onClick()) doit provoquer 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 en excutant la commande android, en choisissant un AVD dans le gestionnaire dAVD puis en cliquant sur le bouton Start. Vous devriez pouvoir vous contenter des valeurs par dfaut qui vous sont prsentes dans la bote de dialogue des options. La Figure3.1 montre lcran daccueil dAndroid. La premire fois que vous utilisez un AVD avec lmulateur, le temps de lancement est beaucoup plus long que les fois suivantes.

Info

24

L'art du dveloppement Android 2

Figure3.1 Lcran daccueil dAndroid.

2. Installez le paquetage (avec ant install, par exemple). 3. Consultez la liste des applications installes sur lmulateur et recherchez celle qui sappelle Now (voir Figure3.2).
Figure3.2 Le lanceur dapplications dAndroid.

4. Ouvrez cette application. Vous devriez voir apparatre un cran dactivit comme celui de la Figure3.3.

Chapitre 3

Cration dun squelette dapplication

25

Figure3.3 Dmonstration de lactivit Now.

En cliquant sur le bouton en dautres termes, quasiment nimporte o sur lcran du tlphone , lheure courante saffichera sur le label du bouton. Vous remarquerez que ce label est centr horizontalement et verticalement car cest le style par dfaut. Nous verrons au Chapitre5 que nous pouvons videmment contrler ce formatage. Une fois repu de cette technologie poustouflante des boutons, vous pouvez cliquer sur le bouton de retour en arrire de lmulateur pour revenir au lanceur.

4
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 Chapitre4, on prfre gnralement employer un fichier de positionnement (layout) cod en XML. Linstanciation dynamique des widgets est rserve aux scnarios plus complexes, 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 spcification des relations existant entre les composants widgets et avec leurs conteneurs (voir Chapitre6) exprime sous la forme dun document XML. Plus prcisment, Android considre les layouts XML comme des ressources stockes dans le rpertoire res/layout du projet.

28

L'art du dveloppement Android 2

Chaque fichier 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, signifie 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 fichier build.xml de Ant). Cest aapt qui produit le fichier 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 fichiers de positionnement XML peut galement tre ralis avec du code Java. Vous pourriez, par exemple, utiliser setTypeface() pour quun bouton affiche son texte en gras au lieu dutiliser une proprit dans un fichier XML. Ces fichiers 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 dfinition des vues: le constructeur dinterfaces graphiques dun IDE comme Eclipse ou un assistant ddi la cration des interfaces graphiques dAndroid, comme DroidDraw1, 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 dfinition dune interface graphique afin de la modifier lorsque cette dfinition est exprime dans un format structur comme XML au lieu dtre code dans un langage de programmation. En outre, sparer ces dfinitions XML du code Java rduit les risques quune modification du code source perturbe lapplication. XML est un bon compromis entre les besoins des concepteurs doutils et ceux des programmeurs. En outre, lutilisation de XML pour la dfinition des interfaces graphiques est devenue monnaie courante. XAML (eXtensible Application Markup Language)2 de Microsoft, Flex3 dAdobe et XUL (XML User interface Language)4 de Mozilla utilisent toutes une approche quivalente de celle dAndroid: placer les dtails des positionnements dans un fichierXML en permettant de les manipuler partir des codes sources ( laide de JavaScript pour XUL, par exemple). De nombreux frameworks graphiques moins connus, comme ZK5, utilisent
1. 2. 3. 4. 5. http://droiddraw.org/. http://windowssdk.msdn.microsoft.com/en-us/library/ms752059.aspx. http://www.adobe.com/products/flex/. http://www.mozilla.org/projects/xul/. http://www.zkoss.org/.

Chapitre 4

Utilisation des layouts XML

29

galement XML pour la dfinition de leurs vues. Bien que "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 fichier layout


Voici le bouton de lapplication du chapitre prcdent, converti en un fichier XML que vous trouverez dans le rpertoire Layouts/NowRedux des codes de ce 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 suffit dutiliser simplement son nom de classe. Dans le cas dun widget personnalis, driv dandroid.view.View, il faudrait utiliser un nom pleinement qualifi, 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 fils de la racine et hritent de cette dclaration despace de noms. Comme lon souhaite pouvoir faire rfrence ce bouton partir de notre code Java, il faut lui associer un identifiant 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 affich 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 Chapitre6.

Ce widget tant le seul contenu de notre activit, nous navons besoin que de cet lment. Les vues plus complexes ncessitent une arborescence dlments, afin de reprsenter les widgets et les conteneurs qui contrlent leur positionnement. Dans la suite de ce livre, nous

30

L'art du dveloppement Android 2

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.

Identifiants des widgets


De nombreux widgets et conteneurs ne peuvent apparatre que dans le fichier de positionnement et ne seront pas utiliss par votre code Java. Le plus souvent, un label statique (TextView), par exemple, na besoin dtre dans le fichier 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 didentifiant, o nom_unique reprsente le nom local du widget, qui doit tre unique. Dans lexemple de la section prcdente, lidentifiant du widget Button tait @+id/button. Android utilise galement quelques valeurs android:id spcifiques, de la forme @android:id/.... Nous les rencontrerons dans diffrents chapitres de ce livre.

Utilisation des widgets dans le code Java


Une fois que vous avez douloureusement configur les widgets et les conteneurs dans un fichier de positionnement XML nomm main.xml et que vous lavez plac dans le rpertoire 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 fichier XML est dsormais accessible partir de la classe R. Tous les positionnements dfinis se trouvent sous R.layout, indiqus par le nom de base du fichier R.layout.main dsigne donc main.xml. Pour accder nos widgets, nous utilisons ensuite la mthode findViewById(), en lui passant lidentifiant numrique du widget concern. Cet identifiant 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 prcdent.

Chapitre 4

Utilisation des layouts XML

31

Fin de lhistoire
Dans le premier exemple Now, le texte du bouton affichait 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 appellerons 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 rfrence au fichier XML de positionnement (avec setContentView(R.layout.main)). Le fichier source R.java sera mis jour lorsque le projet sera recompil, afin dinclure une rfrence au fichier de positionnement (main.xml du rpertoire res/layout).

32

L'art du dveloppement Android 2

La seconde diffrence est quil faut retrouver linstance de notre bouton en appelant la mthode findViewById(). Comme lidentifiant de ce bouton est @+id/button, nous pouvons dsigner son identifiant numrique par R.id.button. Il reste ensuite mettre en place lcouteur dvnement et configurer son label. La Figure4.1 montre que le rsultat est le mme que celui de lexemple Now prcdent.
Figure4.1 Lactivit NowRedux.

5
Utilisation des widgets debase
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 modifiables par les utilisateurs. Ils servent gnralement identifier 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 fichiers layout XML en ajoutant un lment TextView dot dune proprit android:text pour dfinir le texte qui lui est associ. Si vous devez changer des

34

L'art du dveloppement Android 2

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 Chapitre20. Un lment TextView possde de nombreux autres attributs, notamment:

android:typeface pour dfinir 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 dfinir la couleur du texte du label, au format RGB hexadcimal (#FF0000 pour un texte rouge, par exemple).

Voici le contenu du fichier 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 Figure5.1, ce fichier seul, avec le squelette Java fourni par la chane de production dAndroid (android create project), produira lapplication voulue.
Figure5.1 Lapplication LabelDemo.

Chapitre 5

Utilisation des widgets debase

35

Boutons
Nous avons dj utilis le widget Button dans les deux chapitres prcdents. 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. Cependant, Android1.6 ajoute une nouvelle fonctionnalit permettant de dclarer un couteur "de clic" (Listener) pour un bouton. Outre lapproche classique consistant dfinir un objet (comme lactivit) comme une implmentation de linterface View.OnClickListener, nous pouvons dsormais utiliser une approche un peu plus simple:

dfinir une mthode publique de lactivit qui prend un unique paramtre View et qui renvoie void; dans llment Button du fichier de positionnement XML, inclure lattribut android:onClick en prcisant le nom de cette mthode.
public void uneMethode(View leBouton) { // Faire quelque chose }

Nous pourrions, par exemple, avoir une mthode de lactivit qui ressemble celle-ci:

On ajouterait ensuite lattribut android:onClick la dclaration XML concernant le bouton:


<Button android:onClick="uneMethode" ... />

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 Chapitre 20). Vous pouvez galement configurer 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 stan-

dard pour rpondre aux clics et autres actions.

36

L'art du dveloppement Android 2

Examinons, par exemple, le contenu du fichier 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 Figure5.2.
Figure5.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), EditText 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.

Chapitre 5

Utilisation des widgets debase

37

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?). La plupart de ces proprits sont galement disponibles avec le nouvel attribut
android:inputType introduit par Android 1.5 pour lui ajouter des "claviers logiciels"

(prsents au Chapitre10). 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 signifie que les utilisateurs pourront saisir plusieurs lignes de texte dans ce champ. Le fichier 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"); } }

38

L'art du dveloppement Android 2

La Figure5.3 montre le rsultat obtenu.


Figure5.3 Lapplication FieldDemo.

Certains champs offrent une compltion automatique afin de permettre lutilisateur dentrer des informations sans taper lintgralit du texte. Avec Android, ces champs sont des widgets AutoCompleteTextView; ils seront prsents au Chapitre9.

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, par exemple) permettent galement de formater ce widget. Dans le 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"

Chapitre 5


android:id="@+id/check" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cette case est: decochee" />

Utilisation des widgets debase

39

Le fichier CheckBoxDemo.java correspondant rcupre cette case cocher et configure 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 modifier le texte de la case pour reflter son tat courant. Cliquer sur la case modifie donc immdiatement son texte, comme le montrent les Figures5.4 et 5.5.

40

L'art du dveloppement Android 2

Figure5.4 Lapplication CheckBoxDemo avec la case dcoche.

Figure5.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 ils 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

Chapitre 5

Utilisation des widgets debase

41

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 afin quun seul puisse tre slectionn un instant donn. En affectant un identifiant android:id au RadioGroup dans le fichier 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 identifiant (avec groupe.check(R. id.radio1)); clearCheck() pour dcocher tous les boutons du groupe; getCheckedRadioButtonId() pour obtenir lidentifiant 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 Figure5.6 montre le rsultat obtenu en utilisant le projet Java de base, fourni par Android.

42

L'art du dveloppement Android 2

Figure5.6 Lapplication RadioButtonDemo.

Vous remarquerez que, au dpart, aucun bouton du groupe nest coch. Pour que lapplication 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 celles qui contrlent la squence de focus:

android:nextFocusDown; android:nextFocusLeft; android:nextFocusRight; android:nextFocusUp.

La proprit android:visibility, quant elle contrle la visibilit initiale du widget.

Chapitre 5

Utilisation des widgets debase

43

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 CheckBox 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 fils daprs son identifiant. getRootView() renvoie la racine de larborescence (celle que vous avez fournie lactivit via un appel setContentView()).

Couleurs
Les widgets Android disposent de deux types dattributs pour contrler la couleur. Certains, comme android:background, prennent une seule couleur (ou une image servant de fond). Dautres, comme android:textColor, sappliquent TextView (ou ses sous-classes) et peuvent prendre une ColorStateList. Une ColorStateList permet de prciser plusieurs couleurs correspondant diffrentes situations. Un TextView peut, par exemple, utiliser une couleur de texte particulire lorsquun lment est slectionn dans une liste et une autre lorsquil ne lest pas (nous prsenterons les widgets de slection au Chapitre7). Tout ceci est gr par la ColorStateList associe au TextView. Pour modifier la couleur dun widget TextView partir du code Java, deux choix sont possibles:

Utiliser ColorStateList.valueOf(), qui renvoie une ColorStateList dans laquelle tous les tats sont associs la couleur fournie en paramtre valueOf(). Cest lquivalent Java de lapproche android:textColor, qui donne toujours la mme couleur au TextView quelles que soient les circonstances. Crer une ColorStateList avec des valeurs diffrentes pour chaque tat, soit au moyen du constructeur, soit via un document XML.

6
Conteneurs
Les conteneurs permettent de disposer un ensemble de widgets (et, ventuellement, des conteneurs fils) 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 formulaire, 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 afin 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 conteneurs. 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 disposition 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.

46

L'art du dveloppement Android 2

Dans ce chapitre, nous tudierons quatre conteneurs parmi les plus courants : LinearLayout (le modle des botes), RelativeLayout (un modle de positionnement relatif), TableLayout (le modle en grille) et ScrollView, un conteneur conu pour faciliter la mise en place des conteneurs avec barres de dfilement.

Penser de faon linaire : LinearLayout


LinearLayout est un modle reposant sur des botes les widgets ou les conteneurs fils 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 principalement identifier les imbrications et les proprits des diffrentes botes leur alignement par rapport aux autres botes, par exemple.

Concepts et proprits
Pour configurer 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 suffit dajouter la proprit android:orientation llment LinearLayout du fichier XML en fixant sa valeur horizontal pour une ligne ou vertical pour une colonne. Cette orientation peut tre modifie en cours dexcution en appelant la mthode
set Orientation() du conteneur 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 exemple. 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.

Chapitre 6

Conteneurs

47

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 125pixels. 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 le placement des autres widgets. 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 est1 pour un widget et2 pour lautre, le second utilisera deux fois plus despace libre que le premier, etc. Le poids dun widget est fix zro par dfaut. Un autre moyen dutiliser les poids consiste allouer des pourcentages. Pour utiliser cette technique avec une disposition en ligne, par exemple : Initialisez zro les valeurs android:layout_width de tous les widgets du layout. Initialisez avec les pourcentages adquats les valeurs android:layout_weight de tous les widgets du layout. Assurez-vous que la somme de ces pourcentages soit gale 100.

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.

48

L'art du dveloppement Android 2

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 sa mthode setGravity() en cours dexcution) afin dindiquer au widget et son conteneur comment laligner par rapport lcran. Pour une colonne de widgets, les gravits les plus courantes sont left, center_horizontal 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 verticalement 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 en cours dexcution la mthode setPadding() du widget). La valeur de remplissage prcise lespace situ entre le contour de la "cellule" du widget et son contenu rel, comme le montre la Figure6.1.
Figure6.1 Relations entre un widget, sa cellule et ses valeurs de remplissage.

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. La valeur de ces proprits est une dimension, comme 5px pour demander un remplissage de 5pixels.

Chapitre 6

Conteneurs

49

Si vous appliquez un fond au widget (avec lattribut android:background, par exemple), ce fond sera plac la fois derrire le widget et sa zone de remplissage. Si ce nest pas ce que vous souhaitez, utilisez des marges plutt que le remplissage car celles-ci ajoutent de lespace sans augmenter la taille intrinsque du widget. Pour configurer ces marges, utilisez les attributs comme android:layout_marginTop.

Exemple
Prenons un exemple dapplication qui configure les proprits dun conteneur LinearLayout la fois dans le fichier de description XML et en cours dexcution. Voici le contenu du fichier layout/main.xml du projet Containers/Linear:
<?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" 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>

50

L'art du dveloppement Android 2

Vous remarquerez que le conteneur LinearLayout enveloppe deux RadioGroup. RadioGroup tant une sous-classe de LinearLayout, notre exemple revient donc imbriquer des conteneurs LinearLayout. Le premier lment RadioGroup configure une ligne (android:orientation = "horizontal") de widgets RadioButton. Il utilise un remplissage de 5pixels sur ses quatre cts, afin 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 5pixels 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 signifie 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 import import import import import import android.app.Activity; android.os.Bundle; android.view.Gravity; android.text.TextWatcher; android.widget.LinearLayout; android.widget.RadioGroup; android.widget.EditText; LinearLayoutDemo extends Activity RadioGroup.OnCheckedChangeListener { orientation; gravity;

public class implements RadioGroup RadioGroup

@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); }

Chapitre 6


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); } }

Conteneurs

51

} }

Dans onCreate(), nous recherchons nos deux conteneurs RadioGroup et nous enregistrons un couteur pour chacun deux afin dtre prvenu du changement dtat des boutons radio (setOnCheckedChangeListener(this)). Lactivit implmentant linterface OnCheckedChangeListener, 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 lorientation en fonction du choix de lutilisateur. Sil sagit du groupe gravity, on modifie la gravit. La Figure6.2 montre ce quaffiche lapplication lorsquelle est lance dans lmulateur.
Figure6.2 Lapplication LinearLayoutDemo lors de son lancement.

Si lon clique sur le bouton vertical, le RadioGroup du haut sajuste en consquence (voir Figure6.3).

52

L'art du dveloppement Android 2

Figure6.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 Figures6.4 et6.5).
Figure6.4 La mme application, avec les boutons vertical et centre cochs.

Figure6.5 La mme application, avec les boutons vertical et droite cochs.

Chapitre 6

Conteneurs

53

Tout est relatif : RelativeLayout


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 align avec le bord infrieur du conteneur, etc. Ce gestionnaire de placement ressemble donc au conteneur RelativeLayout1 de James Elliot pour Java Swing.

Concepts et proprits
Pour utiliser un conteneur RelativeLayout, il faut pouvoir faire rfrence dautres widgets dans le fichier 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 horizonta

lement 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 horizonta lement

et verticalement dans le conteneur.

Toutes ces proprits prennent soit la valeur true, soit la valeur false.

1.

http://www.onjava.com/pub/a/onjava/2002/09/18/relativelayout.html.

54


Info

L'art du dveloppement Android 2

Le remplissage du widget est pris en compte lors de ces alignements. Ceux-ci reposent 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:

Associez des identifiants (attributs android:id) tous les lments que vous aurez besoin de dsigner, sous la forme @+id/.... Dsignez un widget en utilisant son identifiant, priv du signe plus (@id/...).

Si, par exemple, le widget A est identifi 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.

Chapitre 6

Conteneurs

55

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 afin 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 widgetB soit plac droite du widgetA, llment XML du widgetB doit donc contenir android:layout_toRight = "@id/widget_a" (o @id/widget_a est lidentifiant du widgetA).

Ordre dvaluation
Avant la version1.6, Android ne lisait quune seule fois le fichier XML et calculait donc en squence la taille et la position de chaque widget. Ceci signifiait que lon ne pouvait pas faire rfrence un widget qui navait pas t dfini plus haut dans le fichier, ce qui compliquait un peu les choses. Dsormais, Android traite les rgles en effectuant deux passes: vous pouvez donc utiliser des rfrences vers des widgets qui sont dfinis plus loin dans le fichier.

Exemple
titre dexemple, tudions un "formulaire" classique, compos dun champ, dun label et de deux boutons, OK et Annuler. Voici le fichier 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"

56

L'art du dveloppement Android 2

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 ncessaire (android:layout_height = "wrap_content"), avec un remplissage de 5pixels entre les limites du conteneur et son contenu (android:padding = "5px"). Puis nous dfinissons 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 15pixels 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 dfinie (puisquil apparat avant lui dans le fichier XML), le champ serait trop haut et son bord suprieur serait rogn par le remplissage du conteneur.

Chapitre 6

Conteneurs

57

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 15pixels de remplissage au-dessus du label, afin de le pousser suffisamment vers le bas pour que le champ ne soit pas coup. 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"). Bien sr, ce remplissage de 15pixels est un peu du bricolage. partir dAndroid1.6, une meilleure solution consiste ancrer le widget EditText en haut de lcran et aligner le TextView avec la ligne de base de celui-ci, comme le montre lexemple qui suit (ce ntait pas possible dans les versions antrieures cause de linterprtation en une seule passe que nous avons voque plus haut).
<?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:layout_alignBaseline="@+id/entry" android:layout_alignParentLeft="true"/> <EditText android:id="@id/entry" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_toRightOf="@id/label" android:layout_alignParentTop="true"/> <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" />

58

L'art du dveloppement Android 2

<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>

Sans rien changer au code Java qui a t produit automatiquement, lmulateur affiche le rsultat prsent la Figure6.6.
Figure6.6 Lapplication RelativeLayoutDemo.

Tabula Rasa : TableLayout


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 dfinir 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.

Chapitre 6

Conteneurs

59

Placement des cellules dans les lignes


Cest vous, le dveloppeur, qui dclarez les lignes en plaant les widgets comme des fils dun lment TableRow, lui-mme fils 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 stendre le widget concern. Cette proprit ressemble donc lattribut colspan utilis dans les tableauxHTML. Avec ce fragment XML, le champ de saisie EditText stendra sur trois colonnes.
<TableRow> <TextView android:text="URL :" /> <EditText android:id="@+id/entry" android:layout_span="3"/> </TableRow>

Gnralement, les widgets sont placs dans la premire colonne disponible. Dans lextrait prcdent, par exemple, le label irait dans la premire colonne (la colonne0 car leur numrotation commence 0) et le champ stendrait sur les trois colonnes suivantes (les colonnes1 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 colonne2) et le bouton OK, dans la colonne disponible suivante, cest--dire la quatrime.

60

L'art du dveloppement Android 2

Fils de TableLayout qui ne sont pas des lignes


Gnralement, les seuls fils directs de TableLayout sont des lments TableRow. Cependant, 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 fixes fill_parent pour remplir le mme espace que la ligne la plus longue. Un cas dutilisation de cette configuration consiste se servir dun widget View (<View android:layout_height = "2px" android:background = "#0000FF" />) pour crer une barre de sparation bleue de 2pixels 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). Ceci permet dviter quun contenu trop long pousse certaines colonnes droite de lcran. Vous pouvez galement tirer parti de la proprit android:collapseColumns de TableLayout, en indiquant l aussi un numro ou une liste de numros de colonnes. Cellesci seront alors initialement "refermes", ce qui signifie 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 TableLayout. 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

Chapitre 6

Conteneurs

61

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 Figure6.7.


Figure6.7 Lapplication TableLayoutDemo.

62

L'art du dveloppement Android 2

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 dfilement, afin que seule une partie de linformation soit visible un instant donn, le reste tant disponible en faisant dfiler lcran vers le haut ou vers le bas.
ScrollView est un conteneur qui fournit un dfilement 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 dfilement. Voici par exemple un lment ScrollView qui enveloppe un TableLayout (ce fichier 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

Chapitre 6

Conteneurs

63

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 dfinition de llment View). Certains terminaux peuvent avoir des crans

64

L'art du dveloppement Android 2

capables dafficher 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 Figure6.8 montre ce quaffichera lmulateur dAndroid au lancement de lactivit.
Figure6.8 Lapplication ScrollViewDemo.

Vous remarquerez que lon ne voit que cinq lignes. En pressant les boutons haut et bas du pad directionnel, vous pouvez faire dfiler lcran afin de faire apparatre les lignes restantes. Vous remarquerez galement que le bord droit du contenu est masqu par la barre de dfilement pour viter ce problme, vous pourriez ajouter des pixels de remplissage sur ce ct. Android1.5 a ajout HorizontalScrollView, qui fonctionne comme ScrollView, mais en horizontal. Ce dfilement peut tre utile pour les formulaires qui sont trop larges pour tenir en entier lcran. Notez que ni HorizontalScrollView ni ScrollView ne vous permettent de dfiler dans les deux directions: vous devez choisir entre vertical ou horizontal.

7
Widgets de slection
Au Chapitre5, nous avons vu que les champs pouvaient imposer des contraintes sur leur contenu possible, afin de forcer une saisie uniquement numrique. 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.

66

L'art du dveloppement Android 2

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 spcifiques pour quelles saffichent 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 dveloppement graphiques pour redfinir laffichage 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 modifient ltat de cette liste), vous finirez 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. Ladaptateur le plus simple est ArrayAdapter puisquil suffit dencapsuler 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); lidentifiant de ressource de la vue utiliser (un identifiant dune ressource systme prdfinie, comme dans lexemple prcdent); le tableau ou la liste dlments afficher.

Par dfaut, ArrayAdapter appellera la mthode toString() des objets de la liste et enveloppera 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, safficheront dans la liste, le spinner ou tout widget qui utilise cet ArrayAdapter. Au Chapitre8, nous verrons comment crer une sous-classe dArray

Chapitre 7

Widgets de slection

67

Adapter pour redfinir la cration des lignes afin davoir un meilleur contrle sur leur

apparence. Voici dautres adaptateurs dont vous aurez certainement besoin:

CursorAdapter convertit un Cursor, gnralement fourni par un content provider, en

un objet pouvant safficher dans une vue de slection (nous tudierons plus en dtail
CursorAdapter au Chapitre22, avec les bases de donnes). SimpleAdapter convertit les donnes trouves dans les ressources XML.

Listes des bons et des mchants


Le widget classique dAndroid pour les listes sappelle ListView. Pour disposer dune liste compltement fonctionnelle, il suffit dinclure un objet ListView dans votre prsentation, dappeler setAdapter() pour fournir les donnes et les vues filles, puis dattacher un couteur via setOnItemSelectedListener() pour tre prvenu de toute modification 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 didentifier cette ListView par @android:id/list, afin que ListActivity sache quelle est la liste principale de lactivit. Voici, par exemple, le fichier de disposition du projet Selection/List, qui affiche simplement une liste surmonte dun label qui devra afficher en permanence la slection courante:
<?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"/>

68

L'art du dveloppement Android 2

<ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:drawSelectorOnTop="false" /> </LinearLayout>

Le code Java permettant de configurer 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 configurer ladaptateur dune ListActivity par un appel setList Adapter() ici, on fournit un ArrayAdapter qui enveloppe un tableau de chanes quelconques. Pour tre prvenu des changements dans la liste de slection, on redfinit onListItemClick() pour quelle agisse de faon approprie en tenant compte de la vue fille et de

Chapitre 7

Widgets de slection

69

laposition qui lui sont passes en paramtre (ici, elle crit dans le label le texte situ cette position). Le rsultat est montr la Figure7.1.
Figure7.1 Lapplication ListViewDemo.

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. Par dfaut, ListView est simplement configure pour recevoir les clics sur les entres dela 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 modifications : 1. Dans le code Java, appelez la mthode setChoiceMode() de lobjet ListView afin de configurer le mode de slection en lui passant en paramtre la constante CHOICE_MODE_ SINGLE ou CHOICE_MODE_MULTIPLE (pour obtenir lobjet ListView, il suffit dappeler la mthode getListView() partir dune ListActivity). 2. Puis, au lieu de passer en paramtre android.R.layout.simple_list_item_1 au constructeur dArrayAdapter, utilisez soit android.R.layout.simple_list_item_ single_choice, soit android.R.layout.simple_list_item_multiple_choice pour mettre en place, respectivement, une liste choix unique (voir Figure7.2) ou choix multiples (voir Figure7.3). 3. Pour savoir quel est llment choisi par lutilisateur, appelez la mthode getChecked ItemPositions() du widget ListView.

70

L'art du dveloppement Android 2

Figure7.2 Liste choix unique.

Figure7.3 Liste choix multiples.

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 lutilisateur de

Chapitre 7

Widgets de slection

71

faire son choix. On peut ainsi faire son choix 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 filles via set Adapter() et on accroche un couteur avec setOnItemSelectedListener(). Si lon souhaite personnaliser la vue daffichage de la bote droulante, il faut configurer ladaptateur, pas le widget Spinner. Pour ce faire, on a donc besoin de la mthode setDropDownViewResource() afin de fournir lidentifiant de la vue concerne. Voici par exemple le fichier 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 deListView. La proprit android:drawSelectorOnTop indique que la flche permettant 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",

72

L'art du dveloppement Android 2

"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.setOnItemSelec tedListener(this)), ce qui est possible car elle implmente linterface OnItemSelected Listener. On configure ladaptateur non seulement avec une liste de mots quelconques mais galement avec une ressource spcifique qui servira la vue droulante (via aa.setDropDownViewResource()). Vous remarquerez galement que lon utilise la vue prdfinie android.R.layout.simple_spinner_item pour afficher les lments du Spinner. Enfin, 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 Figures7.4 et 7.5.

Chapitre 7

Widgets de slection

73

Figure7.4 Lapplication SpinnerDemo lors de son lancement.

Figure7.5 La mme application avec affichage de la liste droulante du Spinner.

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. 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.

74

L'art du dveloppement Android 2

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:numColumns 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 disponible sera pris par les colonnes; si elle vaut spacingWidth, il sera absorb par lespacement entre

les colonnes. Supposons, par exemple, que lcran fasse 320 pixels de large, que la valeur dandroid:columnWidth soit de 100px et celle dandroid:horizontalSpacing, de 5px : trois colonnes occuperaient donc 310pixels (trois colonnes de 100pixels et deux sparations de 5pixels). Si android:stretchMode vaut columnWidth, 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 10pixels. Pour le reste, GridView fonctionne exactement comme nimporte quel autre widget de slection on utilise setAdapter() pour fournir les donnes et les vues filles, on appelle setOnItemSelectedListener() pour enregistrer un couteur de choix, etc. Voici, par exemple, le fichier 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"

Chapitre 7


android:numColumns="auto_fit" android:columnWidth="100px" android:stretchMode="columnWidth" android:gravity="center" />

Widgets de slection

75

</LinearLayout>

Cette grille occupe tout lcran, sauf la partie rserve au label qui affiche la slection courante. Le nombre de colonnes est calcul par Android (android:numColumns = "auto_ fit") partir dun espacement horizontal de 5pixels (android:horizontalSpacing = "5px") et dune largeur de colonne de 100pixels (android:columnWidth = "100px"). Les colonnes absorberont lespace restant disponible (android:stretchMode = "columnWidth"). Le code Java permettant de configurer 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]); }

76

L'art du dveloppement Android 2

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 redfinissent getView(). Ici, on enveloppe les chanes dans nos propres widgets TextView, juste pour changer un peu. Notre getView() se contente de rutiliser le texte du TextView qui luiest pass en paramtre; si ce dernier vaut null, il en cre un et le remplit. Avec lespacement vertical de 35 pixels indiqu dans le fichier de description (android:verticalSpacing = "35"), la grille ne tiendra pas entirement dans lcran de lmulateur (voir Figures7.6 et 7.7).

Chapitre 7

Widgets de slection

77

Figure7.6 Lapplication GridDemo lors de son dmarrage.

Figure7.7 La mme application, aprs dfilement vers le bas.

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 prfixe de filtrage: il est compar une liste de prfixes candidats et les diffrentes correspondances saffichent dans une liste de choix qui ressemble un Spinner. Lutilisateur peut alors

78

L'art du dveloppement Android 2

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.

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 comme nimporte quel EditText, pour tre prvenu lorsque le texte a t modifi. Ce type dvnement est dclench par une saisie manuelle ou par une slection dans la liste des propositions. Voici, par exemple, le fichier 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",

Chapitre 7

Widgets de slection

79

"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 signifie que nos mthodes de rappel doivent se nommer onTextChanged() et beforeTextChanged(). Ici, seule la premire nous intresse: elle modifie le label de slection pour quil reflte le choix courant du champ AutoCompleteTextView. Les Figures7.8, 7.9 et 7.10 montrent ce quaffiche cette application.

80

L'art du dveloppement Android 2

Figure7.8 Lapplication AutoCompleteDemo aprs son dmarrage.

Figure7.9 La mme application aprs avoir saisi quelques lettres. La liste des propositions apparat dans une liste droulante.

Figure7.10 La mme application, aprs avoir choisi le texte suggr.

Chapitre 7

Widgets de slection

81

Galeries
Le widget Gallery nexiste gnralement pas dans les autres kits de dveloppement graphiques. En ralit, il sagit dune liste horizontale, o chaque choix dfile 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 suffisamment 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 afin 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 Chapitre20) 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 fils slectionn. Si cette proprit vaut true, assurez-vous que le slecteur soit suffisamment transparent pour que lon puisse apercevoir le fils derrire lui; sinon les utilisateurs ne pourront pas voir ce quils ont choisi.

8
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 afficher 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

84

L'art du dveloppement Android 2

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 constitue dune icne suivie dun texte. Vous pourriez utiliser une disposition en lignes, 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 lidentifiant 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

Chapitre 8

Samuser avec les listes

85

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 Chapitre7. 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 prfixer le nom de base du fichier de description par R.layout (R.layout.row).

Info

On obtient ainsi une liste avec des icnes droite. Ici, comme le montre la Figure 8.1, toutes les icnes sont les mmes.
Figure8.1 Lapplication StaticDemo.

86

L'art du dveloppement Android 2

Prsentation dynamique
Cette technique fournir une disposition personnalise pour les lignes permet de traiter trs lgamment les cas simples, mais elle ne suffit 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). Vous devez configurer 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, redfinir 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;

Chapitre 8


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

Samuser avec les listes

87

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 redfinir getView() pour quelle renvoie une ligne dpendant de lobjet afficher, 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. Dans notre cas, "inflation" dsigne le fait de convertir une description XML dans larborescence 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 fils 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. Ceci nous donne un objet View qui, en ralit, nest autre

88

L'art du dveloppement Android 2

que notre LinearLayout contenant un ImageView et un TextView, exactement comme cela est spcifi 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". Nous avons donc utilis LayoutInflater pour obtenir un objet View reprsentant la ligne. Cette ligne est "vide" car le fichier de description statique ne sait pas quelles sont les donnes quelle recevra. Il nous appartient donc de la personnaliser et de la remplir comme nous le souhaitons 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 Figure8.2).
Figure8.2 Lapplication DynamicDemo.

Il sagit bien sr dun exemple assez artificiel, mais cette technique peut servir personnaliser 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 efficace. En effet, chaque fois que lutilisateur fait dfiler lcran, on doit crer tout

Chapitre 8

Samuser avec les listes

89

un lot de nouveaux objets View pour les nouvelles lignes qui saffichent. Le framework dAndroid ne mettant pas automatiquement en cache les objets View existants, il faut en recrer denouveaux, mme pour des lignes que lon avait cres trs peu de temps auparavant. Ce nest donc pas trs efficace, 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 efficace, plus la batterie du tlphone se dcharge vite et moins lutilisateur est content. On doit donc passer par quelques astuces pour viter ces dfauts. tudions quelques astuces permettant de rendre les widgets ListView plus efficaces.

Utilisation de convertView
La mthode getView() reoit en paramtre un objet View nomm, par convention, convert View. Parfois, cet objet est null, auquel cas vous devez crer une nouvelle View pour la ligne (par inflation), 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 dfiler 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 findViewById() pour accder aux diffrents widgets qui composent la ligne, modifier leur contenu, puis renvoyer convertView partir de getView() au lieu de crer une ligne totalement 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) {

90

L'art du dveloppement Android 2

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); } } }

Chapitre 8

Samuser avec les listes

91

Si convertView est null, nous crons une ligne par inflation; 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 dinflation potentiel lement coteuse lorsque convertView nest pas null.

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 identifiants, afin que lon puisse en personnaliser le contenu (pour modifier le texte dun TextView, changer licne dun ImageView, par exemple).
findViewById() pouvant trouver nimporte quel widget dans larbre des fils 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 fichier de description XML tout en mettant en cache les widgets fils essentiels de notre ligne, afin de ne devoir les rechercher quune seule fois. Cest l quentre en jeu le patron de conception "support". 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 fils intressant. En attachant ce support lobjet View de la ligne, on a accs immdiatement aux widgets fils qui nous intressent chaque fois que lon utilise cette ligne, sans devoir appeler nouveau findViewById(). 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() {

92

L'art du dveloppement Android 2

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 fils: elle les recherche uniquement si

elle ne les dtient pas dj. Si vous crez un wrapper et que vous nayez jamais besoin dun fils 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 inflation 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);

Chapitre 8


setListAdapter(new IconicAdapter(this)); selection=(TextView)findViewById(R.id.selection);

Samuser avec les listes

93

} 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); }

94

L'art du dveloppement Android 2

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 fils consiste simplement appeler les mthodes appropries du wrapper.

Cration dune liste...


Les listes avec de belles icnes sont jolies, mais ne pourrions-nous pas crer des widgets ListView dont les lignes contiendraient des widgets fils interactifs et non plus passifs comme TextView et ImageView? Pourrions-nous, par exemple, combiner une RatingBar avec du texte afin de permettre lutilisateur de faire dfiler 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 dfi lement de la ListView. On doit pouvoir configurer ltat de la RatingBar en fonction du mot qui est visible lorsque la RatingBar est recycle et sauvegarder cet tat, afin 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 affiches. Aprs tout, une RatingBar nest quun widget utilis dans une ligne dune ListView. Nous devons donc apprendre aux lignes quels sont les modles quelles affichent, afin quelles sachent quel tat de modle modifier 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 affiche 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",

Chapitre 8

Samuser avec les listes

95

"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;

96

L'art du dveloppement Android 2

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 {

Chapitre 8


String label; float rating=2.0f; RowModel(String label) { this.label=label; } public String toString() { if (rating>=3.0) { return(label.toUpperCase()); } return(label); }

Samuser avec les listes

97

} }

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 significations mtier plus importantes. Les mthodes utilitaires comme onListItemClick() doivent tre modifies pour reflter les diffrences entre un modle String pur et lutilisation dun RowModel. Dans getView(), la sous-classe dArrayAdapter (CheckAdapter) teste si convertView est null, auquel cas elle cre une nouvelle ligne par inflation 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 quaffiche 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 modifie 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"

98

L'art du dveloppement Android 2

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() {

Chapitre 8


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

Samuser avec les listes

99

} }

Les Figures8.3 et 8.4 montrent ce quaffiche cette application.


Figure8.3 Lapplication RateListDemo lors de son dmarrage.

Figure8.4 La mme application, montrant un mot avec la note maximale.

100

L'art du dveloppement Android 2

Et la vrifier deux fois


La liste dvaluation de la section prcdente fonctionne, mais son implmentation est trslourde. 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"?> <com.commonsware.android.fancylists.seven.RateListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:drawSelectorOnTop="false" />

o toute la logique du code qui utilisait une ListView auparavant "fonctionnerait" avec la RateListView du layout:
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]); }

Chapitre 8

Samuser avec les listes

101

Les choses se compliquent un tout petit peu lorsquon ralise que, jusqu maintenant, les codes de ce chapitre nont jamais rellement modifi la ListView elle-mme. Nous navons fait que travailler sur les adaptateurs, en redfinissant getView(), en crant nos propres lignes par inflation, 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 mmoriser 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); }

102

L'art du dveloppement Android 2

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 redfinit 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()];

Chapitre 8


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

Samuser avec les listes

103

} 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);

104

L'art du dveloppement Android 2

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 modifies 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 RateListDemo, 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). Layout Inflater 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) {

Chapitre 8


rate=(RatingBar)base.getChildAt(0); } return(rate);

Samuser avec les listes

105

} 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)); } }

106

L'art du dveloppement Android 2

On hrite simplement de ListView et on redfinit setAdapter() pour pouvoir envelopper dans notre propre RateableWrapper le ListAdapter fourni en paramtre. Comme le montre la Figure8.5, les rsultats sont identiques ceux de RateListDemo, sauf que les mots ayant la note maximale napparaissent plus en majuscules.
Figure8.5 Lapplication
RateListViewDemo.

La diffrence est la rutilisabilit. Nous pourrions assembler RateListView dans son propre fichier JAR 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 supplmentaires, comme la possibilit de modifier par programme 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 modifi (via une fonction de rappel), etc. Nous les laissons en exercice au lecteur.

Info

Adapter dautres adaptateurs


Toutes les classes adaptateurs peuvent suivre le modle de conception consistant redfinir 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.

Chapitre 8

Samuser avec les listes

107

Si vous tendez CursorAdapter, qui sert afficher le rsultat dune requte adresse une base de donnes ou un fournisseur de contenu, il est donc prfrable de redfinir newView() et bindView() plutt que getView(). Pour cela, il suffit 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 Chapitre22.

9
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).

110

L'art du dveloppement Android 2

Comme on la mentionn au Chapitre 5, EditText possde des variantes permettant de saisir des nombres ou du texte. Android dispose galement de widgets (DatePicker, TimePicker) et de dialogues (DatePickerDialog, TimePickerDialog) facilitant la saisie des dates et des heures.
DatePicker et DatePickerDialog permettent de fixer une date de dpart, sous la forme

dune anne, dun mois et dun jour. Les mois vont de0 (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 de fixer lheure initiale, que lutilisateur peut ensuite ajuster sous la forme dune heure (de0 23) et de minutes (de0 59). Vous pouvez 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). Vous pouvez galement 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, par exemple, utilise une disposition trs simple, forme dun label et de deux boutons qui feront 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"

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

111

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),

112

L'art du dveloppement Android 2

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 dafficher une bote de dialogue DatePickerDialog ou TimePickerDialog selon le bouton sur lequel on clique. Dans le cas de DatePickerDialog, 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. TimePickerDialog, 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 24heures. Le rsultat de cette activit est prsent aux Figures9.1, 9.2 et 9.3.

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

113

Figure9.1 Lapplication ChronoDemo lors de son lancement.

Figure9.2 La mme application, montrant le dialogue de choix de la date.

Figure9.3 La mme application, montrant le dialogue de choix de lheure.

114

L'art du dveloppement Android 2

Le temps scoule comme un fleuve


Pour afficher lheure sans autoriser les utilisateurs la modifier, utilisez les widgets Digital Clock ou AnalogClock. Il suffit simplement de les placer dans votre layout et de les laisser travailler. Le fichier 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" 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 modifier quoi que ce soit au squelette de code Java produit par android create project, on obtient lapplication prsente la Figure9.4.
Figure9.4 Lapplication ClocksDemo.

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

115

Si vous recherchez plutt un chronomtre, le widget Chronometer est fait pour vous car il permet de mmoriser le temps coul partir dun point de dpart: il suffit de lui dire quand dmarrer (start()), sarrter (stop()) et, ventuellement, de redfinir le format de la chane de texte affiche. La Figure9.5 montre le rsultat obtenu.
Figure9.5 La dmo de lAPI
Views/Chronometer

du SDK Android 2.0.

Mesurer la progression
Si une opration doit durer un certain temps, vos utilisateurs doivent pouvoir:

utiliser un thread en arrire-plan; tre tenus au courant de la progression de lopration, sous peine de penser que lactivit a un problme.

Lapproche classique pour tenir les utilisateurs informs dune progression consiste utiliser 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, dfinie par un entier allant de 0 (aucune progression) une valeur maximale dfinie 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().

116

L'art du dveloppement Android 2

Si vous prfrez que la barre soit indtermine, passez la valeur true sa mthode setIndeterminate(). Dans le code Java, vous pouvez fixer le montant de progression effectue (via setProgress()) ou incrmenter la progression courante dune valeur dtermine (via incrementProgressBy()). 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 linterface , nous prfrons reporter la dmonstration de ce widget jusquau Chapitre15.

Prendre la bonne rsolution


La classe SeekBar hrite de ProgressBar mais, alors que ce dernier est un widget de sortie dont le but consiste indiquer ltat dune progression lutilisateur, SeekBar est un widget dentre qui permet de saisir une valeur prise parmi un intervalle donn. La Figure 9.6 montre ce quaffiche lexemple fourni avec le SDK dAndroid2.0.
Figure9.6 La dmo de lAPI Views/SeekBar du SDK Android2.0.

Lutilisateur peut faire glisser le curseur ou cliquer sur lun des deux cts pour le repositionner. Ce curseur dsigne en ralit une valeur dans un intervalle dtermin 0 100 par dfaut, mais vous pouvez modifier la borne suprieure par un appel setMax(). La mthode getProgress() renvoie la position courante et lenregistrement dun couteur

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

117

laide de setOnSeekBarChangeListener() permet dtre prvenu de toute modification du curseur. Lexemple RatingBar que nous avons tudi au Chapitre 8 tait une variante de ce widget.

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 dfilement), il peut tre prfrable de produire ces informations supplmentaires par une autre activit, lance via une Intent, comme on lexplique au Chapitre18. Cependant, ceci peut tre assez compliqu mettre en place. En outre, il arrive parfois que lon doive recueillir et traiter beaucoup dinformations en une seule opration. Dans une interface graphique classique, nous pourrions utiliser des onglets cette fin, comme un JTabbedPane en Java/Swing. Avec Android, on dispose dsormais dun conteneur TabHost qui fonctionne exactement de la mme faon une portion de ce quaffiche lactivit est lie des onglets qui, lorsque lon 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 afficher 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 lorsque lon 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.

Les pices du puzzle


Pour mettre en place des 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, ven-

tuellement, des icnes.


FrameLayout est le conteneur des contenus des onglets: chaque contenu donglet est un fils 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.

118

L'art du dveloppement Android 2

Idiosyncrasies
La version actuelle dAndroid exige de respecter les rgles suivantes pour que ces trois composants puissent fonctionner de concert:

Lidentifiant android:id du TabWidget doit tre @android:id/tabs. Si vous souhaitez utiliser TabActivity, lidentifiant android:id du TabHost doit tre
@android:id/tabhost. TabActivity, comme ListActivity, enveloppe un motif dinterface graphique clas-

sique (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.
<?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>

Voici le fichier de description dune activit utilisant des onglets, tir du projet Fancy/Tab:

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

119

Vous remarquerez que les lments TabWidget et FrameLayout sont des fils directs de TabHost et que llment FrameLayout a lui-mme un fils 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 lidentifiant 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 configurer 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);

120

L'art du dveloppement Android 2

tabs.setup(); TabHost.TabSpec spec=tabs.newTabSpec("tag1"); spec.setContent(R.id.tab1); spec.setIndicator("Heure"); 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 set Indicator() de cette instance, puis la mthode addTab() de lobjet TabHost pour lui ajouter le TabSpec. Enfin, on choisit longlet qui saffichera initialement, laide de la mthode setCurrentTab() (la valeur 0 dsigne le premier onglet). Les Figures9.7 et 9.8 montrent ce quaffiche cette application.
Figure9.7 Lapplication TabDemo affichant son premier onglet.

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

121

Figure9.8 La mme application affichant son second onglet.

Ajouts dynamiques
TabWidget est configur pour simplifier la dfinition 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, afin 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 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 suffit 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 dfinit 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"

122

L'art du dveloppement Android 2

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);

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

123

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("Heure"); 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 un AnalogClock. Le code de construction de cette vue pourrait tre bien plus labor et uti liser, par exemple, un LayoutInflater pour crer une vue partir dun fichier de description XML. La Figure9.9 montre que lactivit naffiche quun seul onglet lorsquelle est lance. La Figure9.10 montre plusieurs onglets, crs en cours dexcution.
Figure9.9 Lapplication DynamicTabDemo avec son unique onglet initial.

124

L'art du dveloppement Android 2

Figure9.10 La mme application, aprs la cration de trois onglets en cours dexcution.

Intent et View
Dans les exemples prcdents, le contenu de chaque onglet tait une View: un Button, par exemple. Ce type de configuration 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 multiplier. 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 Chapitre18.

Tout faire basculer


Parfois, on souhaite bnficier 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.

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

125

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 fille 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 fichier 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"

126

L'art du dveloppement Android 2

android:textStyle="bold" android:textColor="#FFFFFF00" android:text="Troisieme panneau" /> </ViewFlipper> </LinearLayout>

Ce layout dfinit trois vues filles de ViewFlipper, chacune tant un TextView contenant un simple message. Vous pourriez videmment choisir des vues plus complexes.

Basculement manuel
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 affichs (voir Figures9.11 et 9.12).

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

127

Figure9.11 Lapplication Flipper1 montant le premier panneau.

Figure9.12 La mme application, aprs basculement vers le second panneau.

Nous pourrions bien sr grer tout cela plus simplement en utilisant un seul TextView et en modifiant 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.

Ajout de contenu la vole


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.

128

L'art du dveloppement Android 2

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 compilation. Notez galement quil ny a pas de bouton pour basculer entre les contenus nous reviendrons sur ce point dans un instant. Pour le contenu du ViewFlipper, nous crerons de gros boutons contenant, chacun, un ensemble de mots quelconques. Nous configurerons 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));

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

129

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 fichier de disposition, nous commenons par configurer 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 des 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. Les animations sont un sujet complexe que je traite dans le livre The Busy Coders Guide to Advanced Android Development (CommonsWare LLC, 2009).

Info

Basculement automatique
Aprs avoir parcouru tous les mots en les transformant en autant de boutons fils de lobjet ViewFlipper, nous configurons ce dernier pour quil bascule automatiquement entre ses fils (flipper.setFlipInterval(2000);) et nous lanons le basculement (flipper.startFlipping();). Le rsultat est une suite sans fin de boutons apparaissant puis disparaissant vers la gauche au bout de 2secondes, en tant remplacs chaque fois par le bouton suivant de la squence. Lensemble revient au premier bouton aprs la disparition du dernier (voir Figure9.13).

130

L'art du dveloppement Android 2

Figure9.13 Lapplication Flipper2, avec une transition anime.

Ce basculement automatique est utile pour les panneaux dinformation ou les autres situations dans lesquelles vous voulez afficher 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, fonctionnant comme celui de lcran daccueil, qui contient les icnes pour lancer les applications. Limplmentation officielle existait dans le code open-source mais ntait pas intgre dans le SDK... jusqu Android1.5, qui a fourni le widget SlidingDrawer. la diffrence de la plupart des autres conteneurs, SlidingDrawer change daspect puisquil passe dune position ferme une position ouverte. Cette caractristique implique 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 conteneur 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 fils direct dun lment LinearLayout.

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

131

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="Je suis dedans !" /> </SlidingDrawer> </FrameLayout>

Le SlidingDrawer doit contenir:

une poigne le plus souvent un ImageView, comme ici; le contenu du tiroir lui-mme gnralement un conteneur, bien quici nous utilisions un bouton.

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. Ceci permet au tiroir de savoir comment sanimer lorsquil souvre ou se ferme. La Figure 9.14 montre laspect du tiroir ferm, avec la poigne quon lui a fournie. La Figure9.15 montre le tiroir ouvert, avec son contenu.

132

L'art du dveloppement Android 2

Figure9.14 Lapplication DrawerDemo avec son tiroir ferm.

Figure9.15 La mme application avec le tiroir ouvert.

Comme on pourrait sy attendre, on peut ouvrir et refermer le tiroir partir du code Java ou par des vnements de "touchs" utilisateurs (ceux-ci sont grs par le widget et vous navez donc pas vous en occuper). 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;

Chapitre 9

Utiliser de jolis widgets et de beaux conteneurs 

133

un couteur qui sera appel lors de la fermeture du tiroir; un couteur qui sera appel lorsque le tiroir "dfile" (cest--dire lorsque lutilisateur tire ou repousse la poigne).

Le SlidingDrawer du lanceur, par exemple, change licne de sa poigne pour quelle signifie "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 identique 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 Chapitre20.

Autres conteneurs intressants


Android fournit galement le conteneur AbsoluteLayout, dont le contenu est dispos en fonction de coordonnes spcifiques on lui indique o placer un fils en prcisant ses coordonnesX,Y, et Android le positionne cet endroit sans poser de question. Ceci a lavantage de fournir un positionnement prcis; en revanche, cela signifie 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 continuellement, lutilisation dAbsoluteLayout risque de devenir assez problmatique. Officiellement, AbsoluteLayout est considr comme obsolte: bien quil reste disponible, il est dsormais dconseill de lutiliser.

Info

Android dispose galement dune nouvelle variante de liste, ExpandableListView, qui fournit une reprsentation arborescente simplifie autorisant deux niveaux de profondeur: les groupes et les fils. Les groupes contiennent les fils, 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.

10
Le framework des mthodes de saisie
Android1.5 a introduit le framework des mthodes de saisie IMF (Input Method Framework), que lon dsigne sous le terme de claviers logiciels bien quil ne soit pas ncessairement tout fait exact car IMF pourrait tre utilis pour la reconnaissance dcriture ou tout autre moyen de saisir du texte via lcran. Ce chapitre explique comment utiliser IMF pour adapter les claviers logiciels aux besoins de votre application.

Claviers physiques et logiciels


Certains terminaux Android comme le HTC Magic nont pas de clavier physique ; dautres, comme le G1, ont un clavier physique qui nest pas disponible en permanence (il faut louvrir). Il est probable que, dans le futur, apparatront des terminaux Android avec un clavier

136

L'art du dveloppement Android 2

physique permanent (comme les netbooks et certains tlphones mobiles actuels, qui ont toujours un clavier sous leur cran). IMF sait grer tous ces scnarios: sil ny a pas de clavier physique, un IME (Input Method Editor) apparatra lorsque lutilisateur tape sur un EditText. Vos applications nont pas besoin dtre modifies du moment que la fonctionnalit par dfaut de lIME vous convient. Heureusement, Android est suffisamment malin pour deviner ce que vous voulez: vous pouvez donc faire des tests avec lIME, mais sinon il nest pas ncessaire deffectuer des modifications spcifiques dans votre code. Le clavier peut aussi ne pas se comporter tout fait comme vous le souhaitez. Dans le projet Basic/Field, par exemple, lIME recouvre la zone de saisie de lactivit FieldDemo, comme on le voit la Figure10.1: il serait prfrable de pouvoir mieux contrler la faon dont lIME apparat et ragit. Heureusement, nous allons voir que le framework vous laisse une grande latitude.
Figure10.1 LIME de lapplication FieldDemo.

Adaptation vos besoins


Android1.1 et les versions prcdentes offraient de nombreux attributs pour les widgets EditText afin de contrler leur style de saisie android:password, par exemple, pour indiquer quun champ sert saisir un mot de passe (et que le texte entr doit donc tre cach des yeux indiscrets). Avec Android1.5 et IMF, la plupart ont t regroups dans un seul attribut android:inputType dont la valeur est compose dune classe et de modificateurs, spars par le caractre "pipe" (|). La classe dcrit gnralement ce que lutilisateur est autoris

Chapitre 10

Le framework des mthodes de saisie 

137

saisir et dtermine lensemble de base des touches disponibles sur le clavier logiciel. Les classes possibles sont les suivantes:

text (par dfaut); number; phone; datetime; date; time.

La plupart dentre elles offrent un ou plusieurs modificateurs pour affiner ce que pourra saisir lutilisateur. Pour mieux comprendre leur signification, tudions le fichier res/ layout/main.xml du projet InputMethod/IMEDemo1:
<?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="Aucune regle speciale : " /> <EditText /> </TableRow> <TableRow> <TextView android:text="Adresse e-mail : " /> <EditText android:inputType="text|textEmailAddress" /> </TableRow> <TableRow> <TextView android:text="Nombre decimal signe : " /> <EditText android:inputType="number|numberSigned|numberDecimal" />

138

L'art du dveloppement Android 2

</TableRow> <TableRow> <TextView android:text="Date :" /> <EditText android:inputType="date" /> </TableRow> <TableRow> <TextView android:text="Texte multilignes : " /> <EditText android:inputType="text|textMultiLine|textAutoCorrect" android:minLines="3" android:gravity="top" /> </TableRow> </TableLayout>

Nous utilisons ici un conteneur TableLayout de cinq lignes utilisant, chacune, une variante lgrement diffrente dEditText:

La premire nutilise aucun attribut. La deuxime utilise android:inputType = "text|textEmailAddress", ce qui signifie que cest une saisie de texte, une adresse de courrier lectronique, plus prcisment. La troisime permet de saisir un nombre dcimal sign grce android:inputType = "number|numberSigned|numberDecimal". La quatrime est configure pour la saisie dune date (android:inputType = "date"). La dernire autorise une saisie sur plusieurs lignes, avec autocorrection des erreurs dorthographe (android:inputType = "text|textMultiLine|textAutoCorrect").

La classe et les modificateurs permettent dadapter le clavier vos besoins : un champ de saisie de texte brut produira donc un clavier logiciel de base, comme le montre la Figure10.2.

Chapitre 10

Le framework des mthodes de saisie 

139

Figure10.2 Clavier logiciel standard.

Un champ de saisie dadresse e-mail ajoute les touches @ et .com au clavier, au prix dune barre despace plus petite, comme le montre la Figure10.3.
Figure10.3 Clavier logiciel pour la saisie des adresses e-mail.

La Figure10.4 montre que les champs numriques et date limitent les touches au clavier numrique et un ensemble de symboles autoriss pour un champ donn.

140

L'art du dveloppement Android 2

Figure10.4 Clavier logiciel pour les nombres dcimaux signs.

Ce ne sont que quelques exemples qui vous montrent quen choisissant la valeur android:inputType approprie vous pouvez offrir aux utilisateurs le clavier qui convient le mieux ce quils doivent saisir.

Dire Android o aller


Vous aurez peut-tre not une subtile diffrence entre les claviers de la Figure10.2 et celui de la Figure 10.4 hormis lajout des touches @ et .com : la touche Suivant est venue remplacer la Figure10.4 la touche Entre de la Figure10.2. Ceci signifie deux choses:

Les widgets EditText sont multilignes par dfaut si vous nutilisez pas android:inputType.

Vous pouvez contrler ce qui saffiche sur cette touche infrieure droite, appele bouton accessoire.

Par dfaut, le bouton accessoire dun EditText avec un attribut android:inputType sera Suivant pour vous permettre de passer au champ EditText suivant ou OK si vous vous trouvez sur le dernier EditText de lcran. Vous pouvez toutefois indiquer manuellement ce qui saffichera sur ce bouton laide de lattribut android:imeOptions. Dans le fichier res/ layout/main.xml du projet InputMethod/IMEDemo2, par exemple, deux champs de saisie prcisent lapparence du bouton accessoire:
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"

Chapitre 10

Le framework des mthodes de saisie 

141

android:layout_height="fill_parent"> <TableLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:stretchColumns="1" > <TableRow> <TextView android:text="Aucune regle speciale : " /> <EditText /> </TableRow> <TableRow> <TextView android:text="Adresse e-mail : " /> <EditText android:inputType="text|textEmailAddress" android:imeOptions="actionSend" /> </TableRow> <TableRow> <TextView android:text="Nombre decimal signe : " /> <EditText android:inputType="number|numberSigned|numberDecimal" android:imeOptions="actionDone" /> </TableRow> <TableRow> <TextView android:text="Date:" /> <EditText android:inputType="date" /> </TableRow> <TableRow>

142

L'art du dveloppement Android 2

<TextView android:text="Texte multilignes :" /> <EditText android:inputType="text|textMultiLine|textAutoCorrect" android:minLines="3" android:gravity="top" /> </TableRow> </TableLayout> </ScrollView>

Ici, nous attachons une action Envoyer au bouton accessoire de la saisie dune adresse e-mail (android:imeOptions = "actionSend") et laction OK celui de la saisie dun nombre (android:imeOptions = "actionDone"). Par dfaut, Suivant dplace le focus sur le widget EditText suivant et OK ferme lIME. Cependant, pour ces actions ou pour toutes celles comme Envoyer, vous pouvez utiliser setOnEditorActionListener() sur lEditText afin de savoir quand le bouton accessoire ou la touche Entre sont cliqus. Vous recevrez alors un indicateur signalant laction voulue (IME_ACTION_SEND, par exemple) et vous pourrez traiter cette requte (envoyer un courrier ladresse e-mail qui a t saisie, par exemple).

Mise en place
Vous noterez les diffrences entre le layout de IMEDemo1 et celui de IMEDemo2 : dans ce dernier, nous utilisons un conteneur ScrollView pour envelopper le TableLayout. Ceci nous amne un autre niveau de contrle sur les IME : que devient le layout de notre activit lorsque le clavier apparat ? En fonction des circonstances, il y a trois possibilits :

Android peut faire "glisser" vers le haut toute lactivit afin de laisser la place au clavier; il peut galement recouvrir celle-ci selon que lEditText en cours de saisie se trouve en haut ou en bas. Ceci a donc pour effet de masquer certaines parties de votre interface. Il peut modifier la taille de lactivit afin quelle tienne dans une zone dcran plus rduite pour permettre lIME de safficher en dessous. Cette technique est parfaite lorsque le layout peut tre rduit (lorsquil est domin par une liste ou un champ de saisie multilignes qui na pas besoin de tout lcran pour tre fonctionnel, par exemple).

Chapitre 10

Le framework des mthodes de saisie 

143

En mode paysage, Android peut afficher lIME sur tout lcran et masquer ainsi lintgralit de lactivit. Ceci permet de disposer dun clavier plus grand pour faciliter la saisie.

Android contrle tout seul loption plein cran et, par dfaut, choisira le mode dfilement ou rduction en fonction de votre layout. Lattribut android:windowSoftInputMode de llment <activity> du fichier AndroidManifest.xml vous permet toutefois de fixer vousmme ce mode. Voici, par exemple, le manifeste de IMEDemo2:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.imf.two" android:versionCode="1" android:versionName="1.0"> <application android:label="@string/app_name" android:icon="@drawable/cw"> <activity android:name=".IMEDemo2" android:label="@string/app_name" android:windowSoftInputMode="adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

Figure10.5 Layout rduit, avec une barre de dfilement.

144

L'art du dveloppement Android 2

Librez le Dvorak qui est en vous


Vous pouvez crer et distribuer vos propres IME: un clavier Dvorak, un clavier pour une autre langue ou un clavier qui rpte oralement les touches presses, par exemple. Un IME est assembl sous la forme dun service, cest--dire un composant Android que nous dcrirons aux Chapitres29 et30. Si vous voulez crer un clavier, tudiez lapplication SoftKeyboard distribue avec le SDK Android1.5 et, videmment, le code source dAndroid lui-mme (recherchez la classe LatinIME).

11
Utilisation des menus
Les activits Android peuvent comprendre des menus, telles les applications de bureau et de certains systmes mobiles comme Windows Mobile. Android les appelle menus doptions. Certains terminaux Android disposent mme dune touche ddie leur ouverture; dautres offrent des moyens diffrents pour les faire apparatre: la tablette Android Archos5, par exemple, se sert dun bouton sur lcran. 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 apparaisgnralement lorsque lutilisateur touche un widget pendant un certain temps. Si un sent TextView dispose dun menu contextuel et que le terminal a un cran tactile, par exemple, vous pouvez toucher le label pendant une seconde ou deux: un menu surgira alors pour vous permettre de choisir lune des entres du menu.

Menus doptions
Un menu doption se dclenche en appuyant sur le bouton Menu du terminal et a deux modes de fonctionnement : icne et tendu. Lorsque lutilisateur appuie sur le bouton

146

L'art du dveloppement Android 2

Menu, le menu est en mode icne et naffiche 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 affiche "Plus" cliquer sur cette option fait passer le menu en mode tendu, qui affiche tous les choix restants. Lutilisateur peut bien sr faire dfiler le menu afin deffectuer nimporte quel choix.

Cration dun menu 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 de rappel onCreate OptionsMenu() en lui passant une instance de la classe Menu. La premire opration raliser consiste tablir un chanage vers la mthode de la superclasse (via super.onCreateOptionsMenu(menu)), afin quAndroid puisse lui ajouter les choix ncessaires. Puis vous pouvez ajouter les vtres, comme on lexpliquera bientt. Si vous devez modifier le menu au cours de lactivit (pour, par exemple, dsactiver un choix devenu inadquat), il suffit de manipuler linstance de Menu que vous avez transmise onCreateOptionsMenu() ou dimplmenter la mthode onPrepareOptionsMenu(), qui est appele avant chaque affichage du menu.

Ajout de choix et de sous-menus


Pour ajouter des choix au menu, utilisez la mthode add(). Celle-ci est surcharge pour pouvoir recevoir des combinaisons des paramtres suivants:

Un identifiant de groupe (un entier) qui doit valoir NONE, sauf si vous crez un ensemble doptions de menu utilisable avec setGroupCheckable() (voir plus loin). Un identifiant de choix (galement un entier) servant identifier la slection dans la mthode de rappel onOptionsItemSelected() lorsquune option du menu a t choisie. Un identifiant 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 identifiant de ressource.

Toutes les mthodes add() renvoient une instance de MenuItem qui vous permet ensuite de modifier 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 caractre, permettant de slectionner ce choix lorsque le menu est visible. Android permet dutiliser des raccourcis alphabtiques et numriques, mis en place respectivement

Chapitre 11

Utilisation des menus

147

par setAlphabeticShortcut() et setNumericShortcut(). Le menu est plac en mode raccourci alphabtique en passant le paramtre true sa mthode setQwertyMode(). Les identifiants de choix et de groupe sont des cls servant dverrouiller certaines fonction nalits supplmentaires des menus:

Un appel MenuItem#setCheckable() avec un identifiant 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 identifiant de groupe permet de rendre un ensemble de choix mutuellement exclusifs en leur associant des boutons radio, afin de ne pouvoir cocher quune seule option du groupe un instant donn.

Enfin, il est possible de crer des sous-menus la vole, en appelant addSubMenu() avec les mmes paramtres que ceux daddMenu(). Android appellera alors onCreatePanelMenu() en lui passant lidentifiant du choix du sous-menu, ainsi quune autre instance deMenu 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 fin les sousmenus: 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 onOptions ItemSelected(). Vous recevrez alors lobjet MenuItem correspondant ce choix. Un motif de conception classique consiste utiliser une instruction switch() avec lidentifiant du menu (item.getItemId()), afin 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
Laffichage dun menu contextuel est dclench par un touch prolong du widget qui fournit ce menu. 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 suffit dappeler la mthode registerForContextMenu() partir de lactivit, en lui passant en paramtre un objet View le widget qui a besoin dun menu contextuel.

148

L'art du dveloppement Android 2

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 didentifier 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.ContextMenuInfo, 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:vous ne pouvez donc 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 nobtiendrez que linstance du MenuItem qui a t choisi: si lactivit a plusieurs menus contextuels,

il faut donc que les identifiants des lments de menus soient uniques afin de pouvoir les traiter de faon approprie. Vous pouvez galement appeler la mthode getMenuInfo() du MenuItem afin dobtenir lobjet ContextMenu.ContextMenuInfo 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 modifie de Selection/List (voir Chapitre7) avec un menu associ. Les menus tant dfinis dans le code Java, le fichier de description XML nest pas modifi 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;

Chapitre 11

Utilisation des menus

149

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) {

150

L'art du dveloppement Android 2

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);

Chapitre 11


case FORTY_ID: getListView().setDividerHeight(40); return(true); } return(false);

Utilisation des menus

151

} }

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 onCreateOptionsMenu() pour signaler que lactivit 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 superclasse respective si lutilisateur na slectionn aucun choix du menu.
populateMenu() ajoute sept choix de menus, chacun ayant un identifiant 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 fixons lpaisseur du sparateur de la liste la valeur correspondante. Comme le montre la Figure11.1, lactivit initiale a le mme aspect que ListDemo.
Figure11.1 Lapplication MenuDemo lors de son lancement.

152

L'art du dveloppement Android 2

Si lon appuie sur le bouton Menu du terminal, on obtient le menu doptions prsent la Figure11.2.
Figure11.2 La mme application, avec son menu doptions.

La Figure11.3 montre que les deux choix restants du menu apparaissent lorsque lon clique sur le bouton "Plus".
Figure11.3 La mme application, montrant les derniers choix du menu.

Si lon choisit une valeur (16pixels, par exemple), lpaisseur des traits de sparation de la liste est modifie, comme le montre la Figure11.4.

Chapitre 11

Utilisation des menus

153

Figure11.4 La mme application dans une version peu esthtique.

La Figure11.5 montre que lon peut faire apparatre le menu contextuel en "touchant et maintenant" un lment de la liste. L encore, le choix dune option modifiera lpaisseur du trait de sparation.
Figure11.5 La mme application, avec son menu contextuel.

Encore de linflation
Nous avons vu au Chapitre8 que lon pouvait dcrire les vues via des fichiersXML et les transformer par inflation en objets View au moment de lexcution. Android permet de faire de mme avec les menus, qui eux aussi peuvent tre dcrits dans des fichiersXML et

154

L'art du dveloppement Android 2

transforms 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 fichiers 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 fichiers de menusXML, chacun ayant son propre nom et lextension .xml. Voici, par exemple, le contenu du fichier 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" 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"

Chapitre 11

Utilisation des menus

155

android:title="Fantme" android:visible="false" android:alphabeticShortcut="f" /> </menu> </item> </menu>

Voici quelques remarques propos des dfinitions des menus en XML:

Llment racine doit sappeler menu. Les lments fils 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 fils 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 de Java, noubliez pas dutiliser lattribut android:id, exactement comme pour les fichiersXML 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 fichier XML. Vous pouvez modifier cet ordre laide de lattribut android:orderInCategory 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 fichier XML.

Activation. Les item et les group peuvent tre rendus actifs ou inactifs dans le fichier 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

156

L'art du dveloppement Android 2

tre slectionns. Vous pouvez modifier ltat dun item en cours dexcution grce la mthode setEnabled() de MenuItem et celui dun group via la mthode setGroup Enabled() de Menu.

Visibilit. 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 napparaissent pas dans le menu. Vous pouvez modifier cet tat en cours dexcution en appelant la mthode setVisible() dun MenuItem ou la mthode setGroupVisible() dun Menu.

Dans le fichier XML prsent plus haut, le groupe other_stuff est invisible au dpart. 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.

Cration dun menu par inflation


Lutilisation dun menu dfini dans un fichierXML est relativement simple puisquil suffit 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 plateforme, 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 environnement, il faut savoir grer ses spcificits.

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 Ascender1 pour lOpen Handset Alliance. Pour les utiliser, il suffit de les dsigner dans le fichier de description XML. Le layout suivant, par exemple, est extrait du projet Fonts/FontSampler:
<?xml version="1.0" encoding="utf-8"?> <TableLayout

1.

http://www.ascendercorp.com/oha.html.

158

L'art du dveloppement Android 2

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="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"

Chapitre 12

Polices de caractres

159

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 prdfinies ("sans", par exemple).

Polices supplmentaires
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 suffit de crer un rpertoire assets/ dans larborescence du projet et dy placer les polices TrueType (TTF). Vous pourriez, par exemple, crer un rpertoire assets/polices/ et y placer les fichiersTTF. Il faut ensuite demander aux widgets dutiliser cette police mais, malheureusement, vous ne pouvez pas le faire dans le fichier de descriptionXML car il ne connat pas celles que vous avez pu placer dans les assets de lapplication. Cette modification 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); } }

160

L'art du dveloppement Android 2

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 lAssetManager de lapplication (obtenu via getAssets()) et un chemin relatif de la police prsent dans le rpertoire assets/. Ensuite, il suffit dindiquer au TextView dutiliser cette police en appelant sa mthode setTypeface() avec le Typeface que lon vient de crer. Ici, on utilise la police Handmade Typewriter1 (voir Figure12.1).
Figure12.1 Lapplication FontSampler.

Info

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") au lieu de lever une exception. 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 fichiers de polices, afin de respecter les conventions utilises par vos autres ressources. Android1.6 a ajout la possibilit de crer avec la mthode createFromFile() des objets Typeface partir de fichiers TrueType situs sur le systme de fichiers sur la carte SD de lutilisateur, par exemple.

1.

http://moorstation.org/typoasis/designers/klein07/text01/handmade.htm.

Chapitre 12

Polices de caractres

161

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 70Ko et les polices libres DejaVu peuvent atteindre 500Ko chacune. Mme compresses, elles augmentent donc significativement la taille de votre application: ne vous laissez pas envahir par les polices supplmentaires 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 android:ellipsize, par exemple. Ceci fonctionne trs bien, au moins pour les textes dune seule ligne. Lellipse utilise par Android nest pas une simple suite de trois points, mais un vritable caractre "ellipse", o les trois points sont contenus dans un unique glyphe. Par consquent, les polices que vous utilisez auront besoin de ce glyphe pour utiliser la fonctionnalit f ournie par android:ellipsize. En outre, Android complte les chanes qui saffichent 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 signifie 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, laffichage de la chane courte lcran prsentera des caractres curieux (desX la fin de la ligne, par exemple). Bien entendu, le dploiement international dAndroid signifie galement quune police doit pouvoir grer nimporte quelle langue choisie par lutilisateur, avec ses caractres parti culiers (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 soigneusement peser le pour et le contre.

13
Intgrer le navigateur de WebKit
Les autres kits de dveloppement graphiques permettent dutiliser HTML pour prsenter 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 afin dafficher du HTML ou de naviguer sur le Web. Ce navigateur repose sur WebKit, le moteur de Safari (le navigateur dApple). Ce navigateur est suffisamment complexe pour disposer de son propre paquetage Java (android.webkit). La simplicit dutilisation du widget WebView, quant elle, dpend de vos exigences.

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 fini.

Un navigateur, et en vitesse!

164

L'art du dveloppement Android 2

Le layout du projet WebKit/Browser1, 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.browser1; 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://www.pearson.fr"); } }

La seule partie nouvelle de cette version donCreate() est lappel de la mthode loadUrl() du widget WebView afin de charger une page web. Nous devons galement modifier le fichier AndroidManifest.xml, afin de demander la permission daccder Internet; sinon le navigateur refusera de charger les pages:

Chapitre 13

Intgrer le navigateur de WebKit 

165

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.browser1"> <uses-permission android:name="android.permission.INTERNET" /> <application android:icon="@drawable/cw"> <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 Figure13.1, lactivit obtenue ressemble un navigateur web sans barre de dfilement.
Figure13.1 Lapplication Browser1.

Comme avec le navigateur classique dAndroid, vous pouvez faire dfiler 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);.

166

L'art du dveloppement Android 2

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 afficher cette page via loadUrl(). Le navigateur accdera Internet par les moyens mis disposition du terminal cet instant prcis (Wifi, rseau cellulaire, partage de donnes par Bluetooth, pigeons voyageurs bien entrans, etc.). Lautre solution consiste utiliser loadData() en lui fournissant le codeHTML que lon veut afficher dans le navigateur. Cette dernire mthode est notamment utile pour:

afficher un manuel install sous forme de fichier avec lapplication; afficher des extraits HTML rcuprs par un autre traitement la description dune entre dun flux 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 documentHTML 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 Figure13.2.
browser.loadData("<html><body>Bonjour !</body></html>", "text/html", "UTF-8");

Figure13.2 Lapplication Browser2.

Le code complet de cette application est disponible dans le projet WebKit/Browser2.

Chapitre 13

Intgrer le navigateur de WebKit 

167

Navigation au long cours


Comme on la dj mentionn, le widget WebView ne propose pas de barre de navigation, ce qui permet de lutiliser des endroits o cette barre serait inutile et consommerait inuti lement 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 soumettant un Intent Android pour quil ouvre lURL dans un vrai navigateur), soit par dautres moyens (voir Chapitre18). La mthode setWebViewClient(), qui prend en paramtre une instance dune implmentation 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(), onReceivedHttp AuthRequest(), 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

168

L'art du dveloppement Android 2

vous prfrez utiliser le traitement par dfaut (qui consiste rcuprer la page web dsigne par lURL). Pour une application de lecture de fluxRSS, par exemple, le lecteur ne comprendra srement pas un navigateur web complet, avec toutes les options de navigation possibles; si lutilisateur clique sur une URL, vous utiliserez donc probablement un Intent pour demander Android de charger cette page dans un vrai navigateur. Cependant, si vous avez insr une URL "bidon" dans le codeHTML, reprsentant un lien vers un contenu fourni par une activit, vous pouvez modifier vous-mme le WebView. Modifions notre premier exemple pour quil devienne lquivalent de la premire application de ce livre: un programme qui affiche 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); } } }

Chapitre 13

Intgrer le navigateur de WebKit 

169

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 Figure13.3.
Figure13.3 Lapplication Browser3.

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. De la mme faon, vous pouvez modifier 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 relative) permettent de modifier la taille de la police.

170

L'art du dveloppement Android 2

setJavaScriptEnabled() et setJavaScriptCanOpenWindowsAutomatically() permettent, respectivement, de dsactiver totalement JavaScript et de lempcher douvrir des fentres popup. 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 navigateur

mobile alors que, sil vaut 1, le widget enverra une chane indiquant quil sagit dun navigateur qui sexcute sur une machine de bureau. Les modifications que vous apportez ne sont pas persistantes: vous devez donc les stocker quelque part (via le moteur de prfrences dAndroid que nous prsenterons au Chapitre21, par exemple) si vous autorisez vos utilisateurs modifier ces rglages au lieu de les coder en dur dans votre application.

14
Affichage de messages surgissants
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 contenues dans des activits composes de vues. Les erreurs surgissent; les tches en arrireplan 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" (MacOS 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 notifications, par exemple, sont intimement lies aux

172

L'art du dveloppement Android 2

Intents et aux services et seront donc prsentes au Chapitre31. 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 signifie quil saffiche et disparat de lui-mme, sans intervention de lutilisateur. En outre, il ne modifie pas le focus de lactivit courante: si lutilisateur est en train dcrire le prochain trait fondamental sur lart de la program mation, 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 affich suffisamment 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 identifiant 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 suffit de crer une instance Toast laide du constructeur (qui attend un Context) puis dappeler setView() pour lui indiquer la vue utiliser et setDuration() pour fixer sa dure. Lorsque le toast est configur, il suffit dappeler sa mthode show() pour quil saffiche.

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 affich tant que lutilisateur ne le ferme pas. Ce type daffichage convient donc bien aux erreurs critiques, aux messages de validation qui ne peuvent pas tre affichs 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 configurer un AlertDialog. Chacune de ces mthodes renvoie le Builder afin de faciliter le chanage des appels. la fin, il suffit dappeler la mthode show() de lobjet Builder pour afficher la bote de dialogue.

Chapitre 14

Affichage de messages surgissants

173

Voici les mthodes de configuration de Builder les plus utilises:

setMessage() permet de dfinir le "corps" de la bote de dialogue partir dun simple message de texte. Son paramtre est un objet String ou un identifiant de ressource textuelle. setTitle() et setIcon() permettent de configurer 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 emplacement 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 configurations que celles proposes par Builder, appelez la mthode create() la place de show(): vous obtiendrez ainsi une instance dAlertDialog partiellement construite que vous pourrez configurer avant dappeler lune des mthodes show() de lobjet AlertDialog lui-mme. Aprs lappel de show(), la bote de dialogue saffiche et attend une saisie de lutilisateur.

Mise en uvre
Voici le fichier 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="Dclencher 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>

174

L'art du dveloppement Android 2

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 configurer le titre (setTitle("MessageDemo")), le message (setMessage("Oups !!") et le bouton central (setNeutralButton("Fermeture", new

Chapitre 14

Affichage de messages surgissants

175

OnClickListener() ) avant dafficher 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 en profiter pour 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 Figure14.1. Figure14.1 Lapplication MessageDemo aprs avoir cliqu sur le bouton Dclencher une alerte.

Lorsque lon clique sur le bouton Lever un toast, la classe Toast cre un toast textuel (avec makeText(this, "<Clac, Clac>", LENGTH_SHORT)), puis laffiche avec show(). Le rsultat est un message de courte dure qui ninterrompt pas lactivit (voir Figure14.2).
Figure14.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 200millisecondes) est un bon objectif. Au minimum, il faut fournir une rponse en moins de 5secondes; sinon lActivityManager pourrait dcider 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, ce qui implique invariablement dutiliser un thread. Android dispose dune vritable panoplie de moyens pour mettre en place des threads en arrireplan tout en leur permettant dinteragir proprement avec linterface graphique, qui, elle, sexcute dans un thread qui lui est ddi. Cette "interaction propre" est cruciale car linterface graphique ne peut tre modifie que par son thread et non par un thread en arrire-plan. Ceci signifie gnralement quil devra donc exister une certaine coordination entre les threads en arrire-plan, qui effectuent le traitement, et le thread de linterface, qui affiche le rsultat de ce traitement.

178

L'art du dveloppement Android 2

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 suffit 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 modification de ses widgets, par exemple ne doivent inter venir 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() afin dextraire lobjet Message du pool. Cette mthode est surcharge pour vous permettre de crer un Message vide ou contenant des identifiants et des paramtres de messages. 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 file dattente, laide de lune des mthodes de la famille sendMessage...():

sendMessage() place immdiatement le message dans la file. sendMessageAtFrontOfQueue() place immdiatement le message en tte de file (au lieu

de le mettre la fin, ce qui est le comportement par dfaut). Votre message aura donc priorit par rapport tous les autres.
sendMessageAtTime() place le message dans la file linstant indiqu, exprim en millisecondes par rapport luptime du systme (SystemClock.uptimeMillis()). sendMessageDelayed() place le message dans la file aprs un certain dlai, exprim en

millisecondes.

Pour traiter ces messages, votre Handler doit implmenter la mthode handleMessage() qui sera appele pour chaque message qui apparat dans la file dattente. Cest l que le handler peut modifier 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 modifie via un Handler. Voici son fichier de descriptionXML:

Chapitre 15

Utilisation des threads

179

<?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 pour demander ici que la barre de progression soit trace horizontalement, en montrant la proportion de travail effectu. Voici le code Java:
package com.commonsware.android.threads; import import import import import android.app.Activity; android.os.Bundle; android.os.Handler; android.os.Message; 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);

180

L'art du dveloppement Android 2

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 5points puis nous sortons du gestionnaire de message. Dans la mthode onStart(), nous configurons un thread en arrire-plan. Dans une vraie application, celui-ci effectuerait une tche significative 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 5points 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 est100. Vous pouvez ajuster ce maximum 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 la position de un 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 afin quelle puisse modifier les widgets et tout ce qui affecte cette interface la barre de titre, par exemple. Ceci signifie 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.

Chapitre 15

Utilisation des threads

181

La Figure 15.1 montre que lactivit se contente dafficher une barre de progression horizontale.
Figure15.1 Lapplication HandlerDemo.

Notez que, bien que le code de cet exemple sarrange pour mettre jour la barre de progression dans le thread de linterface graphique, ce nest pas ncessaire. partir dAndroid1.5, en effet, ProgressBar est thread safe, ce qui signifie que vous pouvez la modifier partir de nimporte quel thread et quelle grera les dtails de la modification de linterface graphique dans le thread de celle-ci.

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 afin quils soient traits.

Excution sur place


Les mthodes post() et postDelayed() de Handler, qui permettent dajouter des objets Runnable dans la file dattente des vnements, peuvent galement tre utilises avec les vues. Ceci permet de simplifier 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.

182

L'art du dveloppement Android 2

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(). Celleci fonctionne comme les mthodes post() de Handler et View car elle met dans une file un 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, Android1.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 supprimera lui-mme le thread en arrire-plan et grera une petite file 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 13mm dans un magasin, il ne veut pas un foret de 13mm: il veut percer des trous de 13mm." 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 arrireplan ne veulent pas vraiment des threads en arrire-plan : ils souhaitent quun certain travail seffectue en dehors du thread de linterface, afin que les utilisateurs ne soient pas bloqus et que les activits ne reoivent pas la redoutable erreur "application not responding" (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);

Chapitre 15

Utilisation des threads

183

redfinir 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. 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 linterface.

Vous navez pas besoin:

AsyncTask, gnricit et paramtres variables


La cration dune sous-classe dAsyncTask nest pas aussi simple que limplmentation de linterface Runnable, par exemple, 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 signifie 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 fins, vous pouvez redfinir quatre mthodes dAsyncTask. La seule que vous devez obligatoirement redfinir pour que votre classe soit utilisable sappelle doInBackground(). 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 spcifique. Cependant, noubliez pas que les tches doivent avoir une fin il est dconseill dutiliser AsyncTask pour raliser une boucle infinie. La mthode doInBackground() recevra en paramtre un tableau variable contenant des lments du premier des trois types mentionns ci-dessus les donnes ncessaires au traitement de la tche. Si la mission de cette tche consiste, par exemple, tlcharger un ensemble dURL, doInBackground() recevra donc un tableau contenant toutes ces URL.

184

L'art du dveloppement Android 2

Cette mthode doit renvoyer une valeur du troisime type de donnes mentionn le rsultat de lopration en arrire-plan. Vous pouvez galement redfinir 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 indi cateur de dbut du traitement. De mme, vous pouvez redfinir 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 redfinir. 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, ontenant les donnes publies par doInBackground() via publishProgress(). c

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 difficult 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 import import import import import import android.app.ListActivity; android.os.AsyncTask; android.os.Bundle; android.os.SystemClock; android.widget.ArrayAdapter; android.widget.Toast; java.util.ArrayList;

public class AsyncDemo extends ListActivity { private static String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi",

Chapitre 15

Utilisation des threads

185

"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(); } } }

186

L'art du dveloppement Android 2

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 StringTask, la cration de ces mots dans un thread en arrire-plan laide de la classe Add notre implmentation dAsyncTask. Lexcution de cette activit construit la liste de mots en quelques secondes et en temps rel, puis lance un toast pour signaler la fin du traitement (voir Figure15.2).
Figure 15.2 Lactivit AsyncDemo en cours de construction de la liste des mots.

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 configurer les types de donnes spcifiques dont nous aurons besoin dans AddStringTask:

Nous navons besoin daucune information de configuration; par consquent, le premier type est Void. Nous voulons passer onProgressUpdate() chaque chane "produite" par notre tche en arrire-plan, afin 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.

Chapitre 15

Utilisation des threads

187

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 un cinquime de seconde pour simuler un traitement. Comme nous avons choisi de nutiliser aucune information de configuration, 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, afin quelle soit ajoute la fin 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 publishProgress(), on ne doit examiner que le premier lment du tableau item.

188

L'art du dveloppement Android 2

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, nous pourrions en profiter pour 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 configuration, 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().

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. Notamment:

Les utilisateurs peuvent interagir avec linterface graphique de votre activit pendant que le thread en arrire-plan sessouffle. Si son travail est altr ou modifi 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

Chapitre 15

Utilisation des threads

189

SMS; il a ventuellement ensuite effectu une recherche dans la liste de ses contacts. Toutes ces oprations peuvent suffire supprimer votre activit de la mmoire. Le Chapitre16 prsentera les diffrents vnements par lesquels passe une activit; vous devez grer les bons et vous assurer de mettre correctement fin au thread en arrire-plan lorsque vous en avez loccasion.

Un utilisateur risque dtre assez mcontent si vous consommez beaucoup de tempsCPU et dautonomie de la batterie sans rien lui donner en retour. Tactiquement, ceci signifie 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 efficace 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 le terminal peut perdre sa connectivit pendant que vous rcuprez des informations partir dInternet, par exemple. La meilleure solution consiste alors prvenir lutilisateur du problme via une Notification (voir Chapitre31) puis mettre fin 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 sur un tlphone mobile, 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 afin 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.

192

L'art du dveloppement Android 2

Dans ce chapitre, nous prsenterons 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 notification 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 (voir Chapitre31). Morte. Lactivit na jamais t lance (le tlphone vient dtre rinitialis, par exemple) ou elle a t tue, ventuellement 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 application sans lappeler. Tout ceci est assez flou et sujet modifications: cest la raison pour laquelle vous devez consulter attentivement la documentation officielle 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.

Chapitre 16

Gestion des vnements du cycle de vie dune activit

193

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 configure 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. Les ressources seront prsentes au Chapitre20.

Cest dans cette mthode que vous configurez 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 fin, soit parce quelle a appel finish() (qui "finit" 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 essentiellement 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 fonction de ce qui sest pass depuis que lutilisateur la vue pour la dernire fois. Si votre activit interroge un service pour savoir sil y a de nouvelles informations (de nouvelles entres dans un flux 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 modifier cette vue.

194

L'art du dveloppement Android 2

Inversement, tout ce qui dtourne lutilisateur de votre activit gnralement, lactivation dune autre activit provoquera lappel donPause(). Vous pouvez profiter 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 utilicalculette devrait donc retrouver le ou les nombres sur lesquels il travaillait lorsquil sait la la rutilise aprs une absence sauf sil avait lui-mme ferm la calculette. Pour que tout ceci fonctionne, les activits doivent donc pouvoir sauvegarder, rapidement et efficacement, 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 redmarre doit rcuprer son tat antrieur afin 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 affich 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.

17
Cration de filtres 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. Cependant, 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 filles, etc. est gnralement trait par des activits indpendantes. Bien que lune delles puisse tre "spcifique" 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,

196

L'art du dveloppement Android 2

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. Maintenant que nous avons vu comment crer des activits, plongeons-nous dans les intentions afin 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 dfini 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 afin quelle les traite, etc. Les intentions sont similaires car elles reprsentent une action et un contexte. Bien quelles permettent de dfinir 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 constantes, comme ACTION_VIEW (pour afficher la ressource), ACTION_EDIT (pour la modifier) 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 passez Android, ce dernier saura comment trouver et ouvrir une activit capable dafficher 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.

Chapitre 17

Cration de filtres dintentions 

197

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 spcifiques. 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 informations 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 lintention, 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 modifis. Il est donc prfrable dutiliser les modles dUri et les types MIME pour identifier 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 vrifies pour quune activit soit ligible pour une intention donne:

Lactivit doit supporter laction indique. Lactivit doit supporter le type MIME indiqu (sil a t fourni). Lactivit doit supporter toutes les catgories nommes dans lintention.

La conclusion est que vous avez intrt ce que vos intentions soient suffisamment spcifiques pour trouver le ou les bons rcepteurs, mais pas plus. Tout ceci deviendra plus clair mesure que nous tudierons quelques exemples.

198

L'art du dveloppement Android 2

Dclarer vos intentions


Tous les composants Android qui souhaitent tre prvenus par des intentions doivent dclarer des filtres dintention afin quAndroid sache quelles intentions doivent aller vers quel composant. Pour ce faire, vous devez ajouter des lments intent-filter au fichier AndroidManifest.xml. Le script de cration des applications Android (activityCreator ou son quivalent IDE) fournit des filtres 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 annonce que:

Cette activit est lactivit principale de cette application. Elle appartient la catgorie LAUNCHER, ce qui signifie quelle aura une icne dans le menu principal dAndroid.

Cette activit tant lactivit principale de lapplication, Android sait quelle est le composant quil devra lancer lorsquun utilisateur choisit cette application partir du menu principal. Vous pouvez bien sr indiquer plusieurs actions ou catgories dans vos filtres dintention afin 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 destine 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" />

Chapitre 17

Cration de filtres dintentions 

199

<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 laffichage dune Uri reprsentant un contenu vnd.android.cursor.item/vnd.commonsware.tour. Cette intention pourrait provenir dune autre activit de la mme application (lactivit MAIN, 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 filtres dintention taient configurs 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 lintentionX et que la base de donnes contienneY, on lance lactivitM; si la base ne contient pasY, on lance lactivitN, par exemple).

Dans ces situations, Android offre un rcepteur dintention dfini 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 effectuer 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 suffit dajouter un lment receiver au fichier 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 signifie 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.

200

L'art du dveloppement Android 2

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 Android Manifest.xml: il faut appeler registerReceiver() dans la mthode onResume() de lactivit pour annoncer son intrt pour une intention, puis appeler unregisterReceiver() 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 messages: cela ne fonctionne que lorsque le rcepteur est actif. Voici ce que prcise la documentation de BroadcastReceiver ce sujet:
Si vous enregistrez un rcepteur dans votre implmentation dActivity.on Resume(), 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, mais uniquement dans les situations 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 Chapitres29 et 30, nous verrons un exemple de la premire situation, o le rcepteur (le client du service) utilise des messages reposant sur des intentions lorsquils sont disponibles, mais nen a pas besoin quand le client est inactif.

18
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 exemple, pourrait avoir des activits permettant de consulter le calendrier, de visualiser un simple vnement, den modifier 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 affiche tout le calendrier, vous voudrez montrer lactivit permettant dafficher cet vnement. Ceci signifie que vous devez pouvoir lancer cette activit en lui faisant afficher un vnement spcifique (celui sur lequel lutilisateur a cliqu).

202


Info

L'art du dveloppement Android 2

Ce chapitre suppose que vous connaissez lactivit lancer, probablement parce quelle fait partie de votre application. Vous pouvez galement avoir une Uri vers quelque chose et vous voulez que vos utilisateurs puissent en faire quelque chose, bien que vous ne sachiez pas encore comment. Cette dernire situation ncessite un traitement plus complexe, que nous dcrivons dans louvrage The Busy Coders Guide to Advanced Android Development (Commonsware, 2009).

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 dauthentification pour un service web auquel vous vous connectez vous devrez peuttre vous authentifier avec OpenID1 pour utiliser un service OAuth2. En ce cas, votre activit principale devra savoir quand se termine lauthentification pour pouvoir commencer utiliser le service web. Imaginons maintenant une application de courrier lectronique Android. Lorsque lutilisateur dcide de visualiser un fichier 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 signifie que la seconde sera prvenue de la fin de son activit fille. 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 fin de sa "fille" 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.

1. 2.

http://openid.net/. http://oauth.net/.

Chapitre 18

Lancement dactivits et de sous-activits 

203

Cration dune intention


Comme on la expliqu au chapitre prcdent, les intentions encapsulent une requte pour une activit ou une demande adresse un autre rcepteur dintention, afin quil ralise une certaine tche. 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 lafficher (ACTION_VIEW).

Faire appel
Lorsque lon dispose de lintention, il faut la passer Android et rcuprer lactivit fille lancer. Deux 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 fin de lactivit fille. Vous pouvez aussi appeler startActivityForResult() en lui passant lintention et un identifiant (unique pour lactivit appelante). Android recherchera lactivit qui correspond le mieux et lui passera lintention. Votre activit sera prvenue par la mthode de rappel onActivityResult() de la fin de lactivit fille.

Comme on la indiqu, vous pouvez implmenter la mthode de rappel onActivity Result() lorsque vous utilisez startActivityForResult(), afin dtre prvenu de la fin de lactivit Result() pour fille. Cette mthode reoit lidentifiant unique fourni startActivityFor que vous puissiez savoir quelle est lactivit qui sest termine. Vous rcuprez galement:

Le code rsultat de lactivit fille 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).

204

L'art du dveloppement Android 2

Un objet String facultatif 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 facultatif 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 Activities/ Launch. Le fichier 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="Emplacement : " /> <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>

Chapitre 18

Lancement dactivits et de sous-activits 

205

</TableLayout> <Button android:id="@+id/map" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Afficher !" /> </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 import import import import import import android.app.Activity; android.content.Intent; android.net.Uri; android.os.Bundle; android.view.View; android.widget.Button; 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 peut le constater avec la Figure18.1, cette activit ne montre pas grand-chose lorsquelle est lance.

206

L'art du dveloppement Android 2

Figure18.1 Lapplication LaunchDemo, dans laquelle on a saisi un emplacement.

Laffichage devient plus intressant si lon entre un emplacement (48.85825 de latitude et 2.2945 de longitude, par exemple) et que lon clique sur le bouton (voir Figure18.2). Notez quil sagit de lactivit de cartographie intgre Android: nous navons pas cr dactivit pour afficher cette carte. Au Chapitre33, nous verrons comment crer des cartes dans nos activits, au cas o lon aurait besoin de plus de contrle sur leur affichage.
Figure18.2 La carte lance par LaunchDemo, montrant lemplacement de la tour Eiffel, Paris.

Chapitre 18

Lancement dactivits et de sous-activits 

207

Info

Cet exemple risque de ne pas fonctionner dans lmulateur avec un AVD Android2.0 car cet AVD ne dispose pas de lapplication Maps.

Navigation avec onglets


La navigation par onglets est lune des principales fonctionnalits des navigateurs web actuels: grce elle, une mme fentre peut afficher plusieurs pages rparties dans une srie donglets. Sur un terminal mobile, elle a moins dintrt car on gaspillerait la prcieuse surface de lcran pour afficher les onglets eux-mmes. Toutefois, pour les besoins de la dmonstration, nous montrerons comment crer ce genre de navigateur, en utilisant Tab Activity et les intentions. Au Chapitre9, 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://www.pearson.fr"));

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 Chapitre13 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")

208

L'art du dveloppement Android 2

.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 fichier de description XML car TabActivity sen occupe pour nous. Nous nous contentons daccder au TabHost et de lui ajouter deux onglets, chacun prcisant 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://www.pearson.fr"); } } 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://www.android.com"); } }

Tous les deux chargent simplement une URL diffrente dans le navigateur: la page daccueil de Pearson France dans lun (voir Figure 18.3), celle dAndroid dans lautre (voir Figure18.4). Le rsultat montre laspect quaurait un navigateur onglets avec Android.

Chapitre 18

Lancement dactivits et de sous-activits 

209

Figure18.3 Lapplication IntentTabDemo montrant le premier onglet.

Figure18.4 Lapplication IntentTabDemo montrant le second onglet.

Info

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" afin de lutiliser.

19
Gestion de la rotation
Certains terminaux Android comme le G1 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 afin 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 vrifier que vos interfaces utilisateurs apparatront correctement dans les deux orientations.

Philosophie de la destruction
Par dfaut, lorsquune modification dans la configuration 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 affiches. Bien que ce phnomne puisse avoir lieu pour un grand nombre de modifications de configuration (changement 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 layouts, notamment).

212

L'art du dveloppement Android 2

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 configuration.

Tout est pareil, juste diffrent


Comme, par dfaut, Android dtruit et rcre votre activit lorsque lorientation change, il suffit dutiliser la mthode onSaveInstanceState(), qui est appele chaque fois que lactivit est supprime quelle quen soit la raison (un manque de mmoire, par exemple). Implmentez cette mthode dans votre activit afin de stocker dans le Bundle fourni suffisamment dinformations pour pouvoir revenir ltat courant. Puis, dans onCreate() (ou onRestoreInstanceState(), si vous prfrez), restaurez les donnes du Bundle et utilisezles pour remettre lactivit dans son tat antrieur. Le projet Rotation/RotationOne, par exemple, utilise deux fichiers layout main.xml pour les modes portrait et paysage. Ces fichiers se trouvent, respectivement, dans les rpertoires 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>

Chapitre 19

Gestion de la rotation

213

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) modifie la disposition de linterface. Bien que les boutons naient pas dtat, vous consta teriez, si vous utilisiez dautres widgets (comme EditText), quAndroid prend automatiquement 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 permet de choisir un contact et de consulter sa fiche au moyen de deux boutons diffrents. Le bouton Voir nest 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;

214

L'art du dveloppement Android 2

@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);

Chapitre 19


if (contact!=null) {

Gestion de la rotation

215

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. Initialement, 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 appele partir donCreate()). Si le contact nest pas null, le bouton Voir est activ et permet de visualiser la fiche du contact slectionn. Le rsultat est prsent aux Figures19.1 et 19.2.
Figure19.1 Lapplication RotationOne en mode portrait.

216

L'art du dveloppement Android 2

Figure19.2 Lapplication RotationOne en mode paysage.

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. Les exemples de ce chapitre ne fonctionnent quavec Android2.0 ou une version suprieure car ils utilisent les nouveaux mcanismes de slection du fournisseur de contenu Contacts (voir Chapitre26).

Info

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 messagerie instantane, 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 onRetainNonConfigurationInstance() au lieu donSaveInstanceState() pour les "petites" modifications comme les rotations. La mthode de rappel onRetainNonConfigurationInstance() de votre activit

Chapitre 19

Gestion de la rotation

217

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 fichiers de description, et donc lapparence visuelle, sont identiques ceux de Rotation/Rotation One. 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)); } });

218

L'art du dveloppement Android 2

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 redfinissons 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. 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 suffire se faire tuer, ce quils napprcieront srement pas. Bien que ce soit moins un problme avec leG1 puisquune rotation implique douvrir le clavier chose que lutilisateur ne fera pas au milieu dun jeu , dautres terminaux

Chapitre 19

Gestion de la rotation

219

effectuent une rotation simplement en fonction des informations de leurs acclromtres. Dans ce cas, vous pouvez indiquer Android que vous vous occuperez vous-mme des rotations et que vous ne voulez pas que le framework vous aide.

Rotation maison
Pour grer vous-mme les rotations, vous devez procder comme suit: 1. Utilisez lattribut android:configChanges de llment activity dans votre fichier AndroidManifest.xml pour numrer les modifications de configuration que vous voulez grer vous-mme. 2. Dans votre classe Activity, implmentez la mthode onConfigurationChanged(), qui sera appele lorsque lune des modifications numres dans android:configChanges aura lieu. Vous pouvez dsormais outrepasser tout le processus de suppression dactivit pour nimporte quelle modification de configuration et simplement laisser une mthode de rappel vous prvenir de cette modification. Le projet Rotation/RotationThree utilise cette technique. L encore, les fichiers 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 modification 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"> <uses-sdk android:minSdkVersion="5" android:targetSdkVersion="6" /> <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" />

220

L'art du dveloppement Android 2

<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

Ici, nous indiquons que nous traiterons nous-mmes les modifications de configuration keyboardHidden et orientation, ce qui nous protge de toutes les modifications de "rotation" une ouverture dun clavier ou une rotation physique. Notez que cette configuration 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);

Chapitre 19


Button btn=(Button)findViewById(R.id.pick); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { Intent i=new Intent(Intent.ACTION_PICK,

Gestion de la rotation

221

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 configure 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 rotation. 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 suffit dajouter android:screen Orientation = "portrait" (ou "landscape") au fichier 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"

222

L'art du dveloppement Android 2

android:versionName="1.0.0"> <uses-sdk android:minSdkVersion="5" android:targetSdkVersion="6" /> <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 configuration est propre une activit. Avec elle, lactivit est verrouille dans lorientation que vous avez prcise, quoi que lon fasse ensuite. Les copies dcran des Figures19.3 et 19.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.
Figure19.3 Lapplication RotationFour en mode portrait.

Chapitre 19

Gestion de la rotation

223

Figure19.4 Lapplication RotationFour en mode paysage.

Notez quAndroid supprimera et recrera quand mme votre activit, mme si lorientation est fixe une valeur spcifique, comme ici. Pour viter cela, il faut galement initialiser android:configChanges dans le manifeste, comme expliqu plus haut.

Tout comprendre
Tous ces scnarios supposent que vous faites pivoter lcran en ouvrant le clavier du terminal (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. Certains tlphones Android, comme le HTC Magic, se comportent de la mme faon; pour dautres, lorientation de lcran ne dpend que de louverture ou de la fermeture du clavier. Cependant, mme pour ces derniers, il est trs facile de modifier ce comportement : pour que laffichage pivote en fonction de la position du tlphone, il suffit dajouter android:screenOrientation = "sensor" au fichier AndroidManifest.xml (voir le projet Rotation/RotationFive):
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"

224

L'art du dveloppement Android 2

package="com.commonsware.android.rotation.five" android:versionCode="1" android:versionName="1.0.0"> <uses-sdk android:minSdkVersion="5" android:targetSdkVersion="6" /> <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 acclromtres qui contrlent lorientation de lcran, afin quil pivote en mme temps que le tlphone. Sur le G1, en tout cas, ceci semble ne fonctionner que lorsque lon passe de la position portrait classique la position paysage en faisant pivoter le tlphone de 90degrs dans le sens inverse des aiguilles dune montre. Une rotation dans lautre sens ne modifie pas lcran. Notez galement que cette configuration 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 configuration android:screenOrientation = "sensor", lcran ne pivotera pas.

20
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 fichiers de description (layouts) , mais il en existe de nombreux autres dont vos applications peuvent tirer profit: les images et les chanes de caractres, par exemple.

Les diffrents types de ressources


Les ressources dun projet Android sont stockes dans des fichiers 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, linterface utilisateur dune activit via une ressource de type layout (dans res/layout), vous navez pas besoin danalyser vous-mme le contenu du fichierXML Android sen chargera pour vous.

226

L'art du dveloppement Android 2

Outre les layouts (que nous avons rencontrs pour la premire fois au Chapitre4) et les animations (introduites au Chapitre9), il existe plusieurs autres types de ressources:

Les images (res/drawable) permettent de placer des icnes statiques ou dautres images dans une interface utilisateur. Les ressources brutes (res/raw) permettent dutiliser des fichiers quelconques ayant une signification pour votre application, mais pas ncessairement pour les frameworks dAndroid. 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 fichiers 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, gnralement, 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 les vritables informations au cours de lexcution. En outre, le formatage de texte simple, appel "texte styl", 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 fichier 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 fils 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>

Chapitre 20

Utilisation des ressources

227

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 prfixant 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 fichier 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 lidentifiant de ressource de la chane, cest-dire son nom unique prfix 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 variables (Mon nom est %1$s, par exemple). Vous pouvez utiliser pour ces formats des chanes normales stockes sous forme de ressources:
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. Par contre, vous devez protger les marqueurs HTML et non les traiter littralement:
<resources> <string name="b">Ce texte est en &lt;b&gt;gras&lt;/b&gt;.</string> <string name="i"> Alors que celui-ci est en &lt;i&gt;italiques&lt;/i&gt; ! </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));

228

L'art du dveloppement Android 2

Formats styls
Le texte styl devient compliqu grer lorsquil sagit de lutiliser avec les formats de chanes car 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 ce stade (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 vers un objet Spanned, grce Html.fromHtml().


uneTextView.setText(Html.fromHtml(resultatDeStringFormat));

Pour voir tout ceci en action, examinons le fichier 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"

Chapitre 20


android:layout_width="fill_parent" android:layout_height="wrap_content" />

Utilisation des ressources

229

</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 fichier faisant rfrence une ressource chane (@string/btn_ name), nous avons besoin dun fichier 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. Enfin, 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 {

230

L'art du dveloppement Android 2

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 get String() chose que nous aurions pu faire dans onCreate() pour plus defficacit. Puis nous nous en servons pour formater la valeur du champ, ce qui nous renvoie un 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 exemple. Enfin, on convertit ce texte HTML en objet texte styl laide de Html.fromHtml() et nous modifions notre label. La Figure20.1 montre que le label de lactivit est vide lors de son lancement.

Chapitre 20

Utilisation des ressources

231

Figure20.1 Lapplication StringsDemo aprs son lancement.

La Figure20.2 montre ce que lon obtient aprs avoir saisi un nom et cliqu sur le bouton.
Figure20.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 officiellement 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 fichiers images dans res/ drawable/ puis y faire rfrence comme des ressources. Dans les fichiers de descrip-

232

L'art du dveloppement Android 2

tion XML, vous pouvez les dsigner par @drawable/nomfic, o nomfic est le nom de base dufichier (le nom de la ressource correspondant au fichier res/drawable/truc.png est donc @drawable/truc). Dans le code Java, il suffit de prfixer le nom de base du fichier parR.drawable lorsque vous avez besoin de lidentifiant de ressource (R.drawable.truc, par exemple). Modifions lexemple prcdent pour quil utilise une icne la place de la ressource chane du bouton. Ce projet, Resources/Images, ncessite dabord de modifier lgrement le fichier de description afin 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 fichier image dans res/drawable avec icon comme nom de base. Ici, nous utiliserons un fichier PNG de 3232pixels, issu de lensemble dicnes Nuvola1. Enfin, nous modifions le code Java pour remplacer le Button par un ImageButton:

1.

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

Chapitre 20


android.app.Activity; android.os.Bundle; android.text.TextUtils; android.text.Html; android.view.View; android.widget.Button; android.widget.ImageButton; android.widget.EditText; android.widget.TextView;

Utilisation des ressources

233

package com.commonsware.android.resources; import import import import import import import import import

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); 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 Figure20.3).

234

L'art du dveloppement Android 2

Figure20.3 Lapplication ImagesDemo.

Les ressources XML


Pour assembler des documentsXML statiques avec une application, il suffit dutiliser des ressourcesXML. Placez simplement un fichierXML dans res/xml/ et vous pourrez y accder en appelant la mthode getXml() sur un objet Resources et en lui passant en paramtre un identifiant prfix par R.xml, suivi du nom de base du fichierXML. Si une activit est fournie avec un fichier words.xml, par exemple, vous pouvez appeler getResources().getXml(R.xml. words) afin dobtenir une instance de la classe XmlPullParser, qui se trouve dans lespace de noms org.xmlpull.v1. Un analyseur pull XML est pilot par les vnements: il suffit 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 fils directs de cet lment. En itrant, en testant et en invoquant du code lment par lment, vous finissez par analyser tout le fichier. Ce nouveau projet Resources/XML exige que vous placiez le fichier words.xml dans res/ xml:
package com.commonsware.android.resources; import android.app.Activity; import android.os.Bundle; import android.app.ListActivity;

Chapitre 20

Utilisation des ressources

235

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 { 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(); }

236

L'art du dveloppement Android 2

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 fin 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 du widget de slection. Comme nous avons un contrle total sur le fichierXML (puisque cest nous qui lavons cr), nous pouvons supposer quil ny a exactement quun attribut si nous nen tions pas srs, nous pourrions compter leur nombre avec getAttributeCount() et obtenir leur nom avec getAttributeName() au lieu de supposer aveuglment que lattribut dindice0 est celui auquel on pense. Le rsultat obtenu est prsent la Figure20.4.
Figure20.4 Lapplication
XMLResourceDemo.

Valeurs diverses
Outre des ressources chanes, vous pouvez placer dans le rpertoire res/values/ un ou plusieurs fichiers XML dcrivant des ressources simples : des dimensions, des couleurs

Chapitre 20

Utilisation des ressources

237

et des tableaux. Dans les exemples 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 exemple). Vous pouvez, bien sr, configurer 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 fichiers de description XML. En les plaant dans des fichiers de ressourcesXML, vous pouvez faire rfrence ces valeurs la fois dans du code Java et dans les fichiers de description; en outre, elles sont ainsi centralises au mme endroit, ce qui facilite leur maintenance. Les fichiers ressources XML ont pour racine llment resources; tous les autres lments sont des fils 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 10pixels, 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/72e de pouce en termes typographiques, l aussi

daprs la taille physique de lcran.


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 160dpi, 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 avec un attribut name nommant 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 fichier 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 lecode Java, il suffit dutiliser le nom unique prfix par R.dimen (Resources.getDimen (R.dimen.fin), par exemple).

238

L'art du dveloppement Android 2

Couleurs
Les couleurs Android sexpriment en valeurs RGB hexadcimales et peuvent ventuel lement indiquer 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 (Cascading Style Sheets). Vous pouvez, bien sr, les placer comme des chanes de caractres dans le code Java ou les fichiers de description. Cependant, il suffit dajouter des lments color au fichier de ressource afin de les grer comme des ressources. Ces lments doivent galement 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 fichier 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, prfixez 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 fichier 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 fils item quil y a dlments dans le tableau. Le contenu de chacun de ces fils 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>

Chapitre 20

Utilisation des ressources

239

<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 tableau de String contenant tous les lments du tableau nom (Resources. getStringArray(R.array.codes_aeroports), par exemple).

Grer la diffrence
Un mme ensemble de ressources peut ne pas convenir toutes les situations dutilisation de votre application. 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 signifie quil na donc pas dorientation particulire. La taille de lcran. Combien a-t-il de pixels? Vous devez en tenir compte pour accorder 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?

240

L'art du dveloppement Android 2

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 fichier res/ values/strings.xml mais, 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-11. Les chanes anglaises seront donc places dans le fichier res/values-en/strings.xml et les franaises, dans res/values-fr/strings.xml. Android choisira alors le bon fichier en fonction de la configuration 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 et deux autres terminaux fictifs. Lun deux (le Fictif Un) dispose dun cranVGA gnralement en mode paysage ("large"), dun clavier alphanumrique toujours ouvert, dun pad directionnel, mais pas dcran tactile. Lautre (le Fictif Deux) a le mme cran que le G1 ("normal"), un clavier numrique mais pas alphabtique, un pad directionnel mais pas dcran tactile. Pour tirer parti de ces diffrences dcrans et doptions de saisie, vous pourriez crer des fichiers 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. Les options de configuration (-en, par exemple) ont une certaine priorit et doivent apparatre dans cet ordre dans le nom du rpertoire. La documentation dAndroid2 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.

Dans ces situations, toutes sortes de rgles entrent en jeu:

1. http://fr.wikipedia.org/wiki/Liste_des_codes_ISO_639-1. 2. http://code.google.com/android/devel/resources-i18n.html#AlternateResources.

Chapitre 20

Utilisation des ressources

241

Il ne peut exister quune seule valeur par rpertoire pour chaque catgorie doption de configuration. Les options sont sensibles la casse.
res/layout-large-port-notouch-qwerty res/layout-normal-port-notouch-qwerty res/layout-large-port-notouch-12key res/layout-normal-port-notouch-12key res/layout-large-port-notouch-nokeys res/layout-normal-port-notouch-nokeys res/layout-large-port-stylus-qwerty res/layout-normal-port-stylus-qwerty res/layout-large-port-stylus-12key res/layout-normal-port-stylus-12key res/layout-large-port-stylus-nokeys res/layout-normal-port-stylus-nokeys res/layout-large-port-finger-qwerty res/layout-normal-port-finger-qwerty res/layout-large-port-finger-12key res/layout-normal-port-finger-12key res/layout-large-port-finger-nokeys res/layout-normal-port-finger-nokeys res/layout-large-land-notouch-qwerty res/layout-normal-land-notouch-qwerty res/layout-large-land-notouch-12key res/layout-normal-land-notouch-12key res/layout-large-land-notouch-nokeys res/layout-normal-land-notouch-nokeys res/layout-large-land-stylus-qwerty res/layout-normal-land-stylus-qwerty res/layout-large-land-stylus-12key res/layout-normal-land-stylus-12key

Pour notre scnario, nous aurions donc besoin, en thorie, des rpertoires suivants:

242

L'art du dveloppement Android 2

res/layout-large-land-stylus-nokeys res/layout-normal-land-stylus-nokeys res/layout-large-land-finger-qwerty res/layout-normal-land-finger-qwerty res/layout-large-land-finger-12key res/layout-normal-land-finger-12key res/layout-large-land-finger-nokeys res/layout-normal-land-finger-nokeys

Pas de panique! Nous allons abrger cette liste dans un petit moment! En ralit, beaucoup de ces fichiers 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 (finger) et ceux manipulables avec un stylet (stylus). 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 configuration que vous navez pas prises en compte disposer dune description par dfaut permettra alors votre application de fonctionner 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 normale, par exemple, les rpertoires -larges seront limins des candidats possibles car ils font spcifiquement appel une autre taille. 2. Deuximement, Android compte le nombre de correspondances pour chaque rpertoire et ne conserve que les rpertoires qui en ont le plus. 3. Enfin, 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 configurations suivantes:

res/layout-large-port-notouch-qwerty
res/layout-port-notouch-qwerty res/layout-large-port-notouch

Chapitre 20

Utilisation des ressources

243

res/layout-port-notouch res/layout-large-port-qwerty res/layout-port-qwerty res/layout-large-port res/layout-port res/layout-large-land-notouch-qwerty res/layout-land-notouch-qwerty res/layout-large-land-notouch res/layout-land-notouch res/layout-large-land-qwerty res/layout-land-qwerty res/layout-large-land res/layout-land

Ici, nous tirons parti du fait que les correspondances spcifiques ont priorit sur les valeurs "non spcifies". 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 taille normale, 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, Fictif Un et Fictif Deux) et en gardant res/layout comme description par dfaut:

res/layout-large-port-notouch res/layout-port-notouch res/layout-large-land-notouch res/layout-land-notouch res/layout-land res/layout

Ici, -large permet de diffrencier Fictif Un des deux autres, tandis que notouch distingue Fictif Deux du G1. Nous rencontrerons nouveau ces ressources au Chapitre36, lorsque nous expliquerons comment grer les tailles dcrans diffrentes.

21
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 configuration choisie par lutilisateur le dernier flux consult par le lecteurRSS, 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.

246

L'art du dveloppement Android 2

Obtenir ce que vous voulez


Pour accder aux prfrences, vous pouvez:

appeler la mthode getPreferences() partir de votre activit pour accder ses prfrences spcifiques; 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 PreferencesManager 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 valeur0. 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 ensemble de mthodes daccs aux prfrences par leurs noms. Ces mthodes renvoient un rsultat 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.

Dfinir vos prfrences


partir dun objet SharedPreferences, vous pouvez appeler edit() pour obtenir un "diteur" de prfrences. Cet objet dispose dun ensemble de mthodes modificatrices 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 modifications effectues via lditeur.

La dernire est importante: si vous modifiez les prfrences avec lditeur et que vous nappeliez pas commit(), les modifications disparatront lorsque lditeur sera hors de porte. Inversement, comme les prfrences acceptent des modifications en direct, si lune des parties de votre application (une activit, par exemple) modifie des prfrences partages, les autres parties (tel un service) auront immdiatement accs la nouvelle valeur.

Chapitre 21

Utilisation des prfrences 

247

Un mot sur le framework


partir de sa version0.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, afin que les applications naient pas rinventer la roue. Llment central de ce framework est encore une structure de donnesXML. Vous pouvez dcrire les prfrences de votre application dans un fichierXML stock dans le rpertoire res/xml/ du projet. partir de ce fichier, Android peut prsenter une interface graphique pour manipuler ces prfrences, qui seront ensuite stockes dans lobjet SharedPreferences obtenu par getDefaultSharedPreferences(). Voici, par exemple, le contenu dun fichier XML de prfrences, extrait du projet Prefs/ Simple:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key="@string/checkbox" android:title="Prfrence case cocher" android:summary="Cochez ou dcochez" /> <RingtonePreference android:key="@string/ringtone" android:title="Prfrence 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 fichier, PreferenceScreen peut contenir des dfinitions de prfrences des sous-classes de Preference, telles CheckBoxPreference ou RingtonePreference, qui, comme lon pourrait sy attendre, permettent respectivement de cocher une case et de choisir une sonnerie. Dans le cas de Ringtone Preference, 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 fichier 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 suffit den crer une sous-classe, de la faire pointer vers ce fichier et de la lier au reste de votre application.

248

L'art du dveloppement Android 2

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 suffit dappeler addPreferencesFromResource() en lui indiquant la ressource XML contenant les prfrences. Vous devrez galement ajouter cette activit votre fichier 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 maintenant 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);

Chapitre 21

Utilisation des prfrences 

249

return(super.onCreateOptionsMenu(menu)); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case EDIT_ID: startActivity(new Intent(this, EditPreferences.class)); return(true); } return(super.onOptionsItemSelected(item)); }

La Figure21.1 montre linterface de configuration des prfrences de cette application.


Figure21.1 Linterface des prfrences de SimplePrefsDemo.

La case cocher peut tre coche ou dcoche directement. Pour modifier la sonnerie, il suffit de cliquer sur son entre dans les prfrences pour voir apparatre une bote de dialogue comme celle de la Figure21.2.
Figure21.2 Choix dune sonnerie partir des prfrences.

250

L'art du dveloppement Android 2

Vous remarquerez quil nexiste pas de bouton "Save" ou "Commit": les modifications sont sauvegardes ds quelles sont faites. Outre ce menu, lapplication SimplePrefsDemo affiche 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 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() {

Chapitre 21

Utilisation des prfrences 

251

super.onResume(); SharedPreferences prefs=PreferenceManager .getDefaultSharedPreferences(this); checkbox.setText(new Boolean(prefs .getBoolean("checkbox", false)) .toString()); ringtone.setText(prefs.getString("ringtone", "<unset>")); }

Ceci signifie que les champs seront modifis louverture de lactivit et aprs la fin de lactivit des prfrences (via le bouton "back", par exemple). La Figure21.3 montre le contenu de cette table aprs le choix de lutilisateur.
Figure21.3 Liste des prfrences de SimplePrefsDemo.

Ajouter un peu de structure


Si vous devez proposer un grand nombre de prfrences configurer, 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 fichier XML des prfrences et permettent de regrouper des prfrences apparentes. Au lieu quelles soient toutes des filles 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.

252

L'art du dveloppement Android 2

Si vous avez un trs grand nombre de prfrences trop pour quil soit envisageable que lutilisateur les fasse dfiler , vous pouvez galement les placer dans des "crans" distincts laide de llment PreferenceScreen (le mme que llment racine). Tout fils de PreferenceScreen est plac dans son propre cran. Si vous imbriquez des PreferenceScreen, lcran pre affichera lcran fils sous la forme dune entre : en la touchant, vous ferez apparatre le contenu de lcran fils correspondant. Le fichier 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="Prfrences simples"> <CheckBoxPreference android:key="@string/checkbox" android:title="Prfrence case cocher" android:summary="Cochez ou dcochez" /> <RingtonePreference android:key="@string/ringtone" android:title="Prfrence sonnerie" android:showDefault="true" android:showSilent="true" android:summary="Choisissez une sonnerie" /> </PreferenceCategory> <PreferenceCategory android:title="Dtails supplmentaires"> <PreferenceScreen android:key="detail" android:title="Dtails" android:summary="Prfrences supplmentaires 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 fichier XML produit une liste dlments comme ceux de la Figure21.4, regroups en catgories.

Chapitre 21

Utilisation des prfrences 

253

Figure21.4 Linterface des prfrences de StructuredPrefsDemo, montrant les catgories et un marqueur dcran.

En touchant lentre du sous-cran, celui-ci saffiche et montre les prfrences quil contient (voir Figure21.5).
Figure21.5 Le sous-cran de prfrences de StructuredPrefsDemo.

Botes de dialogue
Toutes les prfrences ne sont pas, bien sr, que des cases cocher ou 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 interface des prfrences mais touchent une prfrence, remplissent une valeur et cliquent sur OK pour valider leur modification.

254

L'art du dveloppement Android 2

Comme le montre ce fichier 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="Prfrences simples"> <CheckBoxPreference android:key="@string/checkbox" android:title="Prfrence case cocher" android:summary="Cochez ou dcochez" /> <RingtonePreference android:key="@string/ringtone" android:title="Prfrence sonnerie" android:showDefault="true" android:showSilent="true" android:summary="Choisissez une sonnerie" /> </PreferenceCategory> <PreferenceCategory android:title="Dtails supplmentaires"> <PreferenceScreen android:key="detail" android:title="Dtails" android:summary="Prfrences supplmentaires dans une autre page"> <CheckBoxPreference android:key="@string/checkbox2" android:title="Autre case cocher" android:summary="On ou Off. Peu importe." /> </PreferenceScreen> </PreferenceCategory> <PreferenceCategory android:title="Prfrences 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 intressant" /> <ListPreference android:key="@string/list"

Chapitre 21


android:title="Dialogue de choix"

Utilisation des prfrences 

255

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), nous indiquons la fois un titre au dialogue et deux ressources de type tableau de chanes: lun pour les noms affichs, lautre pour les valeurs correspondantes qui doivent tre dans le mme ordre lindice du nom affich 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>

256

L'art du dveloppement Android 2

Comme le montre la Figure 21.6, les prfrences comprennent dsormais une catgorie supplmentaire contenant deux nouvelles entres.
Figure21.6 Lcran des prfrences de DialogsDemo.

Toucher lentre Dialogue de saisie dun texte provoque laffichage dun... dialogue de saisie dun texte ici, il est prrempli avec la valeur courante de la prfrence (voir Figure21.7).
Figure21.7 Modification dune prfrence avec un champ de saisie.

Toucher lentre Dialogue de choix affiche... une liste de choix sous forme de bote de dialogue prsentant les noms des villes (voir Figure21.8).

Chapitre 21

Utilisation des prfrences 

257

Figure 21.8 Modification dune prfrence avec une liste.

22
Accs et gestion des bases de donnes locales
SQLite1 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 simple 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; comme nous allons le voir, ce nest pas trs difficile.

1.

http://www.sqlite.org.

260

L'art du dveloppement Android 2

Ce chapitre prsente les bases de lutilisation de SQLite dans le contexte du dveloppement Android. Il ne prtend absolument pas tre une prsentation exhaustive de ce SGBDR: pour plus de renseignements et pour savoir comment lutiliser dans dautres environnements quAndroid, nous vous conseillons louvrage de Mike Owens, The Definitive Guide to SQLite1 (Apress, 2006).

Exemple de base de donnes


Lessentiel du code de lexemple prsent dans ce chapitre provient de lapplication Database/Constants, qui, comme le montre la Figure22.1, affiche la liste de constantes dfinies par la classe SensorManager dAndroid.
Figure22.1 Lapplication Constants, juste aprs son lancement.

Vous pouvez ouvrir un menu pour ajouter une nouvelle constante: comme le montre la Figure22.2, une bote de dialogue saffiche alors pour vous permettre de saisir son nom et sa valeur. La constante saisie est ajoute la liste. Un touch prolong sur une constante de la liste affiche un menu contextuel comprenant une option Supprimer qui, aprs confirmation, supprime la constante concerne.

1.

http://www.amazon.com/Definitive-Guide-SQLite/dp/1590596730.

Chapitre 22

Accs et gestion des bases de donnes locales

261

Figure 22.2 Lapplication Constants, juste aprs son lancement.

Ces donnes sont bien entendu stockes dans une base de donnes SQLite.

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 dfinitions de donnes (CREATE TABLE, etc.). certains moments, SQLite scarte du standard SQL-92, comme la plupart des autres SGBDR, dailleurs. La bonne nouvelle est que SQLite est si efficace en terme de mmoire que le moteur dexcution dAndroid peut linclure dans son intgralit: vous ntes donc pas oblig de vous contenter dun sous-ensemble de ses fonctionnalits pour gagner de la place. La plus grosse diffrence avec les autres SGBDR concerne principalement le typage des donnes. Bien que vous puissiez prciser les types des colonnes dans une instruction CREATE TABLE, SQLite ne les utilisera qu titre indicatif.Vous pouvez donc 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 documentation1:
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.

1.

http://www.sqlite.org/different.html.

262

L'art du dveloppement Android 2

Certaines fonctionnalits standard de SQL ne sont pas reconnues par SQLite, notamment les contraintes de cl trangre, les transactions imbriques, les jointures externes 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.

Info

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 sousclasse de SQLiteOpenHelper. Cette classe enveloppe tout ce qui est ncessaire la cration et la mise jour dune base, selon vos spcifications 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 afin de pouvoir convertir au mieux une base de donnes dun schma vers un autre. Pour convertir une base dun ancien schma un nouveau, lapproche la plus simple consiste supprimer les anciennes tables et en crer de nouvelles.

Voici, par exemple, la classe DatabaseHelper de Database/Constants, qui cre une table dans onCreate() et lui ajoute un certain nombre de lignes. Dans onUpgrade(), elle triche un peu en supprimant la table existante et en appelant nouveau onCreate():

Chapitre 22

Accs et gestion des bases de donnes locales

263

package com.commonsware.android.constants; import import import import import import import android.content.ContentValues; android.content.Context; android.database.Cursor; android.database.SQLException; android.database.sqlite.SQLiteOpenHelper; android.database.sqlite.SQLiteDatabase; android.hardware.SensorManager;

public class DatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME="db"; public static final String TITLE="title"; public static final String VALUE="value"; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE constants (_id INTEGER <FL> PRIMARY KEY AUTOINCREMENT, title TEXT, value REAL);"); ContentValues cv=new ContentValues(); cv.put(TITLE, "Apesanteur, Etoile de la mort I"); cv.put(VALUE, SensorManager.GRAVITY_DEATH_STAR_I); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Terre"); cv.put(VALUE, SensorManager.GRAVITY_EARTH); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Jupiter"); cv.put(VALUE, SensorManager.GRAVITY_JUPITER); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Mars"); cv.put(VALUE, SensorManager.GRAVITY_MARS); db.insert("constants", TITLE, cv);

264

L'art du dveloppement Android 2

cv.put(TITLE, "Apesanteur, Mercure"); cv.put(VALUE, SensorManager.GRAVITY_MERCURY); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Lune"); cv.put(VALUE, SensorManager.GRAVITY_MOON); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Neptune"); cv.put(VALUE, SensorManager.GRAVITY_NEPTUNE); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Pluton"); cv.put(VALUE, SensorManager.GRAVITY_PLUTO); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Saturne"); cv.put(VALUE, SensorManager.GRAVITY_SATURN); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Soleil"); cv.put(VALUE, SensorManager.GRAVITY_SUN); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Lile"); cv.put(VALUE, SensorManager.GRAVITY_THE_ISLAND); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Uranus"); cv.put(VALUE, SensorManager.GRAVITY_URANUS); db.insert("constants", TITLE, cv); cv.put(TITLE, "Apesanteur, Venus"); cv.put(VALUE, SensorManager.GRAVITY_VENUS); db.insert("constants", TITLE, cv); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { android.util.Log.w("Constants",

Chapitre 22

Accs et gestion des bases de donnes locales

265

"Maj de la base, suppression de toutes les anciennes donnees"); db.execSQL("DROP TABLE IF EXISTS constants"); onCreate(db); } }

Pour utiliser cette sous-classe de SQLiteOpenHelper, crez une instance et demandez-lui dappeler getReadableDatabase() ou getWriteableDatabase() selon que vous vouliez ou non modifier son contenu. Notre activit ConstantsBrowser, par exemple, ouvre la base de donnes dans onCreate():
db=(new DatabaseHelper(getContext())).getWritableDatabase();

Cet appel renverra une instance de SQLiteDatabase qui vous servira ensuite interroger ou modifier la base de donnes. Lorsque vous avez fini de travailler sur cette base (lorsque lactivit est ferme, par exemple), il suffit 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 dfinition des donnes) que vous voulez excuter. En cas derreur, cette mthode renvoie null. La mthode onCreate() de DatabaseHelper, par exemple, utilise le code suivant:
db.execSQL("CREATE TABLE constants (_id INTEGER PRIMARY KEY AUTOINCREMENT,<FL> title TEXT, value REAL);");

Cet appel cre une table constants 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 : title (un texte) et value (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 suffit dutiliser execSQL() afin dexcuter les instructions DROP INDEX et DROP TABLE.

266

L'art du dveloppement Android 2

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:

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. Utiliser insert(), update() et delete() sur lobjet SQLiteDatabase. Ces mthodes "builder" prennent en paramtre une instruction SQL dcoupe en plusieurs morceaux. Elles utilisent des objets ContentValues qui implmentent une interface 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 ga lement de getAsInteger(), getAsString(), etc.
private void processAdd(DialogWrapper wrapper) { ContentValues values=new ContentValues(2); values.put("title", wrapper.getTitle()); values.put("value", wrapper.getValue()); db.insert("constants", "title", values); constantsCursor.requery(); }

Voici, par exemple, comment insrer une ligne dans la table constants avec insert():

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(). 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 fixes 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.

Chapitre 22

Accs et gestion des bases de donnes locales

267

La mthode delete() fonctionne comme update(): elle prend en paramtre le nom de la table et, ventuellement, une clause WHERE et une liste des paramtres positionnels pour cette clause. Voici, par exemple, comment nous supprimons la ligne d_ID indiqu de la table constants:
private void processDelete(long rowId) { String[] args={String.valueOf(rowId)}; db.delete("constants", "_ID=?", args); constantsCursor.requery(); }

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: cest la raison pour laquelle nous allons les prsenter sparment.

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:
constantsCursor=db.rawQuery("SELECT _ID, title, value "+ "FROM constants ORDER BY title", null);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.

268

L'art du dveloppement Android 2

Requtes normales
La mthode query() prend en paramtre les parties dune instruction SELECT afin de construire la requte. Ces diffrentes composantes apparaissent dans lordre suivant dans la liste des paramtres: le nom de la table interroge;

la liste des colonnes rcuprer; la clause WHERE, qui peut contenir des paramtres positionnels; la liste des valeurs substituer ces paramtres positionnels; une ventuelle clause GROUP BY; une ventuelle clause ORDER BY; 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 possibilits 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 SQLite QueryBuilder:
@Override public Cursor query(Uri url, String[] projection, String selection,

Chapitre 22

Accs et gestion des bases de donnes locales

269

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 aux Chapitres26 et27. En attendant, nous pouvons nous contenter de remarquer que:

Nous construisons un objet SQLiteQueryBuilder. Nous lui indiquons la table concerne par la requte avec setTables(getTableName()). Soit nous lui indiquons lensemble de colonnes renvoyer par dfaut (avec setProjectionMap()), soit nous lui donnons une partie de clause WHERE afin didentifier une ligne prcise de la table partir dun identifiant extrait de lURI fournie lappel de
query() (avec appendWhere()).

Enfin, nous lui demandons dexcuter la requte en mlangeant les valeurs de dpart avec celles fournies query() (qb.query(db, projection, selection, selectionArgs, 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.

270

L'art du dveloppement Android 2

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().
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();

Voici, par exemple, comment parcourir les entres dune table widgets:

Vous pouvez galement encapsuler un Cursor dans un SimpleCursorAdapter ou une autre implmentation, puis passer ladaptateur rsultant une ListView ou un autre widget de slection. Aprs avoir rcupr la liste des constantes tries, par exemple, nous plaons chacune delles dans la ListView de lactivit ConstantsBrowser en quelques lignes de code:
ListAdapter adapter=new SimpleCursorAdapter(this, R.layout.row, constantsCursor, new String[] {"title", "value"}, new int[] {R.id.title, R.id.value}); setListAdapter(adapter);

Chapitre 22

Accs et gestion des bases de donnes locales

271

Info

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().

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 fichier 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.commonsware. 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 interface 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. Cependant, noubliez pas que vous travaillez alors sur une copie de la base: si vous voulez rpercuter les modifications sur le terminal, vous devrez retransfrer cette base sur celui-ci. Pour rcuprer la base stocke sur le terminal, utilisez la commande adb pull (ou le gestionnaire de fichiers du Dalvik Debug Monitor Service, que nous prsenterons au Chapitre35) en lui fournissant le chemin de la base sur le terminal et celui de la destination sur votre machine. Pour faire linverse, 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 Manager1 pour Firefox est lun des clients SQLite les plus accessibles (voir Figure22.3), car elle est disponible sur toutes les plates-formes.
1. https://addons.mozilla.org/en-US/firefox/addon/5817.

272

L'art du dveloppement Android 2

Figure22.3 Lextension SQLite Manager de Firefox.

Vous trouverez galement dautres clients1 sur le site web de SQLite2.

1. http://www.sqlite.org/cvstrac/wiki?p=SqliteTools. 2. http://www.sqlite.org.

23
Accs aux fichiers
Bien quAndroid dispose de moyens de stockage structurs via les prfrences et les bases de donnes, un simple fichier suffit parfois. Android offre donc deux modles daccs aux fichiers: lun pour les fichiers 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 fichier situ dans le rpertoire res/raw : elles seront alors intgres au fichier APK de lapplication comme une ressource brute au cours du processus dassemblage. Pour accder ce fichier, vous avez besoin dun objet Resources que vous pouvez obtenir partir de lactivit en appelant getResources(). Cet objet fournit la mthode openRawResource() pour rcuprer un InputStream sur le fichier spcifi par son identifiant (un entier). Cela fonctionne exactement comme laccs aux widgets avec findViewById():

274

L'art du dveloppement Android 2

si vous placez un fichier words.xml dans res/raw, son identifiant dans le code Java sera R.raw.words. Comme vous ne pouvez obtenir quun InputStream, vous navez aucun moyen de modifier ce fichier: cette approche nest donc vraiment utile que pour lire des donnes statiques. 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 stockage qui, elle, sera modifiable (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 modifications dans un autre fichier ou dans une base de donnes, puis les fusionner lorsque vous avez besoin dune vue complte de ces informations. Si votre application fournit une liste dURL, par exemple, vous pourriez grer un deuxime fichier pour stocker les URL ajoutes par lutilisateur ou pour rfrencer celles quil a supprimes. Le projet Files/Static reprend lexemple ListViewDemo du Chapitre7 en utilisant cette fois-ci un fichier XML la place dun tableau dfini directement dans le programme. Lefichier de descriptionXML 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 fichier XML contenant les mots de la liste:
<words> <word value="lorem" /> <word value="ipsum" />

Chapitre 23

Accs aux fichiers

275

<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 suffira pour notre dmonstration. Le code Java doit maintenant lire ce fichier, 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 {

276

L'art du dveloppement Android 2

InputStream in=getResources().openRawResource(R.raw.words); 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 Chapitre7 se situent essentiellement dans le corps de la mthode onCreate(). Ici, on obtient un InputStream pour le fichierXML en appelant getResources().openRawResource(R.raw.words), puis on se sert des fonctionnalits danalyseXML prdfinies pour transformer le fichier en documentDOM, 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 Figure23.1, le rsultat de lactivit est identique celui lexemple du Chapitre7 car la liste de mots est la mme.

Chapitre 23

Accs aux fichiers

277

Figure23.1 Lapplication StaticFileDemo.

Comme nous lavons vu au Chapitre 20, il existe videmment des moyens encore plus simples dutiliser les fichiersXML contenus dans le paquetage dune application: utiliser une ressource XML, par exemple. Cependant, bien que cet exemple utiliseXML, le fichier aurait pu simplement contenir un mot par ligne ou utiliser un format non reconnu nati vement par le systme de ressources dAndroid.

Lire et crire
La lecture et lcriture de ses propres fichiers de donnes spcifiques 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 afin dobtenir, respectivement, un InputStream et un OutputStream. partir de l, le processus nest pas beaucoup diffrent de celui des E/SJava classiques:

On enveloppe ces flux 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 flux avec close() lorsque lon a termin.

Deux applications qui essaient de lire en mme temps un fichier notes.txt via open FileInput() accderont chacune leur propre dition du fichier. Si vous voulez quun mme fichier soit accessible partir de plusieurs endroits, vous devrez srement crer un fournisseur de contenu, comme on lexplique au Chapitre27.

278

L'art du dveloppement Android 2

Notez galement quopenFileInput() et openFileOutput() ne prennent pas en paramtre des chemins daccs (chemin/vers/fichier.txt, par exemple), mais uniquement des noms de fichiers. Le code suivant, extrait du projet Files/ReadWrite, est le layout 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" android:gravity="top" /> </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 import import import import import import import import import import import android.app.Activity; android.os.Bundle; android.view.View; android.widget.Button; android.widget.EditText; android.widget.Toast; java.io.BufferedReader; java.io.File; java.io.InputStream; java.io.InputStreamReader; java.io.OutputStream; java.io.OutputStreamWriter;

Chapitre 23

Accs aux fichiers

279

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) {

280

L'art du dveloppement Android 2

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 utilisant setOnClickListener() pour invoquer la mthode finish() de lactivit 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 fig). Nous lisons le fichier notes.txt laide dopenFileInput() et nous plaons son contenu dans lditeur de texte. Si le fichier na pas t trouv, nous supposons que cest parce que cest la premire fois que lactivit sexcute (ou que le fichier a t supprim par dautres moyens) et nous nous contentons de laisser lditeur vide. Enfin, 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 fichier notes.txt laide dopenFileOutput() et nous y plaons le contenu de lditeur de texte. Le rsultat est un bloc-notes persistant ayant laspect prsent par les Figures23.2 et 23.3. Tout ce qui est tap le restera jusqu sa suppression; le texte survivra la fermeture de lactivit, lextinction du tlphone et aux autres situations similaires.

Chapitre 23

Accs aux fichiers

281

Figure23.2 Lapplication ReadWriteFileDemo, aprs son lancement.

Figure23.3 Lapplication ReadWriteFileDemo, aprs avoir saisi du texte.

Vous pouvez galement lire et crire des fichiers sur la zone de stockage externe (cest-dire la carte SD) en vous servant dEnvironment.getExternalStorageDirectory() afin dobtenir un objet File situ la racine de la carte SD. partir dAndroid1.6, vous devrez galement possder les permissions pour accder cette zone (WRITE_EXTERNAL_STORAGE, par exemple). Les permissions sont prsentes au Chapitre28. Noubliez pas que la zone de stockage externe est accessible toutes les applications, alors quopenFileInput() et openFileOutput() concernent la zone prive lapplication.

24
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 (pour autant que vous puissiez les utiliser avec la variante Java dAndroid). Ce chapitre explique ce quil faut faire pour tirer parti de ces bibliothques et dcrit les limites de lintgration du code tiers dans une application Android.

284

L'art du dveloppement Android 2

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, videmment, manque des deux. Lutilisation de code tiers, notamment lorsquil est dj assembl sous forme de JAR, peut faire gonfler 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 le modifier pour ladapter Android. Si, par exemple, vous nutilisez que 10% dune bibliothque tierce, il est peut-tre plus intressant de recompiler ce sousensemble 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 assembls. Si vous choisissez la premire mthode, il suffit de copier le code source dans larborescence de votre projet (sous le rpertoire src/) afin 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. Commencez par placer le fichier JAR dans le rpertoire libs/ de votre projet Android. Puis, si vous vous servez dun IDE, ajoutez le JAR la liste des rfrences ncessaires votre projet. Si vous utilisez ant en ligne de commande, celui-ci utilisera automatiquement tous les fichiers JAR prsents dans le rpertoire libs/.

Chapitre 24

Tirer le meilleur parti des bibliothques Java 

285

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. BeanShell1 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 fichier JAR de linterprteur dans votre rpertoire libs/. Malheureusement, le JAR2.0b4 disponible au tlchargement sur le site de BeanShell ne fonctionne pas tel quel avec Android0.9 et les SDK plus rcents, probablement cause du compilateur utilis pour le compiler. Il est donc prfrable de rcuprer son code source laide de Subversion2, 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 configure 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. Voici par exemple le fichier 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 !" />

1. 2.

http://beanshell.org. http://beanshell.org/developer.html.

286

L'art du dveloppement Android 2

<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 import import import import import import import android.app.Activity; android.app.AlertDialog; android.os.Bundle; android.view.View; android.widget.Button; android.widget.EditText; android.widget.Toast; 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);

Chapitre 24

Tirer le meilleur parti des bibliothques Java 

287

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 Figure24.1).
Figure24.1 LIDE AndShell.

Le script que nous voulons excuter dans cet IDE est le suivant:
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 configure globalement par lactivit pour se dsigner elle-mme. 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 Figure24.2.

288

L'art du dveloppement Android 2

Figure24.2 LIDE AndShell excutant un script BeanShell.

Ceci tant dit, il y a quelques limites connatre:

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 modifis pour produire du pseudo-code Dalvik la place. Les langages plus simples, qui interprtent seulement les fichiers 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. Les langages de script sans JIT seront forcment plus lents que des applications Dalvik compiles ; cette lenteur peut dplaire aux utilisateurs et impliquer plus de consom mation de batterie pour le mme travail. Construire une application Android en Bean Shell uniquement parce que vous trouvez quelle est plus facile crire peut donc rendre vos utilisateurs assez mcontents. 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 (les permissions sont prsentes au Chapitre28). Enfin, mais ce nest pas le moins important, les JAR des interprteurs ont tendance tre... gros. Celui du BeanShell utilis ici fait 200Ko, par exemple. Ce nest pas ridicule

Chapitre 24

Tirer le meilleur parti des bibliothques Java 

289

si lon considre ce quil est capable de faire, mais ceci implique que les applications qui utilisent BeanShell seront bien plus longues tlcharger, quelles prendront plus de place sur le terminal, etc.

Tout fonctionne... enfin, presque


Comme nous lavons mentionn, tous les codes Java ne fonctionneront pas avec Android et Dalvik. Vous devez plus prcisment tenir compte des paramtres suivants:

Si le code Java suppose quil sexcute avec JavaSE, JavaME ou JavaEE, il ne trouvera 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 problmes 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 compi lation 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 lecode tiers en mme temps que le vtre et dtecter plus tt les difficults rsoudre.

Autres langages de 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 intgration 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

290

L'art du dveloppement Android 2

sont pas vraiment redistribuables. De plus, ASE na pas t rellement conu pour tendre dautres applications, mme sil peut tre utilis de cette faon. Cependant, si vous voulez programmer directement sur le terminal, il est srement la meilleure solution actuelle. Pour plus dinformations sur ASE, consultez la page daccueil du projet: http://code.google. com/p/android-scripting/.

25
Communiquer via Internet
On sattend gnralement ce que la plupart des terminaux Android, si ce nest tous, intgrent un accs Internet. Cet accs peut passer par le Wifi, les services de donnes cellulaires (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 Wifi 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 navigateur WebKit intgr que nous avons tudi au Chapitre13, mais il peut galement intervenir 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 spcifiques 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.

292

L'art du dveloppement Android 2

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
HttpComponents1: 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 HttpRequest, on construit lURL rcuprer ainsi que les autres donnes de configuration (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 paramtre un objet ResponseHandler<String>: cet appel renverra simplement une reprsentation de la rponse sous forme de String. En pratique, cette approche est dconseille car il est prfrable de vrifier les codes de rponses HTTP pour dtecter les erreurs ; cependant, pour les applications triviales comme les exemples de ce livre, la technique ResponseHandler<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 afficher. 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 ici
1. http://hc.apache.org/.

Chapitre 25

Communiquer via Internet

293

que les extraits de code en rapport avec ce chapitre; les sources complets sont disponibles dans les exemples fournis avec ce livre1. 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 Chapitre32. 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 echoue: " + t.toString(), 4000) .show(); } }

La mthode updateForecast() prend un objet Location en paramtre, obtenu via le pro cessus de mise jour de la localisation. Pour linstant, il suffit de savoir que Location dispose des mthodes getLatitude() et getLongitude(), qui renvoient, respectivement, la latitude et la longitude. LURL de 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.

294

L'art du dveloppement Android 2

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) afin 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 20 ; il dispose galement dun 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 Chapitre24. 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

Chapitre 25

Communiquer via Internet

295

orecast en utilisant la date, la temprature et lURL de licne qui sera affiche en F fo nction 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 Figure25.1 montre le rsultat obtenu.


Figure25.1 Lapplication WeatherDemo.

296

L'art du dveloppement Android 2

Autres points importants


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

26
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 fichiers 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 reprsenter des collections ou des lments individuels. Grce aux premires, vous pouvez crer

298

L'art du dveloppement Android 2

de nouveaux contenus via des oprations dinsertion. Avec les secondes, vous pouvez lire les donnes quelles reprsentent, les modifier ou les supprimer. Android permet dutiliser des fournisseurs de contenu existants ou de crer les vtres. Cechapitre est consacr leur utilisation; le Chapitre27 expliquera comment mettre disposition vos propres donnes laide du framework des fournisseurs de contenu.

Composantes dune Uri


Le modle simplifi de construction dune Uri est constitu du schma, de lespace de noms des donnes et, ventuellement, de lidentifiant de linstance. Ces diffrents composants 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 didentifiant 5. La combinaison du schma et de lespace de noms est appele "Uri de base" dun fournisseur de contenu ou dun ensemble de donnes support par un fournisseur de contenu. Dans lexemple prcdent, content://constants est lUri de base dun fournisseur 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:// com.android.contacts/contacts, 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 identifiant 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://com.android.contacts/contacts. Si vous avez simplement besoin de la collection, cette Uri fonctionne telle quelle; si vous avez besoin dune instance dont vous connaissez lidentifiant, vous pouvez lajouter la fin de cette dernire, afin dobtenir une Uri pour cette instance prcise.

Chapitre 26

Utilisation dun fournisseur de contenu (content provider) 

299

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:// com.android. contacts/contacts", 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:

LUri de base du fournisseur de contenu auquel sadresse la requte ou lUri dinstance de lobjet interrog. Un tableau des proprits dinstances que vous voulez obtenir de ce fournisseur de contenu. Une contrainte, qui fonctionne comme la clause WHERE de SQL. Un ensemble ventuel de paramtres lier la contrainte, par remplacement des ventuels marqueurs demplacements quelle contient. 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 managedQuery() de la classe ConstantsBrowser du projet ContentProvider/ConstantsPlus:
constantsCursor=managedQuery(Provider.Constants.CONTENT_URI, PROJECTION, null, null, null);

300

L'art du dveloppement Android 2

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 reprsente 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. Cette liste devrait tre prcise dans la documentation (ou le code source) de chaque fournisseur de contenu. Ici, nous utilisons des valeurs logiques de la classe Provider qui reprsentent les diffrentes proprits qui nous intressent (lidentifiant 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 manuel lement 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 suffit 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},

Chapitre 26

Utilisation dun fournisseur de contenu (content provider) 

301

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. Lidentifiant du layout utilis pour afficher les lments de la liste (R.layout.row). Le curseur (constantsCursor). Les proprits extraire du curseur et utiliser pour configurer les instances View des lments de la liste (TITLE et VALUE). Les identifiants 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 Figure26.1. Pour disposer de plus de contrle sur les vues, vous pouvez crer une sous-classe de SimpleCursorAdapter et redfinir getView() afin de crer vos propres widgets pour la liste, comme on la expliqu au Chapitre8. Vous pouvez galement manipuler manuellement le Cursor (avec moveToFirst() et String(), par exemple) comme on la montr au Chapitre22. get
Figure26.1
ConstantsBrowser,

affichage dune liste de constantes physiques.

302

L'art du dveloppement Android 2

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 modifier 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 Content Values 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 modifications 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:

LUri reprsentant la collection (ou linstance) que vous voulez supprimer.

Chapitre 26

Utilisation dun fournisseur de contenu (content provider) 

303

Une contrainte fonctionnant comme une clause WHERE, qui sert dterminer les lignes qui doivent tre supprimes. 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. Lutilisation principale dune photo dans Android, par exemple, consiste lafficher, ce que sait parfaitement faire le widget ImageView si on lui fournit une Uri vers un fichier 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 lafficher: il suffit 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 prdfinies pour afficher ces donnes.

27
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 fournisseur, 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,

306

L'art du dveloppement Android 2

par exemple, vous dveloppez un lecteur RSS et que vous vouliez autoriser les autres programmes accder aux flux que vous avez tlchargs et mis en cache , vous aurez besoin dun fournisseur de contenu. Pour illustrer notre propos, nous utiliserons ici lapplication ContentProvider/ ConstantsPlus, qui a dj t prsente au Chapitre26 et reprend le principe de lapplication du Chapitre22, mais en dplaant le code qui manipule la base de donnes dans un fournisseur de contenu qui est ensuite utilis par lapplication.

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 lidentifiant. 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 identifiant 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 lidentifiant 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 identifiant dinstance, qui est un entier identifiant une information particulire du contenu. Une Uri de contenu sans identifiant 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 lensemble du contenu fourni par nimporte quel fournisseur li lautorit sekrits (SecretsProvider, par exemple), ou aussi complique que content://sekrits/card/pin/17, qui dsigne linformation (identifie par 17) de type card/pin gre par le fournisseur de contenu sekrits.

Chapitre 27

Construction dun fournisseur de contenu 

307

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 identifier 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 identifiant. Comme on la dj vu, 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 linstance 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).

Cration dun fournisseur de contenu


La cration dun fournisseur de contenu seffectue en quatre tapes: cration de la classe du fournisseur, fourniture dune Uri, dclaration des proprits et modification du manifeste.

tape n1: cration dune 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, modifier 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.

308

L'art du dveloppement Android 2

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 fichier XML comme "table des matires", cest dans onCreate() que lon vrifiera que ce rpertoire et ce fichier existent et, dans le cas contraire, quon les crera pour que le reste du fournisseur de contenu sache quils sont disponibles. Voici, par exemple, la mthode onCreate() de la classe Provider du projet ContentProvider/ ConstantsPlus:
@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 Chapitre22.

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 tableau de 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 tableau de String contenant les valeurs qui remplaceront les ventuels marqueurs demplacements 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 lutilisation 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.

Chapitre 27

Construction dun fournisseur de contenu 

309

Pour les fournisseurs qui utilisent SQLite, toutefois, limplmentation de la mthode query() devrait tre triviale: il suffit 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 lidentifiant dinstance la requte. Puis nous utilisons la mthode query() de lobjet builder pour obtenir un Cursor correspondant au rsultat.

310

L'art du dveloppement Android 2

insert()
Votre mthode insert() recevra une Uri reprsentant la collection et une structure Content Values 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 suffit de vrifier 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); }

Chapitre 27

Construction dun fournisseur de contenu 

311

La technique est la mme que prcdemment: pour raliser linsertion, on utilise les particularits 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 vrifions 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 automati quement gres par la dfinition de la table SQLite.

update()
La mthode update() prend en paramtre lUri de linstance ou de la collection modifier, une structure ContentValues contenant les nouvelles valeurs, une chane correspondant 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 didentifier linstance ou les instances modifier (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 suffit de transmettre tous les paramtres la mthode update() de la base, mme si cet appel variera lgrement selon que vous modifiez 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

312

L'art du dveloppement Android 2

+ ) : ""), whereArgs); } getContext().getContentResolver().notifyChange(url, null); return count; }

Ici, les modifications pouvant sappliquer une instance prcise ou toute la collection, nous testons lUri avec isCollectionUri(): sil sagit dune modification de collection, nous nous contentons de la faire. Sil sagit dune modification dune seule instance, nous ajoutons 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. Mais 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 nous pouvons 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);

Chapitre 27

Construction dun fournisseur de contenu 

313

} 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 vrifie la clause WHERE passe en paramtre).

getType()
getType() est la dernire mthode quil faut implmenter. Elle prend une Uri en paramtre 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 correspondant. 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 n2: 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.constants.Provider/constants");

314

L'art du dveloppement Android 2

Vous pouvez utiliser le mme espace de noms pour lUri de contenu que pour vos classes Java, afin de rduire le risque de collisions de noms.

tape n3: dclarer les proprits


Pour dfinir 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, afin 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 pourrait sagir de chanes, 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 signifie que vous devez documenter soigneusement vos proprits afin que les utilisateurs de votre fournisseur de contenu sachent quoi sattendre.

tape n4: modifier le manifeste


Ce qui lie limplmentation du fournisseur de contenu au reste de lapplication se trouve dans le fichier AndroidManifest.xml. Il suffit dajouter un lment <provider> comme fils de llment <application>:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.constants"> <application android:label="@string/app_name" android:icon="@drawable/cw"> <provider android:name=".Provider" android:authorities="com.commonsware.android.constants.Provider" /> <activity android:name=".ConstantsBrowser" android:label="@string/app_name"> <intent-filter>

Chapitre 27

Construction dun fournisseur de contenu 

315

<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

La proprit android:name est le nom de la classe du fournisseur de contenu, prfix 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 fournisseur 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 identifiant dinstance. Chaque autorit de chaque valeur CONTENT_URI devrait tre incluse dans la liste android:authorities. Quand Android rencontrera une Uri de contenu, il pourra dsormais passer en revue les fournisseurs enregistrs via les manifestes afin de trouver une autorit qui correspond. Ilsaura 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 modifications


Votre fournisseur de contenu peut ventuellement avertir ses clients lorsque des modifi cations ont t apportes aux donnes dune Uri de contenu particulire. Supposons, par exemple, que vous ayez cr un fournisseur de contenu qui rcupre des flux 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 flux 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 flux). Vous avez galement implment un service qui obtient de faon asynchrone les mises jour de ces flux et qui modifie les donnes stockes sous-jacentes. Votre fournisseur de contenu pourrait alerter les applications qui utilisent les flux que tel ou tel flux a t mis jour, afin que celles qui lutilisent puissent rafrachir ses donnes pour disposer de la dernire version. Du ct du fournisseur de contenu, ceci ncessite dappeler notifyChange() sur votre instance de ContentResolver (que vous pouvez obtenir via un appel getContext().get ContentResolver()). Cette mthode prend deux paramtres: lUri de la partie du contenu qui a t modifie et le ContentObserver qui a initi la modification. Dans de nombreux cas, ce deuxime paramtre vaudra null ; une valeur non null signifie simplement que lobservateur qui a lanc la modification ne sera pas prvenu de sa propre modification.

316

L'art du dveloppement Android 2

Du ct du consommateur de contenu, une activit peut appeler registerContent Observer() 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 fini avec lUri, unappel unregisterContentObserver() permet de librer la connexion.

28
Demander et exiger despermissions
la fin 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 utilisaient son API car 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 puisquil sapplique galement aux fournisseurs de contenu et aux services qui ne font pas partie du framework initial.

318

L'art du dveloppement Android 2

En tant que dveloppeur Android, vous devrez frquemment vous assurer que vos applications 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.

Maman, puis-je?
Demander dutiliser les donnes ou les services dautres applications exige dajouter llment uses-permission au fichier AndroidManifest.xml. Votre manifeste peut ainsi contenir zro ou plusieurs de ces lments comme fils 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 prdfinies les plus importantes:

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 modification des donnes dans les fournis-

seurs de contenu intgrs.

Lutilisateur devra confirmer ces permissions lors de linstallation de lapplication. Il est important de ne demander que le minimum de permissions et de justifier cette demande afin que les utilisateurs ne choisissent pas dignorer votre application parce quelle exige trop de permissions inutiles. Notez toutefois que cette demande de confirmation nest pas disponible dans la version actuelle de lmulateur. Si vous ne possdez pas une permission donne et que vous tentez deffectuer une opration 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. La vrification dune permission nchoue que si vous avez oubli de demander cette permission; votre application ne peut pas sexcuter sans que vous ayez obtenu les permissions quelle exige.

Chapitre 28

Demander et exiger despermissions

319

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 fournisseurs de contenu ou des services, vous voudrez mettre en place une scurit tourne "vers lintrieur", 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 confidentialit des informations ou lutilisation de services qui pourraient vous coter cher. Les permissions de base dAndroid sont conues pour cela pouvez-vous lire ou modifier des contacts, envoyer des SMS ? etc. Si votre application ne stocke pas des informations prives, la scurit est moins un problme. Mais, si elle stocke des donnes personnelles, 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 fichier AndroidManifest.xml. Dans ce cas, au lieu dutiliser usespermission, vous ajoutez des lments permission. L encore, il peut y avoir zro ou plusieurs de ces lments, qui seront tous des fils directs de manifest. Dclarer une permission est un peu plus compliqu quutiliser une permission car vous devez donner trois informations:

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 prfixe. Un label pour la permission: un texte court et comprhensible par les utilisateurs. 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 permission possible; votre application doit quand mme savoir dtecter les violations de scurit lorsquelles ont lieu.

320

L'art du dveloppement Android 2

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.

Imposer les permissions via le manifeste


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 signification 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 lexp diteur 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 la consultation du fournisseur de contenu, tandis que writePermission contrle laccs aux insertions, aux modifications et aux suppressions de donnes dans le fournisseur.

Chapitre 28

Demander et exiger despermissions

321

Imposer les permissions ailleurs


Il y a deux moyens supplmentaires dimposer les permissions dans le code Java.

Vos services peuvent vrifier les permissions chaque appel, grce checkCalling Permission(), qui renvoie PERMISSION_GRANTED ou PERMISSION_DENIED selon que appelant a ou non la permission indique. Si, par exemple, votre service implmente l des mthodes de lecture et dcriture spares, vous pourriez obtenir le mme effet que readPermission et writePermission en vrifiant que ces mthodes ont les permissions requises.

Vous pouvez galement inclure une permission dans lappel sendBroadcast(). Ceci signifie 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 signale quun SMS est arriv, par exemple ceci restreint les rcepteurs de cette intention ceux qui sont autoriss recevoir des messages SMS.

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 confirmer 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.

29
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 flux RSS sur Internet et la persistance dune session de messagerie instantane, 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 limplmentation sera essentiellement prsente dans ce chapitre. Ce projet WeatherPlusService tend Internet/ Weather en lempaquetant dans un service qui surveille les modifications de lemplacement du terminal, afin que les prvisions soient mises jour lorsque celui-ci "se dplace".

324

L'art du dveloppement Android 2

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 redfinit 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 redfinir les mthodes onCreate(), onResume(), onPause() et apparentes, les implmentations de Service peuvent redfinir trois mthodes du cycle de vie:

onCreate(), qui, comme pour les activits, sera appele lorsque le processus du service

est cr.
onStart(), qui est appele chaque fois quun service est lanc par startService(). 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); mgr = (LocationManager)getSystemService(Context.LOCATION_SERVICE); mgr.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 10000.0f, onLocationChange); }

On appelle dabord la mthode onCreate() de la superclasse afin 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 LocationManager.GPS_MANAGER, qui sera dtaill au Chapitre32. La mthode onDestroy() est bien plus simple:
@Override public void onDestroy() {

Chapitre 29

Cration dun service

325

super.onDestroy();

mgr.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.

Nous examinerons cette mthode plus en dtail dans la section suivante.

Il ne peut en rester quun!


Par dfaut, les services sexcutent dans le mme processus que tous les autres composants 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; malheureusement, 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 suffit alors que lon expose le singleton luimme pour que les autres composants puissent accder lobjet. Nous pourrions exposer ce singleton via un membre public statique ou une mthode getter publique et statique, mais nous prendrions alors des risques lis la gestion de la mmoire. En effet, tout ce qui est rfrenc partir dun contexte statique est protg du ramassemiettes: il ne faudrait donc surtout pas oublier de remettre la rfrence statique null dans onDestroy() car, sinon, lobjet WeatherPlusService resterait indfiniment en mmoire, bien quil soit dconnect du reste dAndroid, et ce jusqu ce quAndroid dcide de supprimer notre processus. Il existe heureusement une meilleure solution : utiliser onBind().Une liaison (binding) permet un service dexposer son API aux activits (ou dautres services) qui lui sont lis. Lessentiel de cette infrastructure est configure pour permettre les services distants, o lAPI lier est disponible via IPC: un service peut donc exposer son API aux autres applications. Cependant, une liaison peut galement tre utile dans les situations o le service et ses clients sont tous dans la mme application cest le scnario dun service local.

326

L'art du dveloppement Android 2

Pour exposer le service aux autres activits via une liaison locale, vous devez dabord crer une classe interne publique qui hrite de android.os.Binder:
public class LocalBinder extends Binder { WeatherPlusService getService() { return(WeatherPlusService.this); } }

Ici, notre Binder nexpose quune seule mthode, getService(), qui renvoie le service lui-mme. Dans un scnario de service distant, ceci ne fonctionnerait pas car les IPC nous empchent de passer des services entre les processus. Pour les services locaux, par contre, cela fonctionne parfaitement. Puis nous devons renvoyer cet objet Binder partir de notre mthode onBind():
@Override public IBinder onBind(Intent intent) { return(binder); }

Tout client qui se lie dsormais notre service sera capable daccder lobjet service luimme et dappeler ses mthodes. Nous y reviendrons en dtail au chapitre suivant.

Destine du manifeste
Enfin, vous devez ajouter le service votre fichier AndroidManifest.xml pour quil soit reconnu comme un service utilisable. Il suffit pour cela dajouter un lment service comme fils de llment application en utilisant lattribut android:name pour dsigner la classe du service. Voici, par exemple, le fichier AndroidManifest.xml du projet WeatherPlusService:
<?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" />

Chapitre 29


</intent-filter> </activity> <service android:name=".WeatherPlusService" />

Cration dun service

327

<category android:name="android.intent.category.LAUNCHER" />

</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 simplifie (".WeatherPlusService") pour dsigner votre classe. Si vous voulez exiger certaines permissions pour lancer ou lier le service, ajoutez un attribut android:permission prcisant la permission demande voir Chapitre28 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 prvenir lactivit que ces nouvelles prvisions sont disponibles, afin quelle puisse les charger et les afficher. Pour interagir de cette faon avec les composants, deux possibilits sont votre disposition: les mthodes de rappel et les intentions diffuses. Notez que, si votre service doit simplement alerter lutilisateur dun certain vnement, classique vous pouvez utiliser une notification (voir Chapitre31), ce qui est le moyen le plus pour 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 lorsque cela est ncessaire. Pour que ceci fonctionne, vous devez :

dfinir une interface Java pour cet objet couteur; donner au service une API publique pour enregistrer et supprimer des couteurs; faire en sorte que le service utilise ces couteurs au bon moment, pour prvenir ceux qui ont enregistr lcouteur dun vnement donn;

328

L'art du dveloppement Android 2

faire en sorte que lactivit enregistre et supprime un couteur en fonction de ses besoins; faire en sorte que lactivit rponde correctement aux vnements grs par les couteurs.

La plus grande difficult 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 activits correspondantes resteront en mmoire mme si elles ne sont plus utilises par Android, ce qui cre donc une fuite mmoire importante. Vous pouvez utiliser des WeakReference, des SoftReference ou des constructions similaires pour garantir que les couteurs 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 17 consiste faire en sorte que le service lance une intention diffuse qui pourra tre capture par lactivit ensupposant 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 flux est empaquete dans FetchForecastTask, une implmentation dAsyncTask qui 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); }

Chapitre 29

Cration dun service

329

catch (Throwable t) { android.util.Log.e("WeatherPlus", "Exception dans updateForecast()", t); } return(null); } @Override protected void onProgressUpdate(Void... inutilise) { // Inutile ici } @Override protected void onPostExecute(Void inutilise) { // 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 directement la placer dans le WebView de lactivit. La seconde est que lon appelle sendBroadcast(), 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";

30
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. En revanche, cela 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. Dans ce chapitre, nous tudierons la partie cliente de lapplication Service/WeatherPlus. Lactivit WeatherPlus ressemble normment lapplication Weather originale comme le montre la Figure30.1, il sagit simplement dune page web qui affiche les prvisions mtorologiques.

332

L'art du dveloppement Android 2

Figure30.1 Le client du service WeatherPlus.

Transmission manuelle
Pour dmarrer un service, une premire approche consiste appeler 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, utiliser ce dernier. Si vous avez implment onBind() comme on la montr au chapitre prcdent, vous pouvez accder au service par un autre moyen: en appelant la mthode bindService(). Lorsquune activit se lie un service, elle demande essentiellement pouvoir accder lAPI publique de ce service via le binder qui a t renvoy par la mthode onBind(). Lactivit peut galement demander Android, laide de lindicateur BIND_AUTO_CREATE, de lancer automatiquement le service si ce dernier na pas dj t dmarr. Pour utiliser cette technique avec nos classes WeatherPlus et WeatherPlusService, nous devons dabord appeler bindService() partir de onCreate():
@Override public void onCreate(Bundle savedInstanceState) {

Chapitre 30

Appel dun service

333

super.onCreate(savedInstanceState); setContentView(R.layout.main); browser = (WebView)findViewById(R.id.webkit); bindService(new Intent(this, WeatherPlusService.class), onService, Context.BIND_AUTO_CREATE); }

Cet appel bindService() utilise un objet de rappel onService, qui est une instance de ServiceConnection:
private ServiceConnection onService=new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder rawBinder) { appService=((WeatherPlusService.LocalBinder)rawBinder).getService(); } public void onServiceDisconnected(ComponentName className) { appService=null; } };

Notre objet onService sera appel avec onServiceConnected() ds le lancement de WeatherPlusService. Nous passons un objet IBinder, qui est un descripteur opaque reprsentant le service et que nous pouvons utiliser pour obtenir le LocalBinder expos par WeatherPlusService. Grce ce LocalBinder, on rcupre lobjet WeatherPlusService lui-mme, que lon stocke dans un membre priv:
private WeatherPlusService appService = null;

Nous pouvons ensuite appeler les mthodes de WeatherPlusService et notamment obtenir la page des prvisions:
private void updateForecast() { try { String page=appService.getForecastPage(); browser.loadDataWithBaseURL(null, page, "text/html", "UTF-8", null); } catch (final Throwable t) { goBlooey(t); } }

334

L'art du dveloppement Android 2

Nous devons galement appeler unbindService() partir donDestroy() pour librer la liaison avec WeatherPlusService:
@Override public void onDestroy() { super.onDestroy(); unbindService(onService); }

Si aucun autre client nest li au service, Android lteindra galement pour librer sa mmoire il nest donc pas ncessaire dappeler explicitement stopService() puisque Android le fera pour nous lors de la suppression de la liaison. Bien que cette approche ncessite un peu plus de code que la simple utilisation dun singleton pour lobjet service, elle est moins sujette aux fuites de mmoire. Rcapitulons :

Pour lancer un service, utilisez bindService() avec loption BIND_AUTO_CREATE si vous voulez communiquer en utilisant le mcanisme de liaison; startService() sinon. Pour arrter un service, faites linverse : utilisez unbindService() ou stopService().

Une autre possibilit pour arrter un service consiste faire en sorte que le service appelle sa mthode stopSelf(). Si vous utilisez startService() pour lancer un service et que vous effectuiez un traitement dans un thread en arrire-plan, par exemple, cette approche permettra au service de se terminer de lui-mme lorsque le thread se termine.

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 modification 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)); }

Chapitre 30

Appel dun service

335

@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(); }

31
Alerter les utilisateurs avec des notifications
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, notifications 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 notifications.

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

338

L'art du dveloppement Android 2

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 notifications. 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 napparaissent que lorsque cela est ncessaire (lorsquun message est arriv, par exemple). Vous pouvez lancer des notifications 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 un objet Notification, qui est une structure dcrivant la forme que doit prendre lavertissement. Les possibilits de cet objet sont dcrites dans les sections qui suivent.

Notifications 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). Les terminaux Android feront tout leur possible pour respecter vos choix de couleurs, mais des terminaux diffrents peuvent produire des couleurs diffrentes, voire ne pas vous donner le choix: le DEXT de Motorola, par exemple, na quune seule LED blanche et naffichera donc que cette couleur quelle que soit celle que vous avez demande. 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. Enfin, 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. Pour utiliser cette approche, vous devrez demander la permission VIBRATE (voir Chapitre28).

Icnes
Alors que les lumires, les sons et les vibrations sont destins attirer le regard de lutilisateur vers son terminal, les icnes constituent ltape suivante consistant signaler ce qui est si important.

Chapitre 31

Alerter les utilisateurs avec des notifications

339

Pour configurer une icne pour une Notification, vous devez initialiser deux champs publics: icon, qui doit fournir lidentifiant 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 afin que lutilisateur puisse grer lvnement qui a dclench la notification. Vous pouvez galement fournir un texte qui apparatra lorsque licne est place sur la barre dtat (tickerText). Pour configurer 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:
package com.commonsware.android.notify; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import java.util.Timer; import java.util.TimerTask; 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);

340

L'art du dveloppement Android 2

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!!!", 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); } }

Chapitre 31

Alerter les utilisateurs avec des notifications

341

Cette activit fournit deux gros boutons, un pour lancer une notification aprs un dlai de 5secondes, lautre pour annuler cette notification si elle est active. La Figure31.1 montre laspect de son interface lorsquelle vient dtre lance.
Figure31.1 La vue principale de NotifyDemo.

La cration de la notification dans notifyMe() se droule en six 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 notification est lance et le temps associ cet vnement. 3. Crer un PendingIntent qui dclenchera laffichage dune autre activit -Message). (Notify 4. Utiliser setLatestEventInfo() pour prciser que lon affiche un titre et un message lorsquon clique sur la notification et quon lance le PendingIntent lorsque lon clique sur le message. 5. Mettre jour le numro associ la notification. 6. Demander au NotificationManager dafficher la notification. Par consquent, si lon clique sur le bouton du haut, la boule rouge apparatra dans la barre de menu aprs un dlai de 5secondes, accompagne brivement du message dtat, comme le montre la Figure31.2. Aprs la disparition du message, un chiffre saffichera sur la boule rouge (initialement1) 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 notifications en suspens, dont la ntre, comme le montre la Figure31.3.

342

L'art du dveloppement Android 2

Figure31.2 Notre notification apparat dans la barre dtat, avec le message dtat.

Figure31.3 Le tiroir des notifications compltement ouvert, contenant notre notification.

En cliquant sur lentre de la notification dans la barre de notification, on dclenche une activit trs simple se bornant afficher 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 Effacer du tiroir, la balle rouge disparat de la barre dtat.

32
Accs aux services de localisation
Le GPS (Global Positioning System) 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, afin 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 didentifier 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" Wifi, dont les positions gographiques sont connues.

344

L'art du dveloppement Android 2

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 fonctionnalit.

Fournisseurs de localisation: ils savent o vous vous cachez


Les terminaux Android peuvent utiliser plusieurs moyens pour dterminer votre empla cement gographique. Certains ont une meilleure prcision que dautres ; certains vous fourniront ce service gratuitement, tandis que dautres vous le feront payer ; certains peuvent vous donner des informations supplmentaires, comme votre altitude ou votre vitesse courante. Android a donc abstrait tout cela en un ensemble dobjets LocationProvider. Votre environnement 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 Location Provider pour savoir quel est le LocationProvider qui convient votre cas particulier. Votre application devra galement disposer dune permission; sinon les diffrentes API de localisation choueront cause dune violation de scurit. Selon les fournisseurs de localisation que vous voulez utiliser, vous aurez besoin des permissions ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION ou les deux (les permissions sont dcrites au Chapitre28).

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 activit 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.

Chapitre 32

Accs aux servicesde localisation 

345

Si vous choisissez la premire approche, un appel la mthode getProviders() du Location Manager 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 fixer 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 vrifis 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 Java le nom dun LocationProvider (GPS_PROVIDER, par exemple). Lorsque vous connaissez le nom du LocationProvider, vous pouvez appeler getLastKnownPosition() 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. Cette mthode renvoie 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 Location Provider consiste senregistrer pour tre prvenu des modifications de la position du terminal, comme on lexplique dans la section suivante.

346

L'art du dveloppement Android 2

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 "fix 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 lapplication, vous comprendrez pourquoi il est prfrable de signaler que lon veut connatre les modifications de la position et sen servir pour connatre sa position courante. Les applications Weather et WeatherPlus montrent comment tre prvenu de ces mises jour en appelant la mthode requestLocationUpdates() de lobjet LocationManager. Cette mthode prend quatre paramtres:

Le nom du fournisseur de localisation que vous souhaitez utiliser. Le temps, en millisecondes, qui doit scouler avant que lon puisse obtenir une mise jour de la position. Le dplacement minimal du terminal en mtres pour que lon puisse obtenir une mise jour de la position. Un LocationListener qui sera prvenu des vnements lis la localisation.
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 } };

Voici un exemple de LocationListener :

Ici, nous appelons simplement updateForecast() en lui passant lobjet Location fourni lappel de la mthode de rappel onLocationChanged(). Comme on la vu au Chapitre29,

Chapitre 32

Accs aux servicesde localisation 

347

limplmentation dupdateForecast() construit une page web contenant les prvisions mtorologiques pour lemplacement courant et envoie un message de diffusion afin 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. Si lon oublie de le faire, lapplication continuera de recevoir des mises jour de lemplacement, mme aprs que toutes les activits se seront termines, et Android ne pourra pas rcuprer la mmoire occupe par cette application.

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 finale 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 maintenu jusqu ce que vous le supprimiez manuellement via un appel removeProximityAlert(). Le PendingIntent quil faudra lever lorsque le terminal se trouve dans la "zone de tir" dfinie 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: configurer une notification (faire vibrer le terminal, par exemple), enregistrer linformation dans un fournisseur de contenu, poster un message sur un site web, etc.

348

L'art du dveloppement Android 2

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 larecevrez 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 "fix GPS", de trianguler votre position partir des antennes relais ni de dduire votre position partir des signaux Wifi 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 localisation simules une application, ce qui tait trs pratique pour les tests et les dmonstrations mais, malheureusement, cette possibilit a disparu partir dAndroid1.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 Chapitre35.

33
Cartographie avec MapView et MapActivity
Google Maps est lun des services les plus connus de Google aprs le moteur de recherche, 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. La plupart des terminaux Android intgrent Google Maps, ce qui nest gure surprenant. Pour ceux qui en disposent, cette activit de cartographie est directement disponible partir du menu principal. Ce qui nous intresse surtout ici est que 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 utilisateurs de faire dfiler la carte, mais galement utiliser les services de localisation (voir Chapitre 32) pour marquer lemplacement du terminal et indiquer son dplacement. Heureusement, cette intgration est assez simple et vous pouvez exploiter toute sa puissance si vous le souhaitez.

350

L'art du dveloppement Android 2

Termes dutilisation
Pour tre intgr dans des applications tierces, Google Maps ncessite lacceptation dun assez grand nombre de termes juridiques. Parmi ceux-ci se trouvent des clauses que vous trouverez peut-tre insupportables. Si vous dcidez dutiliser Google Maps, prenez soin de bien lire tous ces termes afin 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 OpenStreetMap1.

Empilements
partir dAndroid1.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.

Info

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. lheure actuelle, par exemple, la tablette Android Archos5 ne dispose pas de Google Maps.

Dans lensemble, le fait que Google Maps soit une extension naffectera pas votre dveloppement habituel condition de ne pas oublier les points suivants:

Vous devrez crer votre projet pour quil utilise une cible adapte afin dtre sr que les API de Google Maps soient disponibles. Pour tester lintgration de Google Maps, vous aurez galement besoin dun AVD qui supporte les API de Google Maps.

1.

http://www.openstreetmap.org/.

Chapitre 33

Cartographie avec MapView et MapActivity

351

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 configuration dune activit domine par une MapView. Dans le fichier 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 safficher 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 dfiler.


<?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" /> </RelativeLayout>

Voici, par exemple, le contenu du fichier layout principal de lapplication Maps/NooYawk:

Nous prsenterons ce mystrieux apiKey plus bas dans ce chapitre. Vous devez galement ajouter deux informations supplmentaires votre fichier AndroidManifest.xml:

Les permissions INTERNET et ACCESS_COARSE_LOCATION (cette dernire est utilise par la classe MyLocationOverlay, que nous dcrirons plus loin). 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.

352

L'art du dveloppement Android 2

Voici le fichier 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" android:icon="@drawable/cw"> <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 Map Activity 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 dfiler 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 suffit, 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 dterminer ce quaffiche 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.

Chapitre 33

Cartographie avec MapView et MapActivity

353

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 obtenir. 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): lutilisateur 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 affich 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 emplacement 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 afficher 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);

354

L'art du dveloppement Android 2

} 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 final est la superposition de ces deux couches. Android permet de crer de telles couches, afin 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 ItemizedOverlay, qui vous simplifiera la tche. Pour attacher une couche votre carte, il suffit 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.

Affichage dItemizedOverlay
Comme son nom lindique, ItemizedOverlay permet de fournir une liste de points dintrt (des instances dOverlayItem) pour les afficher sur la carte. La couche gre ensuite lessentiel du dessin pour vous, mais vous devez toutefois effectuer les oprations suivantes:

Chapitre 33

Cartographie avec MapView et MapActivity

355

1. Drivez votre sous-classe (SitesOverlay, Overlay<OverlayItem>. dItemized

dans

notre

exemple)

2. Dans le constructeur, mettez en place la liste des instances OverlayItem et appelez populate() lorsquelles sont prtes tre utilises par la couche. 3. Implmentez size() pour quelle renvoie le nombre dlments qui devront tre grs par la couche. 4. Redfinissez createItem() pour quelle renvoie linstance OverlayItem corres pondant lindice qui lui est pass en paramtre. 5. 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 dans la dernire tape il affiche une pinglette. Vous pouvez galement redfinir 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, afin 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 !"));

356

L'art du dveloppement Android 2

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 lorsque lutilisateur touche la carte afin que la couche ajuste ce quelle affiche. Dans Google Maps, cliquer sur une pinglette fait surgir une bulle dinformation consacre lemplacement marqu, par exemple: grce onTap(), vous pouvez obtenir le mme rsultat avec Android.

Chapitre 33

Cartographie avec MapView et MapActivity

357

La mthode onTap() dItemizedOverlay prend en paramtre lindice de lobjet Overlay Item 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:

laffichage de votre position sur la carte, en fonction du GPS ou dun autre fournisseur de localisation; laffichage de la direction vers laquelle vous vous dirigez, en fonction de la boussole intgre lorsquelle est disponible.

Il vous suffit 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 affiche 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() {

358

L'art du dveloppement Android 2

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 dAndroid1 donne toutes les instructions ncessaires pour produire ces cls, que ce soit pour le dveloppement ou pour la production. Pour rester brefs, nous nous intresserons 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 certificat utilis pour signer vos applications en mode debug (voir ci-aprs). 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 certificat 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 certificat. Vous recevez automatiquement un certificat de dbogage lorsque autosign vous installez le SDK et il faut suivre un autre processus pour crer un certificat
1. http://code.google.com/android/toolbox/apis/mapkey.html.

Chapitre 33

Cartographie avec MapView et MapActivity

359

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 dekeytool. Si vous utilisez OSX ou Linux, faites la commande suivante pour obtenir la signatureMD5 de votre certificat 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: WindowsXP: C:\Documents et Settings\<utilisateur>\.android\debug.keystore. Windows Vistaet Windows 7: C:\Users\<utilisateur>\.android\debug.keystore (o <utilisateur> est le nom de votre compte). La seconde ligne du rsultat qui saffiche contient votre signatureMD5, qui est une suite de paires de chiffres hexadcimaux spares par des caractres deux-points.

34
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 ses 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 sans cesse: au lieu de devoir constamment "synchroniser" ses contacts avec ceux du tlphone, lutilisateur pourrait les appeler directement partir de cette application.

362

L'art du dveloppement Android 2

Vous pourriez crer une interface personnalise pour le systme de contacts existants, ventuellement pour que les utilisateurs mobilit rduite (comme 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 lidentifiant 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 dune 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 lappel mais activera lactivit du combin, partir duquel lutilisateur pourra alors appuyer sur un bouton pour effectuer lappel. Voici, par exemple, un fichier de disposition simple mais efficace, 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"

Chapitre 34

Gestion des appels tlphoniques 

363

> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Numro : " /> <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() {

364

L'art du dveloppement Android 2

public void onClick(View v) { String toDial="tel:" + number.getText().toString(); startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(toDial))); } }); } }

Comme le montre la Figure34.1, linterface de cette activit nest pas trs impressionnante.
Figure34.1 Lapplication DialerDemo lors de son lancement.

Cependant, le combin tlphonique que lon obtient en cliquant sur le bouton "Appeler!" est plus joli, comme le montre la Figure34.2.
Figure34.2 Lactivit Dialer dAndroid lance partir de DialerDemo.

35
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 lextension pour Eclipse, qui intgre le processus de dveloppement Android dans cet IDE 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 o se cache un widget particulier qui napparat pas lcran. Pour utiliser cet outil, vous devez dabord lancer lmulateur, installer votre application, lancer lactivit et naviguer vers lendroit que vous souhaitez examiner. Notez que vous ne pouvez pas utiliser ce visualisateur avec un vrai terminal Android, comme le G1. Comme le

366

L'art du dveloppement Android 2

montre la Figure35.1, nous utiliserons titre dexemple lapplication ReadWriteFileDemo que nous avions prsente au Chapitre23. 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 Figure35.2.
Figure35.1 Lapplication ReadWriteFileDemo.

Figure35.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 le bouton Start Server et sur un mulateur, la liste des fentres accessibles apparat droite, comme le montre la Figure35.3. Vous remarquerez quoutre lactivit ouverte apparaissent de nombreuses autres fentres, dont celle du lanceur (lcran daccueil), celle du "Keyguard" (lcran noir Appuyez sur

Chapitre 35

Outils de dveloppement

367

Menu pour dverrouiller le tlphone qui apparat lorsque vous ouvrez lmulateur pour la premire fois), etc. Votre activit est identifie par le nom du paquetage et de la classe de lapplication (com.commonsware.android.readwrite/..., ici).
Figure35.3 Hirarchie des fentres disponibles.

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 Figure35.4).
Figure35.4 Layout View de lapplication ReadWriteFileDemo.

368

L'art du dveloppement Android 2

La zone principale de cette Layout View est occupe par une arborescence des diffrentes 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 Figure35.5.
Figure35.5 Affichage 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 modifiables. 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 vrifier que vous avez slectionn le bon widget lorsque, par exemple, il y a plusieurs boutons et que la reprsentation de larborescence ne permet pas de les distinguer.

Chapitre 35

Outils de dveloppement

369

Si vous double-cliquez sur une vue de larborescence, un panneau apparat pour ne vous montrer que cette vue (et ses fils), 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 reprsente une grille, le visualisateur affiche une autre reprsentation, appele "Pixel Perfect View" (voir Figure35.6).
Figure35.6 Visualisateur hirarchique en mode "Pixel Perfect View".

La partie gauche contient une reprsentation 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. 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 refltera donc dans les vues "Normal" et "Loupe" de la fentre "Pixel Perfect View". Les lignes fines de couleur cyan places au-dessus de lactivit montrent la position sur laquelle le zoom sapplique il suffit de cliquer sur une nouvelle zone pour changer lendroit inspect par la "Loupe View". Un autre curseur permet de rgler la puissance du grossissement.

370

L'art du dveloppement Android 2

DDMS (Dalvik Debug Monitor Service)


Lautre outil de larsenal du dveloppeur Android sappelle DDMS (Dalvik Debug Monitor Service). Cest une sorte de "couteau suisse" qui vous permet de parcourir les fichiers journaux, de modifier 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 fichiers. 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 arborescence des mulateurs avec les programmes quils excutent (voir Figure35.7).
Figure35.7 Vue initiale de DDMS.

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 Figure35.8).
Figure35.8 mulateur slectionn dans DDMS.

Chapitre 35

Outils de dveloppement

371

Journaux
la diffrence dadb logcat, DDMS vous permet dexaminer le contenu du journal dans un tableau dot dune barre de dfilement. Il suffit de cliquer sur lmulateur ou le terminal que vous voulez surveiller pour que le bas de la fentre affiche le contenu du journal. En outre, vous pouvez effectuer les oprations suivantes:

Filtrer les entres du journal selon lun des cinq niveaux reprsents par les boutons V E dans la barre doutils. Crer un filtre 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 filtre sera utilis pour nommer un autre onglet qui apparatra ct du contenu du j ournal (voir Figure35.9). Sauvegarder les entres du journal dans un fichier texte, afin de pouvoir les rutiliser plus tard.

Figure35.9 Filtrage des entres du journal avec DDMS.

Stockage et extraction de fichiers


Bien que vous puissiez utiliser adb pull et adb push pour, respectivement, extraire ou stocker des fichiers sur un mulateur ou un terminal, DDMS permet de le faire de faon plus visuelle. Il suffit, 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 Figure35.10, permet alors de parcourir larborescence des fichiers.

372

L'art du dveloppement Android 2

Figure35.10 Explorateur de fichiers de DDMS.

Slectionnez simplement le fichier 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 fichier slectionn.

Lexplorateur de fichiers a toutefois quelques limites :Il 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 fichiers 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, cliquez sur 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 Figure35.11.

Chapitre 35

Outils de dveloppement

373

Figure35.11 Capture dcran avec DDMS.

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.

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 modifier. 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 Figure35.12).
Figure35.12 Contrle de la position avec DDMS.

374

L'art du dveloppement Android 2

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. Les autres onglets permettent de prciser des emplacements aux formats GPX (GPS eXchange) ou KML (Keyhole Markup Language).

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 Figure35.13).
Figure35.13 Contrle de la tlphonie avec DDMS.

Pour simuler un appel tlphonique, saisissez un numro, cochez le bouton radio Voice puis cliquez sur le bouton Call. Comme le montre la Figure35.14, lmulateur affichera lappel entrant et vous demandera si vous lacceptez (avec le bouton vert du tlphone) ou si vous le rejetez (avec le bouton rouge).
Figure35.14 Simulation de la rception dun appel.

Chapitre 35

Outils de dveloppement

375

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 notification, comme la Figure35.15.
Figure35.15 Simulation de la rception dun SMS.

En cliquant sur la notification, vous pourrez voir le contenu intgral du message, comme le montre la Figure35.16.
Figure35.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 "CarteSD".

376

L'art du dveloppement Android 2

Les dveloppeurs sont fortement encourags utiliser les cartes SD pour y stocker les gros fichiers, comme les images, les clips vido, les fichiers musicaux, etc. La mmoire interne duG1, notamment, est relativement peu importante et il est donc prfrable de stocker un maximum de donnes sur une carteSD. 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 leG1, vous devez donc crer et "insrer" une carteSD dans lmulateur.

Cration dune image de carte


Au lieu dexiger que les mulateurs aient accs un vrai lecteur de carteSD pour utiliser de vraies cartes, Android est configur pour utiliser des images de cartes. Une image est simplement un fichier que lmulateur traitera comme sil sagissait dun volume de carteSD: il sagit en fait du mme concept que celui utilis par les outils de virtualisation (comme Virtual Box) Android utilise une image disque pour reprsenter le contenu dune carteSD. 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:

La taille de limage et donc de la "carte". Si vous fournissez simplement 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 kilooctets ou en mgaoctets. Le nom du fichier dans lequel stocker limage.

Pour, par exemple, crer limage dune carte SD de 1Go afin de simuler celle duGI dans lmulateur, utilisez la commande suivante:
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 fichier 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 fichiers dans /sdcard, il suffit ensuite dutiliser lexplorateur de fichiers de DDMS ou les commandes adb push et adb pull partir de la console. Les manipulations de la SD Card (cration et insertion) seront simplifies par lutilisation dADT en compltant le champs de saisie nomm SD Card Size lors de la cration de lAVD.

36
Gestion des diffrentes tailles dcran
Les crans des terminaux Android, qui sont apparus sur le march lanne qui a suivi lapparition dAndroid1.0, avaient tous la mme rsolution (HVGA, 320 480) et la mme taille (environ 3,5pouces, soit 9cm). partir de lautomne 2009, cependant, des terminaux disposant dcrans de tailles et de rsolutions trs diffrentes ont vu le jour, du minuscule QVGA (240320) au WVGA (480800), bien plus grand. Les utilisateurs sattendent bien sr ce que les applications fonctionnent sur tous ces types dcrans et tirent ventuellement parti des crans plus larges. Android1.6 a donc ajout un meilleur support permettant aux dveloppeurs de mieux grer ces diffrentes tailles et rsolutions. La documentation dAndroid explique en dtail les techniques de gestion des crans (http://d.android.com/guide/practices/screens_support.html) : nous vous conseillons de la lire en mme temps que ce chapitre pour comprendre au mieux ce que vous devrez grer et pour tirer le meilleur parti possible des diffrentes tailles dcrans.

378

L'art du dveloppement Android 2

Aprs un certain nombre de sections prsentant les options et la thorie, ce chapitre prsentera une application simple mais sachant sadapter aux diffrents crans existants.

cran par dfaut


Commenons par ignorer totalement cette problmatique des tailles et des rsolutions dcrans. Si votre application a t compile pour Android1.5 ou une version antrieure, Android supposera quelle a t conue pour safficher correctement avec une taille et une rsolution dcran classiques. Si vous linstallez sur un terminal dot dun cran plus grand, Android lexcutera automatiquement en mode compatible, consistant adapter son chelle la taille relle de lcran du terminal. Supposons, par exemple, que vous utilisiez un fichier PNG de 2424 pixels et que votre application sexcute sur un terminal ayant un cran de taille normale, mais avec une rsolution WVGA (cest ce que lon appelle un cran haute densit). Android modifiera la taille du fichier PNG pour quil fasse 36 36 pixels afin doccuper toute la surface visible de lcran. Lavantage est que ce traitement est automatique; linconvnient est que les algorithmes de changement dchelle ont tendance produire des images moins nettes. En outre, Android empchera votre application de sexcuter sur des terminaux ayant des crans plus petits les terminaux QVGA comme le HTC Tattoo, par exemple bien quelle soit disponible sur lAndroid Market. Si lapplication a t compile pour Android 1.6 ou une version suprieure, par contre, Android supposera quelle sait correctement grer toutes les tailles dcran et ne lex cutera donc pas en mode compatible. Nous verrons plus loin comment configurer tout cela.

Tout en un
Lapproche la plus simple pour grer les tailles dcrans multiples avec Android consiste concevoir linterface utilisateur pour quelle sadapte automatiquement la taille de lcran, sans indiquer de taille prcise dans le code Java ou les ressources en dautres termes, faire en sorte que "a marche". Ceci implique, toutefois, que tout ce quutilise linterface utilisateur soit bien redimensionn par Android et que tout se place correctement, mme sur un cran QVGA. Les sections qui suivent donnent quelques conseils pour obtenir ce rsultat.

Chapitre 36

Gestion des diffrentes tailles dcran

379

Penser en termes de rgles, pas en termes de positions


Certains dveloppeurs, notamment ceux qui ont t forms lcole du dveloppement des interfaces (drag and drop notamment), pensent dabord et avant tout la position des widgets. Ils veulent des widgets prcis avec des tailles fixes et des positions bien dtermines. Les gestionnaires de placement (conteneurs) dAndroid les ennuient et ils utilisent donc parfois le conteneur obsolte AbsoluteLayout pour concevoir leurs interfaces comme ils en avaient lhabitude. Cette approche fonctionne rarement bien, mme sur les ordinateurs de bureau, comme on peut le constater avec certaines applications qui ont du mal grer le changement de taille des fentres. De la mme faon, elle ne fonctionne pas sur les terminaux mobiles, qui proposent dsormais un large ventail de tailles et de rsolutions dcran. Au lieu de penser en termes de positions, vous devez rflchir en termes de rgles. Vous devez expliquer Android les rgles mtier concernant les tailles et les positions de vos widgets et le laisser interprter ces rgles en fonction de la rsolution de lcran du terminal sur laquelle votre application est dploye. Les rgles les plus simples consistent utiliser les valeurs fill_parent et wrap_content pour les attributs android:layout_width et android:layout_height car elles ne prcisent aucune taille spcifique et sadaptent lespace disponible.
RelativeLayout (voir Chapitre 6) est lenvironnement qui permet de tirer le meilleur parti de ces rgles. Bien quil semble compliqu au premier abord, RelativeLayout permet de contrler le positionnement des widgets tout en ladaptant aux diffrentes tailles dcran. Vous pouvez donc, par exemple:

Positionner explicitement les widgets en bas ou droite de lcran plutt quesprer quils se placeront l par le bon vouloir dun autre layout. Contrler les distances entre les widgets associs (le label dun champ devrait tre sa gauche, par exemple), sans jouer sur le remplissage ou les marges.

Pour avoir le maximum de contrle sur les rgles, lidal serait de crer votre propre classe de layout. Si, par exemple, vous dveloppez une suite dapplications de jeux de cartes, il serait prfrable de disposer dune classe spcialise dans le placement des cartes la faon quelles ont de se recouvrir, le placement des cartes retournes ou non, la faon de prendre plusieurs cartes la fois, etc. Mme si vous pourriez obtenir laspect recherch avec un RelativeLayout, par exemple, vous auriez intrt implmenter une classe Playing CardLayout spcialement conue pour ce genre dapplications. Malheureusement, il nexiste pas toujours de documentation concernant la cration de ces layouts personnaliss.

380

L'art du dveloppement Android 2

Utilisez des dimensions physiques


Android permet dexprimer les dimensions dans un grand nombre dunits. La plus utilise tait le pixel (px) car on se le reprsente facilement mentalement: les dimensions des crans Android sont dailleurs exprimes en nombre de pixels en largeur et en hauteur. Cependant, les pixels commencent poser problme lorsque la densit des crans change. En effet, la taille des pixels diminue mesure que le nombre de pixels augmente pour une taille dcran donne: une icne de 32pixels sur un terminal Android classique peut convenir aux touchs alors que, sur un cran haute densit (un tlphone mobile WVGA, par exemple), elle peut tre trop petite pour tre manipulable au doigt. Si vous avez exprim en pixels la taille dun widget comme un bouton, utilisez plutt des millimtres (mm) ou des pouces (in) comme unit de mesure: 10millimtres feront toujours 10millimtres, quelle que soit la rsolution et la taille de lcran. Vous pouvez ainsi garantir que ce widget conviendra toujours une manipulation tactile, quel que soit le nombre de pixels quil occupera.

vitez les vrais pixels


Dans les cas o exprimer une dimension en millimtres naurait aucun sens, vous pouvez quand mme utiliser dautres units de mesure et viter les vrais pixels. Android permet dexprimer les dimensions en pixels indpendants de la densit (dip, pour density-independent pixel). Ces pixels correspondent exactement aux vrais pixels sur un cran de 160dpi (celui dun terminal HVGA classique, par exemple) et sadaptent ensuite en consquence. Sur un cran de 240dpi (celui dun tlphone WVGA, par exemple), un dip correspond 1,5px: 50dip correspondent donc 50px 160dpi et 75px 240dpi. Lavantage des dip est donc que la taille relle de la dimension reste la mme et quil ny a aucune diffrence visuelle entre 50dip 160dpi et 50dip 250dpi. Android autorise galement les scaled pixels (sp), dont lchelle dpend, en thorie, de la taille de la police choisie (la valeur FONT_SCALE dans System.Settings).

Choisir des images adaptables


La taille des images bitmaps PNG, JPG et GIF ne sadapte pas naturellement. Si vous nutilisez pas le mode compatible, Android nessaiera donc mme pas de les adapter la rsolution et la taille de lcran. La taille initiale de votre image sera celle utilise pour son affichage, mme si elle est trop petite ou trop grande pour certains crans. Un moyen de rgler ce problme consiste viter les images bitmaps statiques et les remplacer par des bitmaps nine-patch et des images dfinies en XML (avec GradientDrawable, par exemple). Une image bitmap nine-patch est un fichier PNG encod avec des

Chapitre 36

Gestion des diffrentes tailles dcran

381

rgles indiquant comment limage peut sagrandir pour occuper plus despace. Les images dfinies en XML utilisent, quant elles, un langage ressemblant SVG pour dfinir leurs formes, le trac de leurs traits, leurs motifs de remplissage, etc.

Fait maison, rien que pour vous...


Lorsque vous avez besoin de comportements et daspects diffrents en fonction des tailles ou des densits dcran, Android vous permet de choisir des ressources ou des blocs de code selon lenvironnement dans lequel sexcute votre application. Utilis avec les techniques prsentes dans la section prcdente, ceci permet dobtenir une indpendance vis--vis de la taille et de la densit des crans, en tout cas pour les terminaux qui utilisent Android1.6 ou une version plus rcente.

<supports-screens>
La premire tape pour grer activement les tailles dcrans consiste ajouter un lment <supports-screens> votre fichier AndroidManifest.xml. Cet lment permet dindiquer explicitement les tailles dcran que vous grez et celles que vous ne grez pas. Celles qui ne sont pas explicitement gres seront prises en charge par le mode compatible automatique que nous avons dcrit plus haut. Voici un manifeste contenant un lment <supports-screens> :
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.eu4you" android:versionCode="1" android:versionName="1.0"> <supports-screens android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" android:anyDensity="true" /> <application android:label="@string/app_name" android:icon="@drawable/cw"> <activity android:name=".EU4You" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" />

382

L'art du dveloppement Android 2

<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

Les attributs android:smallScreens, android:normalScreens et android:largeScreens prennent une valeur boolenne indiquant si votre application gre explicitement ces crans (true) ou ncessite lassistance du mode compatible (false). Lattribut android:anyDensity prcise que vous tenez compte de la densit dans vos calculs (true) ou non (false). Dans ce dernier cas, Android se comportera comme si toutes vos dimensions (4px, par exemple) taient relatives un cran de densit normale (160dpi). Si lapplication sexcute sur un terminal avec une densit plus faible ou plus leve, Android adaptera en consquence vos dimensions. Si cet attribut vaut true, Android vous laissera la responsabilit dutiliser des units indpendantes de la densit, comme dip, mm ou in.

Ressources et ensembles de ressources


Le principal moyen de choisir les ressources en fonction de la taille ou de la densit de lcran consiste crer des ensembles de ressources spcifiques aux diffrentes caractristiques dcran. Vous expliquez ainsi Android comment grer chacune de ces ressources et il choisira automatiquement lensemble qui convient.

Taille par dfaut


Par dfaut, Android adaptera toutes les ressources drawable. Celles qui peuvent ltre naturellement seront donc correctement redimensionnes. En revanche, la taille des images bitmaps traditionnelles sera recalcule laide dun algorithme classique, ce qui peut, en fonction des situations, donner ou non de bons rsultats; par ailleurs, ce calcul peut galement ralentir un peu lapplication. Pour viter cela, mettez en place des ensembles de ressources distincts contenant des images bitmaps non adaptables.

Ensembles en fonction de la densit


Si vous souhaitez disposer de layouts ou de dimensions diffrentes ou tout ce qui dpend de la densit de lcran , utilisez les labels -ldpi, -mdpi et -hdpi pour les ensembles de ressources. Le fichier res/values-hdpi/dimens.xml, par exemple, contiendra les dimensions utilises pour les crans de haute densit.

Chapitre 36

Gestion des diffrentes tailles dcran

383

Ensembles en fonction de la taille


De mme, pour les ensembles de ressources dpendant de la taille de lcran, Android utilise les labels -small, -normal et -large. Le rpertoire res/layout-large-land/ contiendra donc les layouts pour les grands crans (WVGA, par exemple) en mode paysage.

Ensembles en fonction de la version


Les anciennes versions dAndroid peuvent parfois tre perturbes par ces nouveaux labels densembles de ressources. Vous pouvez alors ajouter un label de version sous la forme -vN, o N est le niveau de lAPI. Ainsi, res/drawable-large-v4/ indique que ces drawables seront utiliss sur de grands crans pour lAPI de niveau4 (Android1.6) ou une version plus rcente. Android sachant filtrer les versions depuis un certain temps, cette technique fonctionne depuis Android1.5 (voire ventuellement avec les versions antrieures). Par consquent, si vous constatez que les mulateurs ou les terminaux Android1.5 choisissent le mauvais ensemble de ressources, ajoutez -v4 aux noms de ces ensembles afin de les filtrer.

Trouver sa taille
Voici quelques possibilits permettant votre code Java dagir diffremment en fonction de la taille ou de la densit de lcran du terminal sur lequel il sexcute. Si vos ensembles de ressources sont diffrentiables, exploitez cette diffrence pour les utiliser de faon approprie dans votre code. Comme nous le verrons plus loin dans ce chapitre, certains layouts peuvent en effet avoir des widgets supplmentaires (res/layout-large/ main.xml, par exemple): il suffit alors de vrifier que lun de ces widgets est prsent pour savoir que vous vous excutez sur un cran large. Vous pouvez galement connatre la classe de votre taille dcran en passant par un objet
Configuration que vous obtiendrez gnralement par un appel getResources().getConfi guration() sur lactivit. Cet objet contient un champ public screenLayout qui est un

masque indiquant le type dcran sur lequel sexcute lapplication : vous pouvez donc tester si lcran est petit, normal ou grand, ou sil est long (cest--dire au format 16:9 et non 4:3). Voici, par exemple, comment tester que lon sexcute sur un grand cran:
if (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_LARGE) == Configuration.SCREENLAYOUT_SIZE_LARGE) { // Cest un grand cran } else { // Ce nest pas un grand cran }

384

L'art du dveloppement Android 2

En revanche, on ne peut pas obtenir la densit de lcran avec une technique quivalente. Si vous devez absolument la connatre, une astuce consiste crer des rpertoires res/ values-ldpi/, res/values-mdpi/ et res/values-hdpi/ dans votre projet en leur ajoutant un fichier strings.xml chacun. Placez ensuite une ressource textuelle de mme nom dans chaque strings.xml, mais avec une valeur diffrente (appelez-la par exemple density et donnez-lui, respectivement, les valeurs ldpi, mdpi et hdpi). Vous pouvez ensuite tester la valeur de cette ressource lors de lexcution. Ce nest pas trs lgant, mais cela devrait fonctionner.

Rien ne vaut la ralit


Les mulateurs Android vous aideront tester votre application sur des crans de diffrentes tailles. Cependant, ceci ne vous emmnera pas bien loin car les crans LCD des terminaux portables nont pas les mmes caractristiques que les crans des ordinateurs portables ou de bureau:

Les crans LCD des mobiles ont une densit bien suprieure lcran de votre machine de dveloppement. Une souris permet deffectuer un touch cran bien plus prcis que ne le permet un doigt.

Lorsque cela est possible, vous devez donc soit utiliser lmulateur de faon originale, soit utiliser un vritable terminal avec dautres rsolutions dcrans.

Diffrences de densit
Le DROID de Motorola a un cran de 240 dpi, de 3,7 pouces et de 480 854 pixels (affichage FWVGA). Pour lmuler en tenant compte du nombre de pixels, vous devez utiliser un tiers dun moniteur LCD de 19pouces et de 12801024pixels car la densit du moniteur est bien plus faible que celle de lcran du DROID environ 96dpi. Lorsque vous lancerez lmulateur Android pour un affichage FWVGA comme celui du DROID, vous obtiendrez donc une fentre dmulation norme. Ceci est tout fait valable pour dterminer laspect gnral de votre application dans un environnement FWVGA. Quelle que soit la densit, les widgets saligneront de la mme faon, les tailles auront les mmes rapports (un widget A sera deux fois plus grand quun widget B, quelle que soit la densit, par exemple), etc. Cependant, les problmes suivants peuvent se poser :

Ce qui semblait tre dune taille correcte sur un cran de 19 pouces peut se rvler vraiment trop petit sur lcran dun mobile avec la mme rsolution.

Chapitre 36

Gestion des diffrentes tailles dcran

385

Ce que vous pouviez aisment cliquer avec une souris dans lmulateur peut tre impossible slectionner avec le doigt sur un cran plus petit et plus dense.

Ajustement de la densit
Par dfaut, lmulateur conserve le nombre prcis de pixels au dtriment de la densit cest la raison pour laquelle sa fentre est si grande mais vous pouvez galement choisir de conserver la densit au dtriment du nombre de pixels. Pour cela, le moyen le plus simple consiste utiliser le gestionnaire dAVD introduit par Android1.6. Comme le montre la Figure36.1, la version de cet outil fournie avec le SDK dAndroid2.0 dispose dune bote de dialogue Launch Options qui souvre lorsque vous lancez une instance de lmulateur en cliquant sur le bouton Start.
Figure 36.1 La bote de dialogue Launch Options.

Par dfaut, la case Scale display to real size nest pas coche et Android ouvrira la fentre de lmulateur normalement, mais vous pouvez la cocher afin de fournir deux informations sur la taille de lcran:

La taille de lcran du terminal que vous souhaitez muler, en pouces (3,7 pour le DROID de Motorola, par exemple). La rsolution en points par pouces (dpi) de votre moniteur (cliquez sur le bouton ? pour afficher une calculatrice permettant de dterminer cette valeur).

Vous obtiendrez ainsi une fentre dmulation qui reprsente plus fidlement laspect quaura votre interface graphique sur un terminal physique, au moins en termes de taille. Cependant, lmulateur utilisant bien moins de pixels quun vrai terminal, les polices seront peut-tre difficiles lire, les images seront pixellises, etc.

386

L'art du dveloppement Android 2

Accs aux vritables terminaux


Le meilleur moyen possible de voir quoi ressemblera votre application sur diffrents terminaux consiste, videmment, la tester sur ces terminaux. Vous navez pas ncessairement besoin de disposer de tous les modles Android existants, mais uniquement de ceux ayant des caractristiques matrielles distinctives, susceptibles davoir un impact sur lapplication et la taille des crans influe peu prs sur tout. Voici quelques suggestions:

Testez virtuellement les terminaux en utilisant des services comme DeviceAnywhere (http://www.deviceanywhere.com/). Bien que ce soit une version amliore de lmulateur, elle nest pas gratuite et ne permet pas de tout tester non plus (les changements demplacements, par exemple). Vous pouvez acheter des terminaux doccasion sur Ebay, par exemple. Les GSM "dsimlocks" peuvent partager une carte SIM lorsque vous voulez tester des oprations de tlphonie ou fonctionner sans carte SIM. Si vous vivez en ville, vous pouvez crer un groupe dutilisateurs et lutiliser pour tester les applications sur les diffrents terminaux des membres de ce groupe. Servez-vous des retours des utilisateurs : produisez une version bta gratuite de votre application et laissez les impressions des utilisateurs guider vos modifications. Vous pouvez distribuer ces versions en dehors de lAndroid Market pour viter que de mauvais retours sur les versions bta fassent chuter le niveau dapprciation de votre application.

Exploitez sans vergogne la situation


Pour linstant, nous nous sommes attachs faire en sorte que les layouts apparaissent correctement sur dautres tailles dcran. Pour les crans plus petits que la moyenne (QVGA, par exemple), cest probablement le mieux que lon puisse faire. Ds que lon passe des crans plus grands, toutefois, une autre possibilit apparat : lutilisation de layouts diffrents pour tirer parti de cet espace supplmentaire. Ceci est tout particulirement utile lorsque la taille physique de lcran est plus grande (un LCD de 5pouces comme celui de la tablette Android Archos5, par exemple) car on prfrera alors modifier la structure du layout pour profiter de cet espace. Les sections qui suivent dcrivent plusieurs moyens de profiter de cet espace supplmentaire.

Remplacer les menus par des boutons


Le choix dun lment dans un menu doptions ncessite deux actions : presser le bouton du menu, puis toucher loption choisie. Le choix dun lment dans un menu contextuel

Chapitre 36

Gestion des diffrentes tailles dcran

387

demande aussi deux actions: un touch prolong sur le widget, suivi dun touch de loption choisie. En outre, les menus contextuels ont un problme supplmentaire d au fait quils sont invisibles: les utilisateurs, par exemple, peuvent ne pas se rendre compte que votre ListView dispose dun menu contextuel. Vous pourriez amliorer votre interface en fournissant un accs direct aux oprations qui sont ordinairement caches dans un menu: non seulement cela diminue le nombre dtapes ncessaires pour effectuer les actions, mais cela rend galement ces actions plus visibles. Supposons, par exemple, que vous dveloppiez un lecteur musical et que vous souhaitiez offrir la possibilit de grer manuellement des listes de morceaux. Une activit affiche les morceaux dune liste dans une ListView et un menu doptions dispose dun choix Ajouter permettant dajouter un nouveau morceau la liste. Le menu contextuel de la liste dispose des options Supprimer, Dplacer vers le haut et Dplacer vers le bas pour rordonner les morceaux dans la liste. Sur un grand cran, vous pourriez ajouter quatre widgets ImageButton pour ces quatre options, les trois du menu contextuel ntant actifs que lorsquune ligne a t slectionne au moyen du pad ou du trackball. Sur des crans plus petits, vous vous contenteriez des menus.

Remplacer les onglets par une seule activit


Votre interface utilise peut-tre un TabHost pour afficher plus de widgets dans lespace disponible lcran. Tant que lespace que vous conomisez en plaant les widgets dans un onglet spar est suprieur celui occup par les onglets eux-mmes, vous tes gagnant. Cependant, utiliser plusieurs onglets implique galement plus dactions de lutilisateur pour naviguer dans votre interface, notamment sil doit passer rgulirement dun onglet lautre. Si vous nutilisez que deux onglets, vous pouvez offrir plus despace votre interface en les supprimant et en plaant tous les widgets sur un seul cran: tout sera sous les yeux de lutilisateur, qui naura plus besoin de naviguer dans les onglets. Si vous avez plus de deux onglets, vous naurez srement pas assez despace pour placer tout leur contenu dans la mme activit. Cependant, vous pouvez couper la poire en deux: faire en sorte que les widgets les plus utiliss soient toujours dans lactivit et laisser le TabHost grer les autres sur lautre moiti de lcran.

Consolider les activits multiples


La technique la plus puissante consiste profiter dun cran plus grand pour se dbarrasser des transitions entre les activits. Si, par exemple, vous utilisez une ListActivity dans laquelle un clic sur un lment place les dtails de celui-ci dans une activit spare, utilisez un layout o les dtails seront dans la mme activit que la ListView (la ListView sur

388

L'art du dveloppement Android 2

la gauche et les dtails sur la droite dans un layout en mode paysage, par exemple). Ceci vitera lutilisateur de devoir sans cesse presser le bouton de retour en arrire pour quitter un ensemble de dtails afin den voir un autre. Nous utiliserons cette technique dans lexemple prsent dans la section suivante.

Exemple: EU4You
Pour illustrer lutilisation de quelques-unes des techniques que nous venons de prsenter, nous examinerons lapplication ScreenSizes/EU4You, qui na quune seule activit (EU4You) contenant une ListView proposant une liste des membres de lUnion europenne (UE) avec leurs drapeaux nationaux (http://www.wpclipart.com/flags/Countries/index. html). Un clic sur un pays affiche la page Wikipedia qui lui est consacre. Nous prsenterons quatre versions de cette application en commenant par celle qui ne tient pas compte de la taille de lcran et, petit petit, nous lui ajouterons des fonctionnalits permettant de mieux ladapter aux crans des terminaux sur lesquels elle sexcute.

Premire version
Voici le fichier AndroidManifest.xml, qui ressemble celui que nous avons prsent plus haut dans ce chapitre:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.eu4you" android:versionName="1.0"> <supports-screens android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" android:anyDensity="true" /> <application android:label="@string/app_name" android:icon="@drawable/cw"> <activity android:name=".EU4You" 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>

Chapitre 36

Gestion des diffrentes tailles dcran

389

Notez que nous utilisons llment <supports-screens> pour indiquer que nous supportons toutes les tailles dcran. Ceci permet dempcher le changement de taille automatique quAndroid effectuerait si nous annoncions que nous ne supportons pas certaines tailles. Le fichier layout principal ne tient pas compte de la taille de lcran car il dfinit simplement une ListView plein cran:
<?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" />

Une ligne, par contre, ncessite un peu de configuration:


<?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:padding="2dip" android:minHeight="?android:attr/listPreferredItemHeight"> <ImageView android:id="@+id/flag" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|left" android:paddingRight="4px" /> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|right" android:textSize="20px" /> </LinearLayout>

Pour linstant, nous utilisons donc une police de 20px, qui ne variera pas en fonction de la taille ou de la densit de lcran. Notre activit EU4You est un peu longue, mais cest essentiellement parce quil y a beaucoup de pays dans lUE et que nous voulons afficher le drapeau et le texte sur une ligne:
package com.commonsware.android.eu4you; import android.app.ListActivity; import android.content.Intent;

390

L'art du dveloppement Android 2

import android.net.Uri; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; public class EU4You extends ListActivity { static private ArrayList<Country> EU=new ArrayList<Country>(); static { EU.add(new Country(R.string.austria, R.drawable.austria, R.string.austria_url)); EU.add(new Country(R.string.belgium, R.drawable.belgium, R.string.belgium_url)); EU.add(new Country(R.string.bulgaria, R.drawable.bulgaria, R.string.bulgaria_url)); EU.add(new Country(R.string.cyprus, R.drawable.cyprus, R.string.cyprus_url)); EU.add(new Country(R.string.czech_republic, R.drawable.czech_republic, R.string.czech_republic_url)); EU.add(new Country(R.string.denmark, R.drawable.denmark, R.string.denmark_url)); EU.add(new Country(R.string.estonia, R.drawable.estonia, R.string.estonia_url)); EU.add(new Country(R.string.finland, R.drawable.finland, R.string.finland_url)); EU.add(new Country(R.string.france, R.drawable.france, R.string.france_url)); EU.add(new Country(R.string.germany, R.drawable.germany, R.string.germany_url)); EU.add(new Country(R.string.greece, R.drawable.greece, R.string.greece_url)); EU.add(new Country(R.string.hungary, R.drawable.hungary, R.string.hungary_url));

Chapitre 36

Gestion des diffrentes tailles dcran

391

EU.add(new Country(R.string.ireland, R.drawable.ireland, R.string.ireland_url)); EU.add(new Country(R.string.italy, R.drawable.italy, R.string.italy_url)); EU.add(new Country(R.string.latvia, R.drawable.latvia, R.string.latvia_url)); EU.add(new Country(R.string.lithuania, R.drawable.lithuania, R.string.lithuania_url)); EU.add(new Country(R.string.luxembourg, R.drawable.luxembourg, R.string.luxembourg_url)); EU.add(new Country(R.string.malta, R.drawable.malta, R.string.malta_url)); EU.add(new Country(R.string.netherlands, R.drawable.netherlands, R.string.netherlands_url)); EU.add(new Country(R.string.poland, R.drawable.poland, R.string.poland_url)); EU.add(new Country(R.string.portugal, R.drawable.portugal, R.string.portugal_url)); EU.add(new Country(R.string.romania, R.drawable.romania, R.string.romania_url)); EU.add(new Country(R.string.slovakia, R.drawable.slovakia, R.string.slovakia_url)); EU.add(new Country(R.string.slovenia, R.drawable.slovenia, R.string.slovenia_url)); EU.add(new Country(R.string.spain, R.drawable.spain, R.string.spain_url)); EU.add(new Country(R.string.sweden, R.drawable.sweden, R.string.sweden_url)); EU.add(new Country(R.string.united_kingdom, R.drawable.united_kingdom, R.string.united_kingdom_url)); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); setListAdapter(new CountryAdapter()); }

392

L'art du dveloppement Android 2

@Override protected void onListItemClick(ListView l, View v, int position, long id) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(EU.get(position).url)))); } static class Country { int name; int flag; int url; Country(int name, int flag, int url) { this.name=name; this.flag=flag; this.url=url; } } class CountryAdapter extends ArrayAdapter<Country> { CountryAdapter() { super(EU4You.this, R.layout.row, R.id.name, EU); } @Override public View getView(int position, View convertView, ViewGroup parent) { CountryWrapper wrapper=null; if (convertView==null) { convertView=getLayoutInflater().inflate(R.layout.row, null); wrapper=new CountryWrapper(convertView); convertView.setTag(wrapper); } else { wrapper=(CountryWrapper)convertView.getTag(); }

Chapitre 36

Gestion des diffrentes tailles dcran

393

wrapper.populateFrom(getItem(position)); return(convertView); } } class CountryWrapper { private TextView name=null; private ImageView flag=null; private View row=null; CountryWrapper(View row) { this.row=row; } TextView getName() { if (name==null) { name=(TextView)row.findViewById(R.id.name); } return(name); } ImageView getFlag() { if (flag==null) { flag=(ImageView)row.findViewById(R.id.flag); } return(flag); } void populateFrom(Country nation) { getName().setText(nation.name); getFlag().setImageResource(nation.flag); } } }

Les Figures36.2, 36.3 et 36.4 montrent laspect de cette activit sur un mulateur HVGA classique, un mulateur WVGA et un mulateur QVGA.

394

L'art du dveloppement Android 2

Figure36.2 EU4You, version initiale, cran HVGA.

Figure36.3 EU4You, version initiale, cran WVGA (800400pixels).

Chapitre 36

Gestion des diffrentes tailles dcran

395

Figure36.4 EU4You, version initiale, cran QVGA.

Corriger les polices


Le premier problme corriger est celui de la taille de la police. Comme vous pouvez le constater, avec une taille fixe de 20pixels les fontes vont de trs grande minuscule selon la taille et la densit de lcran. Sur un cran WVGA, le texte peut mme tre assez difficile lire. Nous pourrions considrer la dimension de la police comme une ressource res/values/ dimens.xml et utiliser diffrentes versions de celle-ci en fonction de la taille ou de la densit de lcran, mais il est plus simple de prciser une taille indpendante de la densit 5mm, par exemple. Cest ce que nous avons fait dans le projet ScreenSizes/EU4You_2:
<?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:padding="2dip" android:minHeight="?android:attr/listPreferredItemHeight"> <ImageView android:id="@+id/flag" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|left" android:paddingRight="4px" /> <TextView android:id="@+id/name"

396

L'art du dveloppement Android 2

android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|right" android:textSize="5mm" /> </LinearLayout>

Figure 36.5 EU4You, police de 5mm, cran HVGA.

Figure 36.6 EU4You, police de 5mm, cran WVGA (800400pixels).

Chapitre 36

Gestion des diffrentes tailles dcran

397

Figure 36.7 EU4You, police de 5mm, cran QVGA.

Comme le montrent les Figures36.5, 36.6 et 36.7, la police a dsormais toujours la mme taille et elle est suffisamment grande pour saccorder avec les drapeaux.

Corriger les icnes


Comment faire pour les icnes ? Leur taille devrait varier galement or ce sont les mmes pour les trois mulateurs. Cependant, Android adapte automatiquement la taille des ressources bitmap, mme avec <supports-screens> et ses attributs true. Lavantage est que cela signifie que vous pouvez vous contenter de laisser ces bitmaps comme ils sont. Par contre, ceci signifie que cest le terminal qui devra adapter la taille, ce qui a videmment un cot en terme de temps CPU (et donc en terme de batterie). En outre, les algorithmes de redimensionnement peuvent ne pas tre optimaux compar ce que vous pouvez faire avec les outils graphiques de votre machine de dveloppement. Le projet ScreenSizes/EU4You_3 utilise donc deux rpertoires, res/drawable-ldpi et res/ drawable-hdpi, pour y placer des versions respectivement plus petites et plus grandes des drapeaux. Il renomme galement res/drawable en res/drawable-mdpi. Android utilisera ainsi les tailles de drapeaux adaptes la densit de lcran du terminal ou de lmulateur.

Utilisation de lespace
Bien que lactivit saffiche correctement sur un cran WVGA en mode portrait, elle gaspille normment despace en mode paysage, comme le montre la Figure36.8.

398

L'art du dveloppement Android 2

Figure36.8 EU4You, cran WVGA (800400pixels) en mode paysage.

Au lieu de lancer une activit spare pour excuter le navigateur web, vous pouvez faire apparatre le contenu Wikipedia directement dans lactivit principale lorsque le terminal a un cran large et quil est en mode paysage. Pour ce faire, nous devons dabord cloner le layout main.xml dans un rpertoire res/ layout-large-land et lui ajouter un widget WebView. Cest ce que nous faisons dans ScreenSizes/EU4You_4:
<?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" > <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" /> <WebView android:id="@+id/browser" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" /> </LinearLayout>

Puis il faut modifier notre activit pour quelle recherche la prsence de ce WebView et quelle lutilise sil est prsent; sinon on lance une activit de navigation classique:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

Chapitre 36


setContentView(R.layout.main);

Gestion des diffrentes tailles dcran

399

browser=(WebView)findViewById(R.id.browser); setListAdapter(new CountryAdapter()); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { String url=getString(EU.get(position).url); if (browser==null) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); } else { browser.loadUrl(url); } }

Ceci nous donne une version utilisant plus efficacement lespace disponible, comme le montre la Figure36.9.
Figure36.9 EU4You, cran WVGA (800400pixels) en mode paysage configur en densit normale et montrant le WebView intgr.

Si lutilisateur clique sur un lien de la page Wikipedia, le navigateur souvre afin de faciliter la navigation.

400

L'art du dveloppement Android 2

Notez que, pour tester la version de cette activit et son nouveau comportement, vous devez configurer correctement lmulateur. Par dfaut, en effet, Android configure les terminaux WVGA en haute densit, ce qui signifie que WVGA nest pas "grand" du point de vue des ensembles de ressources, mais normal. Vous devrez donc crer un nouvel AVD en le configurant avec une densit normale (medium) afin dobtenir une taille dcran large.

Et si ce nest pas un navigateur ?


Bien sr, EU4You triche un peu. Ici, la seconde activit est un navigateur (ou un WebView dans la forme intgre): ce nest pas votre propre cration. Les choses se compliquent un peu lorsque la seconde activit est une activit vous, avec plusieurs widgets configurs dans un layout, et que vous souhaitez la fois lutiliser comme une activit (sur un petit cran) et lintgrer dans lactivit principale de linterface utilisateur (pour les crans plus grands). Il existe un motif de conception pour ce type de scnario: 1. Dveloppez et testez dabord la seconde activit comme une activit autonome. 2. Faites en sorte que toutes les mthodes du cycle de vie de la seconde activit dlguent leur traitement une classe interne. Dplacez toutes les donnes membres de lactivit qui ne servent qu la classe interne dans celle-ci et assurez-vous que cela fonctionne. 3. Extrayez la classe interne pour en faire une classe publique spare et assurez-vous que cela fonctionne. 4. Pour votre premire activit (principale), crez un layout pour les grands crans et utilisez la directive <include> pour y intgrer le contenu du layout de la seconde activit au bon endroit. 5. Si le layout de la seconde activit a t trouv dans la premire activit (en vrifiant, par exemple, lexistence dun widget particulier laide de findViewById()), crez une instance de la classe publique de ltape3 et faites-lui grer tous ces widgets. Modifiez votre code pour quil dsigne directement cette classe plutt que lancer la seconde activit comme dans la section prcdente. En rsum, utilisez une classe publique et un layout rutilisable afin de conserver au mme endroit votre code et vos ressources tout en les utilisant la fois comme une activit autonome et comme partie dune version grand cran dune activit principale.

Chapitre 36

Gestion des diffrentes tailles dcran

401

Nos amis ont-ils quelques bogues?


Le DROID de Motorola, fourni avec Android 2.0, comprenait deux bogues concernant les tailles dcrans:

Ses valeurs pour la densit de lcran taient incorrectes, la fois en horizontal et en vertical: ceci signifie quil utilisait des dimensions incorrectes pour les tailles physiques pt, mm et in. Il utilisait Android 2.0 au niveau 6 de lAPI au lieu de 5: les rpertoires de ressources spcifiques aux versions devaient donc utiliser le suffixe -v6 au lieu de -v5.

Ces deux bogues ont t corrigs par Android 2.0.1 et aucun autre terminal ne devrait dsormais tre fourni avec Android 2.0 ou tre affect par ces bogues.

37
Gestion des terminaux
Pour les constructeurs de terminaux, Android est gratuit car cest un projet open-source. Ils ont donc carte blanche pour en faire ce quils veulent sur leurs matriels. Ceci a pour effet doffrir un trs grand choix aux utilisateurs, qui peuvent ainsi disposer de terminaux Android de toutes les formes, tailles ou couleurs. Ceci signifie galement que les dveloppeurs devront tenir compte des diffrences et des particularits des terminaux. Ce chapitre vous donnera quelques conseils pour grer les problmes spcifiques des terminaux, quil faut ajouter ce que nous avons expliqu sur la taille des crans au Chapitre36.

Cette application contient des instructions explicites


Au dpart, le G1 tait le seul terminal Android disponible : lorsque lon crivait une application Android, on pouvait donc supposer la prsence dun clavier physique alphanumrique, dun trackball pour la navigation, etc. Depuis, dautres terminaux ont vu le jour (le HTC Magic, par exemple), avec des quipements matriels diffrents (pas de clavier physique, notamment).

404

L'art du dveloppement Android 2

Dans lidal, votre application peut fonctionner sans tenir compte de la prsence des diffrents composants matriels. Certaines applications, cependant, ne pourront pas marcher si le terminal na pas certaines caractristiques. Un jeu plein cran, par exemple, peut dpendre dun clavier physique ou dun trackball pour signaler les actions de lutilisateur les claviers logiciels et les crans tactiles peuvent ne pas suffire. Heureusement, partir dAndroid1.5, vous pouvez ajouter des instructions explicites indiquant vos besoins Android, afin que votre application ne puisse pas sinstaller sur les terminaux qui ne disposent pas du matriel ncessaire. Outre lidentifiant systme indiquant la version laquelle sadresse votre projet, vous pouvez dsormais ajouter des lments <uses-configuration> llment <manifest> du fichier AndroidManifest.xml afin de prciser le matriel requis par votre application. Chacun de ces lments permet de dcrire une configuration matrielle valide pour votre application. Actuellement, vous pouvez prciser cinq exigences matrielles:

android:reqFiveWayNav. Indique si lon a besoin dun dispositif de pointage avec cinq directions (android:reqFiveWayNav = "true", par exemple). android:reqNavigation. Restreint le type du dispositif de pointage cinq directions (android:reqNavigation = "trackball", par exemple). android:reqHardKeyboard. Indique si lon a besoin dun clavier physique (android:reqHardKeyboard = "true", par exemple). android:reqKeyboardType. Combin avec android:reqHardKeyboard, indique un type de clavier physique particulier (android:reqKeyboardType = "qwerty", par exemple). android:reqTouchScreen. Indique le type dcran tactile ventuellement ncessaire (android:reqTouchScreen = "finger", par exemple).

Android 1.6 ajoute un lment similaire, <uses-feature>, conu pour documenter les besoins dune application concernant dautres fonctionnalits facultatives des terminaux Android. Cet lment peut contenir les attributs suivants:

android:glEsVersion. Indique que votre application a besoin dOpenGL. La valeur de cet attribut prcise la version minimale dOpenGL (0x00010002 pour OpenGL1.2 ou

suprieur, par exemple).


android:name = "android.hardware.camera". Indique que votre application a besoin

dun appareil photo.


android:name = "android.hardware.camera.autofocus". Indique que votre appli

cation a besoin dun appareil photo autofocus.

Chapitre 37

Gestion des terminaux

405

Boutons, boutons, qui a des boutons ?


La prsence des boutons sur les diffrents terminaux Android est trs variable : boutons physiques ou boutons logiciels sur lcran, voire pas de bouton du tout. Le HTC Dream (lautre nom du G1), par exemple, a des boutons appel, fin dappel, home, retour, menu et appareil photo, ainsi quun contrle du volume et un bouton rserv aux recherches sur son clavier physique. Sur le HTC Magic (galement appel myTouch 3G), le bouton pour lappareil photo a t remplac par le bouton de recherche. La tablette Android Archos5 na aucun bouton physique hormis le contrle du volume: les boutons home, retour et menu sont tous logiciels. Vous devez donc faire attention lorsque vous supposez lexistence ou lemplacement desboutons physiques. Proposez dautres moyens deffectuer les oprations rserves aux boutons : si, par exemple, vous redfinissez le contrle du volume pour quil serve de touche Page vers le haut/vers le bas, assurez-vous que lutilisateur aura un autre moyen defaire dfiler les pages.

Un march garanti
Comme on la mentionn dans lintroduction de ce chapitre, Android est open-source. Plus prcisment, il est essentiellement plac sous les termes de lApache Software License2.0. Cette licence imposant peu de restrictions aux constructeurs de terminaux, il est donc possible que lun deux cre un terminal qui, franchement, ne conviendra pas Android. Les applications fournies avec le terminal sexcuteront correctement, mais pas les applications tierces comme celles que vous pourriez crire. Pour aider rsoudre ce problme, Google a cr quelques applications, comme lAndroid Market, qui ne sont pas open-source. Bien quelles soient disponibles pour les constructeurs de terminaux, ceux qui proposent lAndroid Market sont dabord tests afin dassurer que les utilisateurs pourront utiliser correctement leurs terminaux. Un ingnieur de Google citait le cas dun constructeur prt produire un tlphone avec un cran QVGA avant la sortie dAndroid1.6, qui a officiellement ajout le support QVGA cette plateforme. Mme si ce constructeur stait arrang pour que les applications fournies avec lappareil fonctionnent sur un cran de rsolution rduite, ctait lhorreur pour les applications tierces. Apparemment, Google a refus de fournir Android Market ce constructeur pour ce produit. Par consquent, lexistence mme dAndroid Market sur un terminal, outre quil offre un moyen de distribution de vos applications, sert galement un peu de certificat de conformit dun terminal en garantissant quil devrait supporter les applications tierces correctement crites.

406

L'art du dveloppement Android 2

Les dtails scabreux


Malheureusement, lAndroid Market ne garantit pas un dploiement dpourvu de tout problme sur les terminaux sur lesquels il est install ; il nempche pas non plus les constructeurs de proposer des terminaux qui ne passent pas par le Market. Invitablement, certains auront donc des particularits pouvant avoir un impact ngatif sur vos applications. Voici une liste de quelques terminaux Android, dans lordre de leur venue sur le march, et leurs diffrences par rapport des dispositifs plus standard.

Tablette Internet Android Archos5


La tablette Internet Android Archos5 est le premier dispositif grand public reposant exclusivement sur le projet open-source Android. la diffrence des tlphones HTC, Motorola et autres, lArchos 5 nest pas un terminal certifi par Google et ne dispose donc ni de lAndroid Market, ni de Google Maps, ni des autres applications propritaires de Google. LArchos 5 est un terminal WVGA, mais il est livr avec Android 1.5 : de base, il ne reconnat donc pas la nouvelle dsignation -large pour les ensembles de ressources (voir Chapitre36). Ce terminal nayant pas t beaucoup vendu, vous devrez vous contenter de son interface non optimise jusqu ce que le support dAndroid1.6 lui soit ajout. Lcran tactile de lArchos5 est rsistif et non capacitif : pour manipuler lcran, les utilisateurs doivent donc utiliser leurs ongles ou un stylet plutt que leurs doigts. Vous devez en tenir compte lorsque vous dveloppez des interfaces tactiles. LArchos5, depuis son firmware1.1.01, renvoyait une valeur incorrecte pour ANDROID_ID (un identifiant unique affect chaque terminal Android). Dans lmulateur, ANDROID_ID vaut null et est cens tre une chane hexadcimale sur les vrais terminaux or, avec lArchos5, cet identifiant nest ni lun ni lautre. Si vous testez simplement sil vaut null ou non, vous naurez donc pas de problme ; en revanche, si vous attendez une valeur hexadcimale, ce comportement vous posera quelques problmes. LArchos5 ntant pas un tlphone, les fonctionnalits lies la tlphonie les appels via ACTION_DIAL, par exemple ne sont pas disponibles. Il na pas non plus dappareil photo. Comme on la dj mentionn, il ne dispose pas de Google Maps, de lAndroid Market et des autres applications propritaires de Google. Notons galement que lIMEI de lArchos est un faux puisque ce nest pas un tlphone.

CLIQ/DEXT de Motorola
Le CLIQ de Motorola (appel DEXT en dehors des tats-Unis) est un terminal HVGA livr avec Android1.5.

Chapitre 37

Gestion des terminaux

407

Le CLIQ possde un pad directionnel pour la navigation non tactile. Toutefois, ce pad se trouve sur un clavier alphanumrique coulissant et nest donc pas disponible lorsque le tlphone est en mode portrait moins de forcer le mode portrait via le manifeste de votre activit et dobliger les utilisateurs utiliser leur CLIQ avec le clavier sorti. Ncrivez pas des applications qui supposent quun terminal dispose toujours dun pad directionnel. Le CLIQ est galement fourni avec MOTOBLUR, la couche de prsentation sociale de Motorola. Ceci signifie que lcran daccueil, les contacts et dautres fonctionnalits dAndroid ont t remplacs par ceux spcifiques MOTOBLUR. Ceci ne devrait pas poser trop de problme si vous vous en tenez au SDK. La seule consquence un peu particulire est que vous naurez pas accs tous les contacts MOTOBLUR via le fournisseur de contenu Android Contacts les contacts Facebook, par exemple, sont accessibles MOTOBLUR mais pas aux applications tierces, probablement pour des raisons de licence. Cette situation peut tre amene changer lorsque le CLIQ sera mis jour avec le nouveau systme ContactsContract dAndroid2.0.1.

DROID/Milestone de Motorola
Le DROID de Motorola (appel Milestone en dehors des tats-Unis) est un terminal WVGA854 livr avec Android2.0, bien que la plupart des terminaux vendus aient depuis t mis jour avec Android2.0.1. Le DROID, comme le CLIQ, possde un pad directionnel sur un clavier amovible, ce qui signifie que le pad nest pas accessible lorsque le tlphone est en mode portrait. Le DROID ayant un cran WVGA854 de taille normale, Android considrera quil a un cran haute densit et utilisera donc les ensembles de ressources -hdpi.

Nexus One de Google/HTC


Le Nexus One construit par HTC et vendu par Google est un terminal WVGA800 livr avec Android2.1. Comme le DROID, lcran du Nexus One est considr comme ayant une haute densit (-hdpi).

BACKFLIP de Motorola
Le BACKFLIP de Motorola utilise une autre approche pour le pointage : plutt quun trackball ou un pad directionnel, il dispose de deux options de navigation non tactile: Son clavier physique a des touches directionnelles comme celui dun PC, qui produisent des vnements DPAD standard. Le touchpad qui se trouve au dos de lcran tactile produit des vnements trackball (ou DPAD si les prcdents ne sont pas consomms).

38
Gestion des changements de plateformes
Android continuera dvoluer rapidement au cours des prochaines annes. un moment, le rythme des changements dclinera sans doute mais, pour linstant, vous devez supposer que des versions importantes dAndroid verront le jour tous les 6ou 12mois et modifieront la gamme des matriels. Par consquent, bien quAndroid cible actuellement les tlphones, vous verrez bientt apparatre des tablettes Android, des lecteurs multimdias Android, etc. La plupart de ces changements auront peu dimpact sur le code existant. Toutefois, certains ncessiteront au moins quelques tests de vos applications et, ventuellement, des modi fications de votre code en fonction des rsultats de ces tests. Ce chapitre prsente les domaines qui peuvent poser problme mesure quAndroid volue, ainsi que quelques conseils pour les rsoudre.

410

L'art du dveloppement Android 2

Gestion de la marque
lheure o ce livre est crit, les terminaux Android existants sont des tlphones Google. Ceci signifie quils disposent de linterface standard dAndroid que vous retrouvez dans lmulateur et quils intgrent tout un lot dapplications comme Google Maps et Gmail. De leur ct, les constructeurs sont autoriss placer le logo "with Google" sur leurs produits, mais ce nest pas toujours le cas. Certains constructeurs se servent galement dAndroid comme dune base et modifient ce quil contient, ventuellement en ajoutant leurs propres applications et en changeant laspect de linterface (icnes des menus, structure de lcran daccueil, etc). Dautres peuvent utiliser uniquement le dpt open-source dAndroid et, bien quils proposent laspect standard, ne pas disposer des applications propritaires. Mme aujourdhui, certains terminaux proposent un mlange dapplications en fonction des rgions o ils sont distribus: les possesseurs amricains du G1, par exemple, disposent dune application Amazon MP3 Store qui nest pas disponible dans tous les pays. Une application indpendante de tous ces critres devrait pouvoir sexcuter nimporte o. En revanche, si votre code ou votre documentation suppose lexistence de Google Maps, Gmail, Amazon MP3 Store, etc., des problmes surgiront invitablement. Assurez-vous de tester soigneusement votre code dans les environnements o ces applications ne sont pas disponibles.

Des choses qui risquent de vous rendre nerveux


La plupart des lments cits dans la section prcdente concernaient les changements matriels. Examinons maintenant les problmes que peuvent vous poser les modifications du systme dexploitation lui-mme.

Hirarchie des vues


Android nest pas conu pour grer des hirarchies de vues complexes. Ici, "hirarchie de vues" signifie conteneurs contenant des conteneurs contenant des conteneurs contenant deswidgets. Comme le montre la Figure38.1, le programme Hierarchy Viewer dcrit au Chapitre 35 reprsente bien ces hirarchies. Dans cet exemple, on voit une hirarchie sur cinq niveaux car la longueur de la plus longue chane de conteneurs et de widgets (de PhoneWindow$DecorView Button) a cinq nuds. Android a toujours limit la profondeur maximale des hirarchies de vues. Toutefois, Android1.5 a rduit cette limite: certaines applications qui fonctionnaient parfaitement

Chapitre 38

Gestion des changements de plateformes

411

avec Android 1.1 chouaient donc en produisant une exception StackOverflowException dans la nouvelle version. Ctait, bien sr, assez frustrant pour les dveloppeurs qui ne se rendaient pas compte que le problme provenait de la profondeur de la hirarchie et qui se faisaient piger par cette modification. Les leons tirer de cette exprience sont les suivantes:

Limitez la profondeur de vos hirarchies de vues. Si vous atteignez une profondeur deux chiffres, vous risquez fortement dpuiser lespace allou la pile. Si vous rencontrez une exception StackOverflowException et que la trace montre quelle survient quelque part au milieu du dessin de vos widgets, cest que votre hirarchie de vues est probablement trop complexe.

Figure 38.1 Vue dun layout dans le Hierarchy Viewer.

Changement des ressources


Lquipe dAndroid peut modifier les ressources au cours dune mise jour, ce qui peut avoir des effets inattendus pour votre application. Avec Android1.5, par exemple, le fond du widget Button a t modifi afin de permettre de crer des boutons plus petits. Les applications qui se fiaient la taille minimale prcdente ont donc d revoir leurs interfaces utilisateurs.

412

L'art du dveloppement Android 2

De mme, les applications peuvent rutiliser des ressources publiques des icnes, par exemple quelles puisent directement dans Android. Bien que procder ainsi permette dconomiser lespace de stockage, nombreuses sont ces ressources qui sont publiques par ncessit et ne sont pas considres comme faisant partie du SDK. Les constructeurs, par exemple, peuvent modifier les icnes pour quelles correspondent laspect de leur interface propritaire: penser que ces icnes auront toujours le mme aspect est donc un peu dangereux. Il est prfrable de prendre ces ressources dans le dpt du projet Android open-source (http://source.android.com/) et de les copier dans votre propre base de code.

Gestion des modifications de lAPI


Lquipe dAndroid a gnralement fait un bon travail en maintenant la stabilit des API et en utilisant un modle dobsolescence lorsquelle les modifie. Avec Android, tre obsolte nimplique pas dtre supprim lutilisation est simplement dconseille. De nouvelles API sont fournies chaque mise jour dAndroid et les modifications des API existantes sont bien documentes dans les rapports qui accompagnent la nouvelle version. Malheureusement, lAndroid Market (le canal de distribution principal des applications Android) ne permet de ne dposer quun seul fichier APK par application. Ceci signifie donc que le mme fichier APK doit pouvoir saccommoder du plus grand nombre possible de versions. Souvent, votre code "fonctionne" et ne ncessite aucune modification ; parfois, cependant, vous devrez faire quelques changements, surtout si vous voulez prendre en compte les nouvelles API sans remettre en cause les anciennes versions. Nous allons prsenter quelques techniques permettant de traiter ces situations.

Dtecter la version dAndroid


Si vous voulez crer plusieurs branches dans votre code en fonction de la version dAndroid sur laquelle il sexcute, le plus simple consiste utiliser android.os.VERSION.SDK_INT. Cette valeur entire publique et statique contient le mme niveau dAPI que celui que vous utilisez lorsque vous crez des AVD et que vous prcisez dans le manifeste. Vous pouvez donc la comparer android.os.VERSION_CODES.DONUT, par exemple, pour savoir si lapplication sexcute sur Android1.6 ou une version plus rcente.

Envelopper lAPI
Tant que les API que vous tentez dutiliser existent sur toutes les versions dAndroid que vous supportez, vous pouvez vous contenter deffectuer des branchements dans votre code. Les choses se compliquent lorsque les API changent lorsque les mthodes prennent denouveaux paramtres, lorsquil y a de nouvelles mthodes, voire de nouvelles classes.

Chapitre 38

Gestion des changements de plateformes

413

Vous devez crire du code qui fonctionnera quelle que soit la version dAndroid tout en tirant parti des nouvelles API lorsquelles sont disponibles. Pour rsoudre ce problme, une astuce consiste utiliser lintrospection plus un soupon de mise en cache. Au Chapitre8, par exemple, nous avions utilis getTag() et setTag() pour associer un objet quelconque une View. Plus prcisment, nous les utilisions pour associer un objet enveloppe qui recherchait tous les widgets ncessaires lorsquil y en avait besoin. Nous avons galement vu que les nouvelles versions de getTag() et setTag() sont indexes et prennent un identifiant de ressource en paramtre. Cependant, ces nouvelles mthodes indexes nexistaient pas avec Android 1.5. Si vous voulez utiliser cette nouvelle technique, vous devrez imposer une version dAndroid1.6 ou suprieure ou utiliser lintrospection. Avec Android1.5, en effet, vous pourriez associer un ArrayList<Object> comme marqueur et faire en sorte que votre propre paire getTag()/ setTag() prenne lindice de chacun de vos lments prsents dans lArrayList. titre dexemple, tudions le projet APIVersions/Tagger. Notre activit a un layout simple, form uniquement dun widget TextView:
<?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" > <TextView android:id="@+id/test" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>

Le code source de lactivit Tagger examine la version de lAPI utilise et dlgue les oprations getTag() et setTag() aux versions natives indexes (pour Android1.6 et les versions suprieures) ou aux versions originales non indexes getTag() et setTag(), auquel cas nous utilisons un HashMap pour mmoriser les diffrents objets indexs:
package com.commonsware.android.api.tag; import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.TextView;

414

L'art du dveloppement Android 2

import java.util.HashMap; import java.util.Date; public class Tagger extends Activity { private static final String LOG_KEY="Tagger"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView view=(TextView)findViewById(R.id.test); setTag(view, R.id.test, new Date()); view.setText(getTag(view, R.id.test).toString()); } public void setTag(View v, int key, Object value) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT) { v.setTag(key, value); } else { HashMap<Integer, Object> meta = (HashMap<Integer, Object>)v.getTag(); if (meta==null) { meta=new HashMap<Integer, Object>(); } meta.put(key, value); } } public Object getTag(View v, int key) { Object result=null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT) { result=v.getTag(key); } else { HashMap<Integer, Object> meta=(HashMap<Integer, Object>)v.getTag(); if (meta==null) { meta=new HashMap<Integer, Object>(); }

Chapitre 38


result=meta.get(key); } return(result);

Gestion des changements de plateformes

415

} }

Si nous compilons ce code et que nous le dployions sur Android 1.6 ou une version suprieure, tout fonctionnera correctement: lactivit affichera lheure courante. Si nous le compilons et que nous le dployions sur Android 1.5, par contre, son excution provoquera une exception VerifyError. Ici, VerifyError signifie en fait que nous faisons appel des lments qui nexistent pas dans notre version dAndroid:

Nous faisons rfrence SDK_INT, qui nexiste pas avant Android1.6. Nous faisons rfrence aux versions indexes de getTag() et setTag(): mme si nous nexcuterons pas la partie du code en question, le chargeur de classes cherche quand mme rsoudre ces mthodes et choue.

Nous devons donc utiliser lintrospection. Examinons le projet APIVersions/Tagger2, qui utilise le mme layout que le prcdent, mais dont le code Java est plus labor:
package com.commonsware.android.api.tag; import import import import import import import import import android.app.Activity; android.os.Build; android.os.Bundle; android.util.Log; android.view.View; android.widget.TextView; java.lang.reflect.Method; java.util.HashMap; java.util.Date;

public class Tagger extends Activity { private static final String LOG_KEY="Tagger"; private static Method _setTag=null; private static Method _getTag=null; static { int sdk = new Integer(Build.VERSION.SDK).intValue(); if (sdk >= 4) { try {

416

L'art du dveloppement Android 2

_setTag=View.class.getMethod("setTag", new Class[] {Integer.TYPE, Object.class }); _getTag=View.class.getMethod("getTag", new Class[] {Integer.TYPE}); } catch (Throwable t) { Log.e(LOG_KEY, "Could not initialize 1.6 accessors", t); } } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView view=(TextView)findViewById(R.id.test); setTag(view, R.id.test, new Date()); view.setText(getTag(view, R.id.test).toString()); } public void setTag(View v, int key, Object value) { if (_setTag!=null) { try { _setTag.invoke(v, key, value); } catch (Throwable t) { Log.e(LOG_KEY, "Could not use 1.6 setTag()", t); } } else { HashMap<Integer, Object> meta=(HashMap<Integer, Object>)v.getTag(); if (meta==null) { meta=new HashMap<Integer, Object>(); v.setTag(meta); } meta.put(key, value); } } public Object getTag(View v, int key) { Object result=null;

Chapitre 38

Gestion des changements de plateformes

417

if (_getTag!=null) { try { result=_getTag.invoke(v, key); } catch (Throwable t) { Log.e(LOG_KEY, "Could not use 1.6 getTag()", t); } } else { HashMap<Integer, Object> meta=(HashMap<Integer, Object>)v.getTag(); if (meta==null) { meta=new HashMap<Integer, Object>(); v.setTag(meta); } result=meta.get(key); } return(result); } }

Quand la classe est charge, les mthodes dinitialisation statiques sexcutent dabord. Ici, nous vrifions la version dAndroid utilise laide de lancienne chane SDK et non plus avec lentier SDK_INT plus rcent. Si nous sommes sous Android1.6 ou une version suprieure, nous utilisons lintrospection pour tenter de trouver les mthodes getTag() et setTag() indexes et nous mettons les rsultats en cache: ces mthodes ne changeant pas au cours de lexcution de lapplication, nous pouvons les mmoriser sans crainte dans des variables statiques. Puis, quand il faut vraiment utiliser getTag() ou setTag(), nous vrifions si les objets Method en cache existent ou valent null. Dans ce dernier cas, nous supposons quil faut utiliser les anciennes versions de ces mthodes. Si ces objets Method existent, nous nous en servons pour tirer parti des versions indexes natives. Cette version de lapplication fonctionne trs bien avec Android1.5 et les versions suprieures. partir dAndroid1.6, le code utilisera les mthodes indexes prdfinies alors quAndroid1.5 utilisera notre fausse version de ces mthodes. Lutilisation de lintrospection laide de Method a un lger cot en termes de performances, mais elle peut tre justifie dans certains cas afin daccder aux API des nouvelles versions dAndroid au lieu de nous restreindre aux anciennes. Nous pouvons mme utiliser cette technique lorsque des classes ont t ajoutes aux nouvelles versions dAndroid (voir http://android-developers.blogspot.com/2009/04/backward-compatibility-forandroid.html).

39
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 fin 2008 concernent probablement le SDK M5 et ncessiteront donc des modifi cations trs importantes pour fonctionner correctement avec les SDK actuels. Ce chapitre vous donnera donc quelques pistes explorer.

Questions avec, parfois, des rponses


Il existe de nombreuses ressources anglophones et francophones. Les groupes Google consacrs Android (http://groups.google.com/group) sont les endroits officiels pour obtenir de laide. Trois groupes sont consacrs au SDK:

android-beginners est le meilleur endroit pour poster des questions de dbutant.

420

L'art du dveloppement Android 2

android-developers est consacr aux questions plus compliques ou celles qui

relvent de parties plus exotiques du SDK. android-discuss 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 http://anddev.org ; le wiki Open Mob for Android (http://wiki.andmob.org); le canal IRC #android sur freenode (http://freenode.net); les tags android et android-sdk de StackOverflow (http//stackoverflow.com/) ; le forum Android de JavaRanch (http://www.javaranch.com/).

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, http:// source.android.com. partir de ce site, vous pouvez:

tlcharger ou parcourir le code source; signaler des bogues du systme lui-mme; proposer des patchs et apprendre comment ces patchs sont valus et approuvs; rejoindre un groupe Google particulier pour participer au dveloppement de la plateforme Android.

Au lieu de tlcharger les gigaoctets des sources dAndroid, vous pouvez utiliser Google Code Search (http://www.google.com/codesearch): il suffit dajouter android:package votre requte afin de ne rechercher que ce qui concerne Android et les projets associs. Citons galement quelques ressources francophones:

http://www.frandroid.com: le premier site communautaire pour les utilisateurs et les dveloppeurs de la plateforme Android (forum, tutoriaux, wiki, actualits). http://www.poingphone.com: forums et actualits. http://www.android-france.fr: actualits de la plateforme. La communaut francophone est de plus en plus dynamique et a cr le channel IRC #androidfr disponible sur freenode.

Chapitre 39

Pour aller plus loin

421

Lire les journaux


Ed Burnette, qui a crit son propre livre sur Android, est galement le gestionnaire de Planet Android (http://www.planetandroid.com/), un agrgateur de flux pour un certain nombre de blogs consacrs Android. En vous abonnant ce flux, 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 flux qui rsume cette recherche (http://www.dzone.com/links/feed/search/android/ rss.xml).

Index

A
aapt, outil 28 AbsoluteLayout, conteneur 133, 379 Acclromtres 211 Activity classe 22, 182, 219 lment 219 du manifeste 13 activityCreator, script 229 ActivityManager, classe 177 Adaptateur 65 adb, programme 271, 371, 376, addMenu(), mthode 147 add(), mthode 146, 354 addProximityAlert(), mthode 347 addSubMenu(), mthode 147 addTab(), mthode 120 AlertDialog, classe 172 AnalogClock, widget 114 android alphabeticShortcut, attribut 156 anyDensity, attribut 381, 382, 388 apiKey, attribut 351, 358 authorities, attribut 315

autoText, attribut de widget 36 background, attribut 43 capitalize, attribut de widget 36 clickable, attribut 351 collapseColumns, proprit 60 columnWidth, proprit 74 configChanges, attribut 219 content, attribut 131 digits, attribut de widget 37 drawSelectorOnTop, proprit 71, 81 ellipsize, attribut 161 enabled, attribut 155 glEsVersion, lment 404 handle, attribut 131 horizontalSpacing, proprit 74 icon, attribut 155 id attribut 118, 155, 351 de main.xml 29 imeOptions, attribut 140 inputType, attribut 136 largeScreens, attribut 381, 382, 388 layout_above, proprit 54 layout_alignBaseline, proprit 55 layout_alignBottom, proprit 54

424

L'art du dveloppement Android 2

layout_alignLeft, proprit 54 layout_alignParentBottom, proprit 53 layout_alignParentLeft, proprit 53 layout_alignParentRight, proprit 53 layout_alignParentTop, proprit 53 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 59 layout_gravity, proprit 48 layout_height, lment 379, 389, 395, 396, 398 layout_span, proprit 59 layout_toLeftOf, proprit 54 layout_toRightOf, proprit 54 layout_weight, proprit 47 layout_width, attribut de main.xml 29 layout_width, lment 379, 389, 395, 396, 398 layout_width, proprit 47 menuCategory, attribut 155 name, attribut 315, 318, 326, 404 nextFocusDown, attribut 42 nextFocusLeft, attribut 42 nextFocusRight, attribut 42 nextFocusUp, attribut 42 normalScreens, attribut 381, 382, 388 numColumns, proprit 73 numericShortcut, attribut 156 orderInCategory, attribut 155 orientation, proprit 46 paddingLeft, proprit 48 padding, proprit 48 paquetage 21 permission, attribut 320, 327 readPermission, attribut 320 reqFiveWayNav, attribut 404 reqHardKeyboard, lment 404 reqKeyboardType, lment 404 reqNavigation, lment 404

reqTouchScreen, lment 404 screenOrientation, attribut 221 script 20 shrinkColumns, proprit 60 singleLine, attribut de widget 37 smallScreens, attribut 381, 382, 388 spacing, proprit 81 spinnerSelector, proprit 81 src, attribut de widget 35 stretchColumns, proprit 60 stretchMode, proprit 74 text attribut de main.xml 29 attribut de widget 34 textColor, attribut 43 typeface, attribut 159 versionCode, attribut du manifeste 15 verticalSpacing, proprit 74 visibility, attribut 42 visible, attribut 156 windowSoftInputMode, attribut 143 writePermission, attribut 320 AndroidManifest 248 AndroidManifest.xml, fichier 9, 11, 143, 164, 198, 219, 221, 314, 318, 326, 381, 404 android.os.VERSION.SDK_INT, version de lAPI 412 Android Scripting Environment (ASE) 289 animateClose(), mthode 132 animateOpen(), mthode 132 animateToggle(), mthode 132 Animations 129 apk, fichier 11 appendWhere(), mthode 269 application lment 314, 326 du manifeste 12 Arborescence de rpertoires 9 ArrayAdapter adaptateur 66, 68, 85, 97 classe 186, 276 ArrayList, classe 276 AssetManager, classe 160

Index

425

AsyncTask, classe 182, 328 AutoCompleteTextView, widget 38, 77 Auto-compltion 77 AVD (Android Virtual Device) 20

B
BaseColumns, interface 314 BeanShell, programme 285 beforeTextChanged(), mthode 79 bindService(), mthode 332 bindView(), mthode 106 BLOB (Binary Large Object) 303 Bouton accessoire 140 BroadcastReceiver classe 335 interface 199, 200 Builder, classe 172 buildQuery(), mthode 269 build.xml, fichier 9 bulkInsert(), mthode 302 Bundle classe 194, 204, 212, 218 objet 193 Button, widget 35

C
Calendar, classe 112 cancelAll(), mthode 338 cancel(), mthode 338 canGoBack(), mthode 167 canGoBackOrForward(), mthode 167 canGoForward(), mthode 167 Catgories dactivits 196 CheckBoxPreference, lment 247 CheckBox, widget 38 checkCallingPermission(), mthode 321 check(), mthode 41 Chronometer, widget 115 clearCache(), mthode 167 clearCheck(), mthode 41 clearHistory(), mthode 167 clear(), mthode 246 close(), mthode 132, 265, 270, 277

color, lment 238 ColorStateList, widget 43 commit(), mthode 246 CompoundButton, widget 40 Configuration, classe 383 ContentManager, classe 338 ContentObserver, classe 315 ContentProvider classe 307 interface 302 ContentResolver, classe 302, 315 ContentValues, classe 266, 302, 310, 311 Context, classe 246, 277 ContextMenuInfo, classe 148 convertView, paramtre de getView() 89 createDatabase(), mthode 271 createFromAsset(), mthode 160 createFromFile(), mthode 160 createItem(), mthode 355 create(), mthode 173 createTabContent(), mthode 121 Criteria, classe 345 Cursor classe 267 interface 299, 308 widget 67 CursorAdapter, adaptateur 67, 106 CursorFactory, classe 271

D
DatabaseHelper, classe 308 DateFormat, classe 112 DatePickerDialog, widget 110 DatePicker, widget 110 DDMS (Dalvik Debug Monitor Service) 348 ddms, programme 370 DefaultHttpClient, classe 292 default.properties, fichier 9 delete(), mthode 266, 302, 312 DialogWrapper, classe 302 DigitalClock, widget 114 dimen, lment 237 dip, unit de mesure 380

426

L'art du dveloppement Android 2

doInBackground(), mthode 183 Drawable classe 355 interface 231 draw(), mthode 355

E
edit(), mthode 246 EditTextPreference, lment 255 EditText, widget 36 Environment, classe 281 Espace de noms 12 execSQL(), mthode 265 execute(), mthode 183, 188, 292 ExpandableListView, classe 133

F
fill_parent, valeur de remplissage 47 findViewById (), mthode 30 findViewById(), mthode 43, 89, 120, 273, 352 findViewById(); mthode 91 finish(), mthode 193, 280 Forecast, classe 295, 329 format(), mthode 228 FrameLayout, conteneur 117, 125 fromHtml(), mthode 228

G
Gallery, widget 81 GeoPoint, classe 353 getAltitude(), mthode 345 getAsInteger(), mthode 266 getAssets(), mthode 160 getAsString(), mthode 266 getAttributeCount(), mthode 236 getAttributeName(), mthode 236 getBearing(), mthode 345 getBestProvider(), mthode 345 getBoolean(), mthode 246 getCallState(), mthode 362 getCheckedRadioButtonId(), mthode 41

getColor(), mthode 238 getColumnIndex(), mthode 270 getColumnNames(), mthode 270 getConfiguration(), mthode 383 getContentProvider(), mthode 302 getContentResolver(), mthode 315 getCount(), mthode 270 getDefaultSharedPreferences(), mthode 246, 247 getDimen(), mthode 237 getExternalStorageDirectory(), mthode 281 getInputStream(), mthode 303 getInt(), mthode 270 getItemId(), mthode 147 getLastKnownPosition(), mthode 345 getLastNonConfigurationInstance(), mthode 217 getLatitude(), mthode 293 getListView(), mthode 69 getLongitude(), mthode 293 getMapController(), mthode 352 getMenuInfo(), mthode 148 getNetworkType(), mthode 362 getOutputStream(), mthode 303 getOverlays(), mthode 354 getParent(), mthode 43 getPhoneType(), mthode 362 getPreferences(), mthode 246 getProgress(), mthode 116 getProviders(), mthode 345 getReadableDatabase(), mthode 265 getRequiredColumns(), mthode 311 getResources(), mthode 234, 273, 276 getSettings(), mthode 165, 169 getSharedPreferences(), mthode 246 getSpeed(), mthode 345 getStringArray(), mthode 239 getString(), mthode 227, 270 getSubscriberId(), mthode 362 getSystemService(), mthode 338, 344 getTableName(), mthode 269 getTag(), mthode 91, 97 getType(), mthode 313

Index

427

getView(), mthode 67, 86, 89, 106, 301 getWriteableDatabase(), mthode 265 getXml(), mthode 234 goBack(), mthode 167 goBackOrForward(), mthode 167 goForward(), mthode 167 GPS (Global Positioning System) 343 GradientDrawable, classe 380 group, lment 155

H
handleMessage(), mthode 178 Handler, classe 177 hasAltitude(), mthode 345 hasBearing(), mthode 345 hasSpeed(), mthode 345 hierarchyviewer, programme 366 HorizontalScrollView, conteneur 64 htmlEncode(), mthode 230 HttpClient classe 324 interface 292 HttpComponents, bibliothque 292 HttpGet, classe 292 HttpPost, classe 292 HttpRequest, interface 292 HttpResponse, classe 292 HVGA, rsolution dcran 377

insert(), mthode 266, 302, 310 instrumentation, lment du manifeste 12 Intent, classe 196 intent-filter, lment 198 Internationalisation (I18N) 226, 239 Interpreter, classe de BeanShell 285 isAfterLast(), mthode 270 isChecked(), mthode 38 isCollectionUri(), mthode 311, 312 isEnabled(), mthode 43 isRouteDisplayed(), mthode 352 item, lment 155, 238 ItemizedOverlay, classe 354

J
JavaScript, et WebView 165 JRuby, langage de script 289 Jython, langage de script 289

K
keytool, utilitaire 359

L
LayoutInflater, classe 87, 104 LinearLayout, conteneur 46, 84, 97, 104 ListActivity, classe 67, 184 ListAdapter, adaptateur 101 ListPreference, lment 255 ListView, widget 67, 83 loadData(), mthode 166 loadUrl(), mthode 164 Localisation (L10N) 226, 239 Location, classe 293, 345 LocationListener, classe 346 LocationManager, classe 324, 344 LocationProvider, classe 344 lock(), mthode 132

I
IBinder classe 325 interface 333 ImageView classe 303 widget 35 IME (Input Method Editor) 136 IMF (Input Method Framework) 135 incrementProgressBy(), mthode 116 indiquant 248 InputStream, classe 273, 277, 294 InputStreamReader, classe 277

M
makeText(), mthode 172 managedQuery(), mthode 299

428

L'art du dveloppement Android 2

manifest lment 318 lment racine du manifeste 11 MapActivity, classe 349, 351 MapView, classe 349, 351 Menu classe 146, 156 lment 155 MenuInflater, classe 156 MenuItem, classe 146 Message, classe 178 MIME, types 307 mksdcard, programme 376 moveToFirst(), mthode 270 moveToNext(), mthode 270 MyLocationOverlay, classe 357

N
name attribut 226, 237, 238 lment du manifeste 13 newCursor(), mthode 271 newTabSpec(), mthode 119 newView(), mthode 106 next(), mthode 234 nine-patch, image 380 Notification, classe 189, 338 NotificationManager, classe 338 notifyChange(), mthode 315 notify(), mthode 338

O
obtainMessage(), mthode 178 onActivityResult(), mthode 203 onBind(), mthode 325, 332 OnCheckedChangeListener, interface 51 OnClickListener classe 112, 205 interface 22 OnClickListener(), mthode 175 onClick(), mthode 23 onConfigurationChanged(), mthode 219 onContextItemSelected(), mthode 148, 151

onCreateContextMenu(), mthode 148, 151 onCreate(), mthode 22, 151, 192, 194, 212, 250, 262, 276, 300, 307, 324 onCreateOptionsMenu() menu 151 mthode 146, 147 onCreatePanelMenu(), mthode 147 OnDateChangedListener, classe 110 OnDateSetListener, classe 110 onDestroy(), mthode 192, 324 OnItemSelectedListener, interface 72 onListItemClick(), mthode 68, 97 onLocationChanged(), mthode 346 onOptionsItemSelected(), mthode 146, 147, 151 onPageStarted(), mthode 167 onPause(), mthode 193, 200, 280, 324, 334, 357 onPostExecute(), mthode 184 onPreExecute(), mthode 184 onPrepareOptionsMenu(), mthode 146 onProgressUpdate(), mthode 184 onRatingChanged(), mthode 97 onReceivedHttpAuthRequest(), mthode 167 onReceive(), mthode 199 onRestart(), mthode 193 onRestoreInstanceState(), mthode 194, 212 onResume(), mthode 193, 200, 250, 280, 293, 324, 334, 357 onRetainNonConfigurationInstance(), mthode 216 onSaveInstanceState(), mthode 193, 194, 200, 212, 213 onStart(), mthode 180, 193, 324 onStop(), mthode 193 onTap(), mthode 356 onTextChanged(), mthode 79 OnTimeChangedListener, classe 110 OnTimeSetListener, classe 110 onTooManyRedirects(), mthode 167 onUpgrade(), mthode 262 openFileInput(), mthode 277, 280 openFileOutput(), mthode 277, 280

Index

429

open(), mthode 132 openRawResource(), mthode 273, 276 OpenStreetMap, cartographie 350 OutputStream, classe 277 OutputStreamWriter, classe 277 Overlay, classe 354 OverlayItem, classe 354

P
package, attribut du manifeste 12 parse(), mthode 298, 299 PendingIntent, classe 339, 347 permission lment 319 du manifeste 12 populateDefaultValues(), mthode 311 populate(), mthode 355 postDelayed(), mthode 181 post(), mthode 181 PreferenceCategory, lment 251 PreferenceScreen, lment 247, 251 PreferencesManager, classe 246 ProgressBar, widget 115, 178 provider lment 314 du manifeste 13 publishProgress(), mthode 184 px, unit de mesure 380

registerForContextMenu(), mthode 147 registerReceiver(), mthode 200 RelativeLayout, conteneur 379 reload(), mthode 167 remove(), mthode 246 removeProximityAlert(), mthode 347 removeUpdates(), mthode 347 requery(), mthode 270, 302 requestFocus(), mthode 43 requestLocationUpdates(), mthode 346 Resources classe 234, 273 lment 237 ResponseHandler, classe 292 ressources, lment 226 RingtonePreference, lment 247 R.java, fichier 10 RowModel, classe 97 Runnable, classe 181 runOnUiThread(), mthode 182

S
ScrollView, conteneur 62 SecurityException, exception 318 SeekBar, widget 116 sendBroadcast(), mthode 321, 329 sendMessageAtFrontOfQueue(), mthode 178 sendMessageAtTime(), mthode 178 sendMessageDelayed(), mthode 178 sendMessage(), mthode 178 SensorManager, classe 260 Service classe 324 lment 326 du manifeste 14 ServiceConnection, classe 333 setAccuracy(), mthode 345 setAdapter(), mthode 67, 71 setAlphabeticShortcut(), mthode 147 setAltitudeRequired(), mthode 345 setBuiltInZoomControls(), mthode 353 setCenter(), mthode 353 setCheckable(), mthode 147

Q
query(), mthode 267, 308 queryWithFactory(), mthode 271 QVGA, rsolution dcran 377

R
RadioButton, widget 40 RadioGroup, widget 41 RatingBar, widget 94 rawQuery(), mthode 267 rawQueryWithFactory(), mthode 271 receiver, lment 199 registerContentObserver(), mthode 316

430

L'art du dveloppement Android 2

setChecked(), mthode 38, 42 setChoiceMode(), mthode 69 setColumnCollapsed(), mthode 60 setColumnStretchable(), mthode 60 setContent(), mthode 119, 121 setContentView(), mthode 30 setCostAllowed(), mthode 345 setCurrentTab(), mthode 120 setDefaultFontSize(), mthode 169 setDropDownViewResource(), mthode 71, 72 setDuration(), mthode 172 setEnabled(), mthode 156 setFantasyFontFamily(), mthode 169 setFlipInterval(), mthode 129 setGravity(), mthode 48 setGroupCheckable(), mthode 146, 147 setGroupEnabled(), mthode 156 setGroupVisible(), mthode 156 setIcon(), mthode 173 setImageURI(), mthode 35 setIndeterminate(), mthode 116 setIndicator(), mthode 119 setJavaScriptCanOpenWindowsAutomatically(), mthode 170 setJavaScriptEnabled(), mthode 165, 170 setLatestEventInfo(), mthode 339 setListAdapter(), mthode 68 setMax(), mthode 115, 180 setMessage(), mthode 173 setNegativeButton(), mthode 173 setNeutralButton(), mthode 173 setNumericShortcut(), mthode 147 setOnCheckedChanged(), mthode 39 setOnCheckedChangeListener(), mthode 39 setOnClickListener(), mthode 23, 280 setOnItemSelectedListener(), mthode 67, 71 setOrientation(), mthode 46 setProgress(), mthode 115 setProjectionMap(), mthode 269 setQwertyMode(), mthode 147 setResult(), mthode 203

setTables(), mthode 269 setTag(), mthode 91, 97 setText(), mthode 23 setTextSize(), mthode 169 setTitle(), mthode 173 setTypeface(), mthode 160 setup(), mthode 119 setUserAgent(), mthode 170 setView(), mthode 172 setVisible(), mthode 156 setWebViewClient(), mthode 167 setZoom(), mthode 353 SharedPreferences, classe 246, 255 shouldOverrideUrlLoading(), mthode 167 show(), mthode 172 showNext(), mthode 126 SimpleCursorAdapter, classe 270, 300 Singleton, patron de conception 325 size(), mthode 355 SlidingDrawer, widget 130 SOAP, protocole 292 SoftReference, classe 328 Spanned, interface 227 Spinner, widget 70 sp, unit de mesure 380 sqlite3, programme 271 SQLiteDatabase, classe 262 SQLite Manager, extension Firefox 271 SQLiteOpenHelper, classe 262 SQLiteQueryBuilder, classe 267, 309 SSL et HttpClient 296 StackOverflowException, exception 411 startActivityForResult(), mthode 203 startActivity(), mthode 203, 362 startFlipping(), mthode 129 startService(), mthode 324, 332 stopSelf(), mthode 334 stopService(), mthode 332 string-array, lment 238 string, lment 226 strings.xml, fichier 226 supports-screens, lment 381, 388, 389, 397 SystemClock, classe 178

Index

431

T
TabActivity, classe 118, 207 TabContentFactory(), mthode 121 TabHost classe 208 conteneur 117 widget 117 TableLayout, conteneur 58, 250 TableRow, conteneur 58 TabSpec, classe 119 TabView, conteneur 207 TabWidget, widget 117 TelephonyManager, classe 362 TextView, widget 33, 76, 85, 160 TextWatcher, interface 78 TimePickerDialog, widget 110 TimePicker, widget 110 Toast, classe 172 toggle(), mthode 38, 132 toggleSatellite(), mthode 353 TrueType, polices 159 Typeface, classe 160

Uri, classe 196, 297, 362 uses-configuration, lment 404 uses-feature, lment 404 uses-library, lment du manifeste 12 uses-permission lment 318 du manifeste 12 uses-sdk, lment du manifeste 12, 14

V
VerifyError, exception 415 Versions du SDK 14 View classe 23 widget 60, 86, 87 ViewFlipper, conteneur 124 Virus 317

W
WeakReference, classe 328 WebKit, widget 227, 291 WebSettings, classe 169 WebViewClient, classe 167 WebView, widget 163, 398, 399, 400 wrap_content, valeur de remplissage 47 WVGA, rsolution dcran 377

U
unbindService(), mthode 334 unlock(), mthode 132 unregisterContentObserver(), mthode 316 unregisterReceiver(), mthode 200 update(), mthode 266, 311 uptimeMillis(), mthode 178

X
XmlPullParser, classe 234 XML-RPC, protocole 292

Lart du dveloppement

Android 2 dition
e

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 ! Cette seconde dition couvre dsormais Android 2. Vous y apprendrez toutes les bases de la programmation sous Android de la cration des interfaces graphiques lutilisation de GPS, en passant par laccs aux services web et bien dautres choses encore ! Il regorge dastuces et de conseils qui vous aideront raliser vos premires applications. travers des dizaines de projets dexemples, vous assimilerez les points techniques les plus dlicats et apprendrez crer rapidement des applications convaincantes. Les codes sources des exemples sont tlchargeables sur le site www.pearson.fr.

propos de lauteur
Programmeur depuis plus de 25 ans, Mark Murphy a travaill sur des plateformes 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.

Tour dhorizon Projets et cibles 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 Le framework des mthodes de saisie Utilisation des menus Polices de caractres Intgrer le navigateur de WebKit Afchage de messages surgissants Utilisation des threads Gestion des vnements du cycle de vie dune activit Cration de ltres dintentions Lancement dactivits et de sous-activits Gestion de la rotation Utilisation des ressources Utilisation des prfrences Accs et gestion des bases de donnes locales Accs aux chiers Tirer le meilleur parti des bibliothques Java Communiquer via Internet 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 Outils de dveloppement Gestion des diffrentes tailles dcran Gestion des terminaux Gestion des changements de plateformes Pour aller plus loin

Niveau : Intermdiaire / Avanc Catgorie : Dveloppement mobile

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-4159-4