Vous êtes sur la page 1sur 391

Lart du dveloppement

Android

Mark Murphy
customer 27921 at Fri Mar 11 19:19:45 +0100 2011 Proprit de Albiri Sigue <tag.tog@gmail.com>

L E

P R O G R A M M E U R

Lart du dveloppement Android


Mark L. Murphy

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

Pearson Education France a apport le plus grand soin la ralisation de ce livre an de vous fournir une information complte et able. 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/licenses/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-4094-8 Copyright 2009 Pearson Education France Tous droits rservs

Titre original : Beginning Android

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

ISBN original : 978-1-4302-2419-8 Copyright 2009 by Mark L. Murphy All rights reserved dition originale publie par Apress, 2855 Telegraph Avenue, Suite 600, Berkeley, CA 94705 USA

Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a) du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le respect des modalits prvues larticle L. 122-10 dudit code. No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any means, electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher.

Sommaire

propos de lauteur .............................. Remerciements ....................................... Prface ldition franaise .................. Introduction ............................................ Partie I Concepts de base ................... 1. Tour dhorizon ................................... 2. Structure dun projet ......................... 3. Contenu du manifeste ........................ Partie II Les activits .......................... 4. Cration dun squelette dapplication 5. Utilisation des layouts XML ............. 6. Utilisation des widgets de base ......... 7. Conteneurs ......................................... 8. Widgets de slection ........................... 9. Samuser avec les listes ...................... 10. Utiliser de jolis widgets et de beaux conteneurs .................... 11. Utilisation des menus ....................... 12. Polices de caractres ........................ 13. Intgrer le navigateur de WebKit ...

IX XI XIII 1 3 5 9 13 19 21 29 35 45 65 83 107 129 141 147

14. Afchage de messages surgissant ... 15. Utilisation des threads ..................... 16. Gestion des vnements du cycle de vie dune activit ........................ Partie III Stockage de donnes, services rseaux et API .......................... 17. Utilisation des prfrences .............. 18. Accs aux chiers ............................ 19. Utilisation des ressources ................ 20. Accs et gestion des bases de donnes locales ........................... 21. Tirer le meilleur parti des bibliothques Java .................... 22. Communiquer via Internet ............. Partie IV - Intentions (Intents) ............. 23. Cration de ltres dintentions ....... 24. Lancement dactivits et de sous-activits ........................... 25. Trouver les actions possibles grce lintrospection ............................... 26. Gestion de la rotation ......................

155 161 173

177 179 191 199 217 227 235 241 243 249 259 265

IV

Lart du dveloppement Android

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

277 279 287

Partie VI Autres fonctionnalits dAndroid ............................................... 33. Accs aux services de localisation ... 34. Cartographie avec MapView et MapActivity ................................. 35. Gestion des appels tlphoniques ...

319 321 327 337 341 351 363 367

297 303 309 313 36. Recherches avec SearchManager ... 37. Outils de dveloppement ................. 38. Pour aller plus loin .......................... Index .......................................................

Table des matires

propos de lauteur .............................. Remerciements ....................................... Prface ldition franaise .................. Introduction ............................................ Bienvenue ! ......................................... Prrequis ............................................. ditions de ce livre ............................. Termes dutilisation du code source ... Partie I Concepts de base ................... 1. Tour dhorizon ................................... Contenu dun programme Android ..... Fonctionnalits votre disposition ..... 2. Structure dun projet ......................... Contenu de la racine .......................... la sueur de votre front ..................... La suite de lhistoire ........................... Le fruit de votre travail ....................... 3. Contenu du manifeste ........................ Au dbut, il y avait la racine ............... Permissions, instrumentations et applications ..................................... Que fait votre application ? ................. Faire le minimum ................................ Version = contrle ...............................

XI XIII XV 1 1 1 2 2 3 5 6 8 9 9 10 11 11 13 14 14 15 16 17

Partie II Les activits ......................... 4. Cration dun squelette dapplication Terminaux virtuels et cibles ............... Commencer par le dbut ..................... Lactivit ............................................. Dissection de lactivit ....................... Compiler et lancer lactivit ............... 5. Utilisation des layouts XML ............. Quest-ce quun positionnement XML ? Pourquoi utiliser des layouts XML ? .. Contenu dun chier layout ................ Identiants des widgets ...................... Utilisation des widgets dans le code Java ................................ Fin de lhistoire .................................. 6. Utilisation des widgets de base ......... Labels ................................................. Boutons ............................................... Images ................................................ Champs de saisie ................................ Cases cocher .................................... Boutons radio ..................................... Rsum ...............................................

19 21 21 23 24 25 27 29 29 30 31 32 32 33 35 35 36 37 38 40 42 43

VI

Lart du dveloppement Android

7. Conteneurs ......................................... Penser de faon linaire ..................... Tout est relatif .................................... Tabula Rasa ........................................ ScrollView .......................................... 8. Widgets de slection .......................... Sadapter aux circonstances ............... Listes des bons et des mchants ......... Contrle du Spinner ......................... Mettez vos lions en cage .................... Champs : conomisez 35 % de la frappe ! Galeries .............................................. 9. Samuser avec les listes ..................... Premires tapes ................................. Prsentation dynamique ..................... Mieux, plus robuste et plus rapide ..... Crer une liste... ................................. Et la vrier deux fois ................... Adapter dautres adaptateurs .............. 10. Utiliser de jolis widgets et de beaux conteneurs .................... Choisir ................................................ Le temps scoule comme un euve .. Mesurer la progression ....................... Utilisation donglets ........................... Tout faire basculer .............................. Fouiller dans les tiroirs ....................... Autres conteneurs intressants ........... 11. Utilisation des menus ...................... Variantes de menus ............................ Les menus doptions .......................... Menus contextuels ............................. Illustration rapide ............................... Encore de lination ........................... 12. Polices de caractres ....................... Sachez apprcier ce que vous avez .... Le problme des glyphes ................... 13. Intgrer le navigateur de WebKit ......................................... Un navigateur, et en vitesse ! .............

45 46 52 57 61 65 66 67 71 73 77 81 83 83 85 88 94 99 105 107 107 111 112 113 120 125 128 129 130 130 131 132 137 141 141 144 147 147

Chargement immdiat ......................... Navigation au long cours .................... Amuser le client .................................. Rglages, prfrences et options ........ 14. Afchage de messages surgissant ... Les toasts ............................................ Les alertes ........................................... Mise en uvre .................................... 15. Utilisation des threads ..................... Les handlers ........................................ Excution sur place ............................. O est pass le thread de mon interface utilisateur ? ......................................... Dsynchronisation .............................. viter les piges .................................. 16. Gestion des vnements du cycle de vie dune activit .......................... Lactivit de Schroedinger .................. Vie et mort dune activit ................... Partie III Stockage de donnes, services rseaux et API .......................... 17. Utilisation des prfrences ................ Obtenir ce que vous voulez ................. Dnir vos prfrences ....................... Un mot sur le framework .................... Laisser les utilisateurs choisir ............. Ajouter un peu de structure ................ Botes de dialogue .............................. 18. Accs aux chiers ............................. Allons-y ! ............................................ Lire et crire ....................................... 19. Utilisation des ressources ................ Les diffrents types de ressources ...... Thorie des chanes ............................ Vous voulez gagner une image ? ........ Les ressources XML ........................... Valeurs diverses .................................. Grer la diffrence ..............................

150 151 151 153 155 156 156 157 161 162 165 165 166 172 173 174 174

177 179 179 180 180 181 185 187 191 191 195 199 199 200 205 207 210 212

Table des matires

VII

20. Accs et gestion des bases de donnes locales ............................. Prsentation rapide de SQLite ............ Commencer par le dbut ..................... Mettre la table ..................................... Ajouter des donnes ............................ Le retour de vos requtes .................... Des donnes, des donnes, encore des donnes ............................. 21. Tirer le meilleur parti des bibliothques Java ...................... Limites extrieures .............................. Ant et JAR .......................................... Suivre le script .................................... Tout fonctionne... enn, presque ........ Relecture des scripts ........................... 22. Communiquer via Internet ............. REST et relaxation .............................. Partie IV Intentions (Intents) ............ 23. Cration de ltres dintentions ....... Quelle est votre intention ? ................. Dclarer vos intentions ....................... Rcepteurs dintention ........................ Attention la pause ............................ 24. Lancement dactivits et de sous-activits ............................. Activits paires et sous-activits ......... Dmarrage .......................................... Navigation avec onglets ...................... 25. Trouver les actions possibles grce lintrospection ................................ Faites votre choix ................................ Prfrez-vous le menu ? ...................... Demander lentourage ...................... 26. Gestion de la rotation ...................... Philosophie de la destruction .............. Tout est pareil, juste diffrent ............. Il ny a pas de petites conomies ! ......

217 218 219 219 220 221 224 227 228 228 229 233 233 235 236 241 243 244 245 247 247 249 250 250 255 259 260 263 264 265 265 266 270

Rotation maison .................................. Forcer le destin ................................... Tout comprendre ................................. Partie V Fournisseurs de contenus et services ................................................

272 274 276

277

27. Utilisation dun fournisseur de contenu (content provider) .............................. 279 Composantes dune Uri .................... Obtention dun descripteur ................. Cration des requtes ......................... Sadapter aux circonstances ............... Gestion manuelle des curseurs ........... Insertions et suppressions ................... Attention aux BLOB ! ........................ 28. Construction dun fournisseur de contenu ..................... Dabord, une petite dissection ............ Puis un peu de saisie ........................... tape n 1 : crer une classe Provider tape n 2 : fournir une Uri .............. tape n 3 : dclarer les proprits ..... tape n 4 : modier le manifeste ...... Avertissements en cas de modications 29. Demander et exiger des permissions Mre, puis-je ? .................................... Halte ! Qui va l ? ............................... Vos papiers, sil vous plat ! ............... 30. Cration dun service ...................... Service avec classe ............................. Il ne peut en rester quun ! ................. Destine du manifeste ........................ Sauter la clture .................................. 31. Appel dun service ........................... Transmission manuelle ....................... Capture de lintention ......................... 32. Alerter les utilisateurs avec des notications ........................ Types davertissements ....................... Les avertissements en action .............. 280 280 281 282 283 284 285 287 288 288 289 294 295 295 296 297 298 299 301 303 304 305 306 306 309 310 311 313 313 315

VIII

Lart du dveloppement Android

Partie VI Autres fonctionnalits dAndroid ............................................... 33. Accs aux services de localisation ................................... Fournisseurs de localisation : ils savent o vous vous cachez ........... Se trouver soi-mme .......................... Se dplacer ......................................... Est-on dj arriv ? Est-on dj arriv ? Est-on dj arriv ? ............................ Tester... Tester... .................................. 34. Cartographie avec MapView et MapActivity ................................. Termes dutilisation ........................... Empilements ...................................... Les composants essentiels ................. Testez votre contrle .......................... Terrain accident ................................ Couches sur couches ..........................

319 321 322 322 324 325 326 327 328 328 328 330 331 332

Moi et MyLocationOverlay .............. La cl de tout ...................................... 35. Gestion des appels tlphoniques ... Le Manager ......................................... Appeler ............................................... 36. Recherches avec SearchManager ... La chasse est ouverte .......................... Recherches personnelles ..................... Effectuer une recherche ...................... 37. Outils de dveloppement .................

334 335 337 338 338 341 342 343 349 351

Gestion hirarchique ........................... 351 DDMS (Dalvik Debug Monitor Service) 356 Gestion des cartes amovibles .............. 362 38. Pour aller plus loin .......................... Questions avec, parfois, des rponses . Aller la source .................................. Lire les journaux ................................. Index ....................................................... 363 363 364 365 367

propos de lauteur

Mark Murphy est le fondateur de CommonsWare et lauteur de The Busy Coders Guide to Android Development. Son exprience professionnelle va du conseil pour les 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, Jean-Baptiste Queru, Jeff Sharkey et Xavier Ducrohet. Les icnes utilises dans les exemples de ce livre proviennent du jeu dicnes Nuvola 1.

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

Prface ldition franaise

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

XVI

Lart du dveloppement Android

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

Introduction

Bienvenue !
Merci de votre intrt pour le dveloppement dapplications Android ! De plus en plus de personnes accdent dsormais aux services Internet via des moyens "non traditionnels" comme les terminaux mobiles, et ce nombre ne peut que crotre. Bien quAndroid soit rcent ses premiers terminaux sont apparus la n de 2008 , il ne fait aucun doute quil se rpandra rapidement grce linuence et limportance de lOpen Handset Alliance. Merci surtout de votre intrt pour ce livre ! Jespre sincrement que vous le trouverez utile, voire divertissant par moments.

Prrequis
Pour programmer des applications Android, vous devez au moins connatre les bases de Java. En effet, la programmation Android utilise la syntaxe de ce langage, plus une bibliothque de classes sapparentant un sous-ensemble de la bibliothque de Java SE (avec des extensions spciques). Si vous navez jamais programm en Java, initiez-vous ce langage avant de vous plonger dans la programmation Android. Ce livre nexplique pas comment tlcharger ou installer les outils de dveloppement Android, que ce soit le plugin Eclipse ou les outils ddis. Vous trouverez toutes ces 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.

Lart du dveloppement Android

ditions de ce livre
Cet ouvrage est le fruit dune collaboration entre Apress et CommonsWare. La version que vous tes en train de lire est la traduction de ldition dApress, qui est disponible sous forme imprime ou numrique. De son ct, CommonsWare met continuellement jour le contenu original et le met disposition des membres de son programme Warescription sous le titre The Busy Coders Guide to Android Development. La page http://commonsware.com/apress contient une FAQ concernant ce partenariat avec Apress.

Termes dutilisation du code source


Le code source des exemples de ce livre est disponible partir du site web de Pearson (www.pearson.fr), sur la page consacre cet ouvrage. Tous les projets Android sont placs sous les termes de la licence Apache 2.01, que nous vous invitons lire avant de rutiliser ces codes.

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

Partie

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

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

Les crans sont sous-dimensionns. Les claviers, quand ils existent, sont minuscules. Les dispositifs de pointage, quand il y en a, sont peu pratiques (qui na pas dj perdu son stylet ?) ou imprcis (les gros doigts se marient mal avec les crans tactiles).

Lart du dveloppement Android

La vitesse du processeur et la taille de la mmoire sont ridicules par rapport celles des machines de bureau et des serveurs auxquels nous sommes habitus. On peut utiliser le langage de programmation et le framework de dveloppement que lon souhaite, condition que ce soit celui qua choisi le constructeur du terminal. Etc.

En outre, les applications qui sexcutent sur un tlphone portable doivent grer le fait quil sagit justement dun tlphone. Les utilisateurs sont gnralement assez irrits lorsque leur mobile ne fonctionne pas, et cest la raison pour laquelle la campagne publicitaire "Can you hear me now?" de Verizon a si bien fonctionn ces dernires annes. De mme, ces mmes personnes seront trs mcontentes si votre programme perturbe leur tlphone, pour les raisons suivantes :

Il occupe tellement le processeur quelles ne peuvent plus recevoir dappels. Il ne sintgre pas correctement au systme dexploitation de leur mobile, de sorte que lapplication ne passe pas en arrire-plan lorsquelles reoivent ou doivent effectuer un appel. Il provoque un plantage de leur tlphone cause dune fuite de mmoire.

Le dveloppement de programmes pour un tlphone portable est donc diffrent de lcriture dapplications pour des machines de bureau, du dveloppement de sites web ou de la cration de programmes serveurs. Vous nirez par utiliser des outils et des frameworks diffrents et vos programmes auront des limites auxquelles vous ntes pas habitu. Android essaie de vous faciliter les choses :

Il fournit un langage de programmation connu (Java), avec des bibliothques relativement classiques (certaines API dApache, par exemple), ainsi quun support pour les outils auxquels vous tes peut-tre habitu (Eclipse, notamment). Il vous offre un framework sufsamment rigide et tanche pour que vos programmes sexcutent "correctement" sur le tlphone, sans interfrer avec les autres applications ou le systme dexploitation lui-mme.

Comme vous vous en doutez srement, lessentiel de ce livre sintresse ce framework et la faon dcrire des programmes qui fonctionnent dans son cadre et tirent parti de ses possibilits.

Contenu dun programme Android


Le dveloppeur dune application classique est "le seul matre bord". Il peut ouvrir la fentre principale de son programme, ses fentres lles les botes de dialogue, par 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

Chapitre 1

Tour dhorizon

susceptibles de sexcuter en mme temps que son programme. Sil doit interagir avec dautres applications, il passe gnralement par une API, comme JDBC (ou les frameworks qui reposent sur lui) pour communiquer avec MySQL ou un autre SGBDR. Android utilise les mmes concepts, mais proposs de faon diffrente, avec une structure permettant de mieux protger le fonctionnement des tlphones.

Activity (Activit)
La brique de base de linterface utilisateur sappelle activity (activit). Vous pouvez la considrer comme lquivalent Android de la fentre ou de la bote de dialogue dune application classique. Bien que des activits puissent ne pas avoir dinterface utilisateur, un code "invisible" sera dlivr le plus souvent sous la forme de fournisseurs de contenus (content provider) ou de services.

Content providers (fournisseurs de contenus)


Les fournisseurs de contenus offrent un niveau dabstraction pour toutes les donnes 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 content provider permet dobtenir ce rsultat tout en gardant un contrle total sur la faon dont on accdera aux donnes.

Intents (intentions)
Les intentions sont des messages systme. Elles sont mises par le terminal pour prvenir les applications de la survenue de diffrents vnements, que ce soit une modication matrielle (comme linsertion dune carte SD) ou larrive de donnes (telle la rception dun SMS), en passant par les vnements des applications elles-mmes (votre activit a t lance partir du menu principal du terminal, par exemple). Vous pouvez non seulement rpondre aux intentions, mais galement crer les vtres an de lancer dautres activits ou pour vous prvenir quune situation particulire a lieu (vous pouvez, par exemple, mettre lintention X lorsque lutilisateur est moins de 100 mtres dun emplacement Y).

Services
Les activits, les fournisseurs de contenus et les rcepteurs dintentions ont une dure de vie limite et peuvent tre teints tout moment. Les services sont en revanche conus pour durer et, si ncessaire, indpendamment de toute activit. Vous pouvez, par exemple, utiliser un service pour vrier les mises jour dun ux RSS ou pour jouer de la musique, mme si lactivit de contrle nest plus en cours dexcution.

Lart du dveloppement Android

Fonctionnalits votre disposition


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

Stockage
Vous pouvez empaqueter (packager) des chiers de donnes dans une application, pour y stocker ce qui ne changera jamais les icnes ou les chiers daide, par exemple. Vous pouvez galement rserver un petit emplacement sur le terminal lui-mme, pour y stocker une base de donnes ou des chiers contenant des informations ncessaires votre application et saisies par lutilisateur ou rcupres partir dune autre source. Si lutilisateur fournit un espace de stockage comme une carte SD, celui-ci peut galement tre lu et crit en fonction des besoins.

Rseau
Les terminaux Android sont gnralement conus pour tre utiliss avec Internet, via un support de communication quelconque. Vous pouvez tirer parti de cet accs Internet nimporte quel niveau, des sockets brutes de Java un widget de navigateur web intgr que vous pouvez intgrer dans votre application.

Multimdia
Les terminaux Android permettent denregistrer et de jouer de la musique et de la vido. Bien que les caractristiques spciques varient en fonction des modles, vous pouvez connatre celles qui sont disponibles et tirer parti des fonctionnalits multimdias offertes, que ce soit pour couter de la musique, prendre des photos ou enregistrer des mmos vocaux.

GPS
Les fournisseurs de positionnement, comme GPS, permettent dindiquer aux applications o se trouve le terminal. Il vous est alors possible dafcher des cartes ou dutiliser ces donnes gographiques pour retrouver la trace du terminal sil a t vol, par exemple.

Services tlphoniques
videmment, les terminaux Android sont gnralement des tlphones, ce qui permet vos programmes de passer des appels, denvoyer et de recevoir des SMS et de raliser tout ce que vous tes en droit dattendre dune technologie tlphonique moderne.

2
Structure dun projet
Le systme de construction dun programme Android est organis sous la forme dune arborescence de rpertoires spcique un projet, exactement comme nimporte quel projet Java. Les dtails, cependant, sont spciques Android et sa prparation de lapplication qui sexcutera sur le terminal ou lmulateur. Voici un rapide tour dhorizon de la structure dun projet, qui vous aidera mieux comprendre les exemples de code utiliss dans ce livre et que vous pouvez tlcharger sur le site web de Pearson, www.pearson.fr, sur la page consacre cet ouvrage.

Contenu de la racine
La cration dun projet Android (avec la commande android create project ou via un environnement de programmation adapt Android) place plusieurs lments dans le rpertoire racine du projet :

AndroidManifest.xml est un chier XML qui dcrit lapplication construire et les composants activits, services, etc. fournis par celle-ci.

10

Lart du dveloppement Android

build.xml est un script Ant1 permettant de compiler lapplication et de linstaller sur le terminal (ce chier nest pas prsent avec un environnement de programmation adapt, tel Eclipse). default.properties et local.properties sont des chiers de proprits utiliss par le script prcdent. bin/ contient lapplication compile. gen/ contient le code source produit par les outils de compilation dAndroid. libs/ contient les chiers JAR extrieurs ncessaires lapplication. src/ contient le code source Java de lapplication. res/ contient les ressources icnes, descriptions des lments de linterface graphique (layouts), etc. empaquetes avec le code Java compil. tests/ contient un projet Android entirement distinct, utilis pour tester celui que vous avez cr. assets/ contient les autres chiers statiques fournis avec lapplication pour son dploiement sur le terminal.

la sueur de votre front


Lors de la cration dun projet Android (avec android create project, par exemple), vous devez fournir le nom de classe ainsi que le chemin complet (paquetage) de lactivit "principale" de lapplication (com.commonsware.android.UneDemo, par exemple). Vous constaterez alors que larborescence src/ de ce projet contient la hirarchie des rpertoires dnis par le paquetage ainsi quun squelette dune sous-classe dActivity reprsentant lactivit principale (src/com/commonsware/android/UneDemo.java). Vous pouvez bien sr modier ce chier et en ajouter dautres larborescence src/ selon les besoins de votre application. La premire fois que vous compilerez le projet (avec ant, par exemple), la chane de production dAndroid crera le chier R.java dans le paquetage de lactivit "principale". Ce chier contient un certain nombre de dnitions de constantes lies aux diffrentes ressources de larborescence res/. Il est dconseill de le modier manuellement : laissez les outils dAndroid sen occuper. Vous rencontrerez de nombreuses rfrences R.java dans les exemples de ce livre (par exemple, on dsignera lidentiant dun layout par R.layout.main).

1. http://ant.apache.org/.

Chapitre 2

Structure dun projet

11

La suite de lhistoire
Comme on la dj indiqu, larborescence res/ contient les ressources, cest--dire des chiers statiques fournis avec lapplication, soit sous leur forme initiale soit, parfois, sous une forme prtraite. Parmi les sous-rpertoires de res/, citons :

res/drawable/ pour les images (PNG, JPEG, etc.) ; res/layout/ pour les descriptions XML de la composition de linterface graphique ; res/menu/ pour les descriptions XML des menus ; res/raw/ pour les chiers gnraux (un chier CSV contenant les informations dun compte, par exemple) ; res/values/ pour les messages, les dimensions, etc. ; res/xml/ pour les autres chiers XML gnraux que vous souhaitez fournir.

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

Le fruit de votre travail


Lorsque vous compilez un projet (avec ant ou un IDE), le rsultat est plac dans le 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. bin/votreapp.ap_ (o votreapp est le nom de lapplication) contient les ressources de celle-ci, sous la forme dun chier ZIP. bin/votreapp-debug.apk ou bin/votreapp-unsigned.apk est la vritable application Android.

Le chier .apk est une archive ZIP contenant le chier .dex, la version compile des ressources (resources.arsc), les ventuelles ressources non compiles (celles qui se trouvent sous res/raw/, par exemple) et le chier AndroidManifest.xml. Cette archive est signe : la partie -debug du nom de chier indique quelle la t laide dune cl de dbogage qui fonctionne avec lmulateur alors que -unsigned prcise que lapplication a t construite pour tre dploye (ant release) : larchive APK doit alors tre signe laide de jarsigner et dune cl ofcielle.

3
Contenu du manifeste
Le point de dpart de toute application Android est son chier manifeste, AndroidManifest.xml, qui se trouve la racine du projet. Cest dans ce chier que lon dclare ce que contiendra lapplication les activits, les services, etc. On y indique galement la faon dont ces composants seront relis au systme Android lui-mme en prcisant, par 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 modications mineures. En revanche, le manifeste de lapplication de dmonstration Android fournie contient plus de 1 000 lignes. Vos applications se situeront probablement entre ces deux extrmits. La plupart des parties intressantes du manifeste seront dcrites en dtail dans les chapitres consacrs aux fonctionnalits dAndroid qui leur sont associes llment service, par exemple, est dtaill au Chapitre 30. Pour le moment, il vous suft de comprendre le rle de ce chier et la faon dont il est construit.

14

Lart du dveloppement Android

Au dbut, il y avait la racine


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

Cet lment comprend la dclaration de lespace de noms android. Curieusement, les manifestes produits nappliquent cet espace quaux attributs, pas aux lments (on crit donc manifest, pas android:manifest). Nous vous conseillons de conserver cette convention, sauf si Android la modie dans une version future.
Lessentiel des informations que vous devez fournir cet lment est lattribut package (qui nutilise pas non plus lespace de noms). Cest l que vous pouvez indiquer le nom du paquetage Java qui sera considr comme la "base" de votre application. Dans la suite du chier, vous pourrez alors simplement utiliser le symbole point pour dsigner ce paquetage : si vous devez, par exemple, faire rfrence com.commonsware.android.search.Snicklefritz dans le manifeste de cet exemple, il sufra dcrire .Snicklefritz puisque com.commonsware.android.search est dni comme le paquetage de lapplication.

Info

Permissions, instrumentations et applications


Sous llment manifest, vous trouverez les lments suivants :

Des lments uses-permission indiquant les permissions dont a besoin votre application pour fonctionner correctement (voir Chapitre 19 pour plus de dtails). Des lments permission dclarant les permissions que les activits ou les services peuvent exiger des autres applications pour utiliser les donnes et le code de lapplication (voir galement Chapitre 19). Des lments instrumentation qui indiquent le code qui devrait tre appel pour les vnements systme essentiels, comme le lancement des activits. Ces lments sont utiliss pour la journalisation ou la surveillance. Des lments uses-library pour relier les composants facultatifs dAndroid, comme les services de golocalisation. ventuellement, un lment uses-sdk indiquant la version du SDK Android avec laquelle a t construite lapplication. Un lment application qui dnit le cur de lapplication dcrite par le manifeste.

Chapitre 3

Contenu du manifeste

15

Le manifeste de lexemple qui suit contient des lments uses-permission qui indiquent certaines fonctionnalits du terminal dont lapplication a besoin ici, lapplication doit avoir le droit de dterminer sa position gographique courante. Llment application dcrit les activits, les services et tout ce qui constitue lapplication elle-mme.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android"> <uses-permission android:name="android.permission.ACCESS_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_GPS" /> <uses-permission android:name="android.permission.ACCESS_ASSISTED_GPS" /> <uses-permission android:name="android.permission.ACCESS_CELL_ID" /> <application> ... </application> </manifest>

Que fait votre application ?


Le plat de rsistance du chier manifeste est dcrit par les ls de llment application. Par dfaut, la cration dun nouveau projet Android nindique quun seul lment 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 lactivit, android:label pour le nom afch de lactivit et (souvent) un lment ls intentfilter dcrivant les conditions sous lesquelles cette activit safchera. Llment activity de base congure votre activit pour quelle apparaisse dans le lanceur et que les utilisateurs puissent lexcuter. Comme nous le verrons plus tard, un mme projet peut dnir plusieurs activits.

16

Lart du dveloppement Android

Il peut galement y avoir un ou plusieurs lments receiver dcrivant les non-activits qui devraient se dclencher sous certaines conditions la rception dun SMS, par exemple. On les appelle rcepteurs dintentions (intent receivers) et ils sont dcrits au Chapitre 23. De mme, un ou plusieurs lments provider peuvent tre prsents an dindiquer les fournisseurs de contenus (content providers) les composants qui fournissent les donnes vos activits et, avec votre permission, aux activits dautres applications du terminal. Ces lments enveloppent les bases de donnes ou les autres stockages de donnes en une API unique que toute application peut ensuite utiliser. Nous verrons plus loin comment crer des fournisseurs de contenus et comment utiliser les fournisseurs que vous ou dautres ont crs. Enn, il peut y avoir un ou plusieurs lments service dcrivant les services, cest--dire les parties de code qui peuvent fonctionner indpendamment de toute activit et en permanence. Lexemple classique est celui du lecteur MP3, qui permet de continuer couter de la musique, mme si lutilisateur ouvre dautres activits et que linterface utilisateur nest pas afche au premier plan. La cration et lutilisation des services sont dcrites aux Chapitres 30 et 31.

Faire le minimum
Android, comme la plupart des systmes dexploitation, est rgulirement amlior, ce qui donne lieu de nouvelles versions du systme. Certains de ces changements affectent le SDK car de nouveaux paramtres, classes ou mthodes apparaissent, qui nexistaient pas dans les versions prcdentes. Pour tre sr que votre application ne sexcutera que pour une version prcise (ou suprieure) dun environnement Android, ajoutez un lment uses-sdk comme ls de llment manifest du chier AndroidManifest.xml. uses-sdk na quun seul attribut, minSdkVersion, qui indique la version minimale du SDK exige par votre application :
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.search"> <uses-sdk minSdkVersion="2" /> ... </manifest>

lheure o ce livre est crit, minSdkVersion peut prendre trois valeurs :


1 pour indiquer le premier SDK dAndroid, la version 1.0 ; 2 pour indiquer la version 1.1 du SDK dAndroid ; 3 pour indiquer la version 1.5.

Chapitre 3

Contenu du manifeste

17

Labsence dun lment uses-sdk revient utiliser 1 comme valeur de minSdkVersion. Cela dit, la boutique Android semble tenir ce que lon indique explicitement la valeur de minSdkVersion : faites en sorte dutiliser un lment uses-sdk si vous comptez distribuer votre programme par ce biais. Si cet lment est prsent, lapplication ne pourra sinstaller que sur les terminaux compatibles. Vous navez pas besoin de prciser la dernire version du SDK mais, si vous en choisissez une plus ancienne, cest vous de vrier que votre application fonctionnera sur toutes les versions avec lesquelles elle prtend tre compatible. Si, par exemple, vous omettez uses-sdk, cela revient annoncer que votre application fonctionnera sur toutes les versions existantes du SDK et vous devrez videmment tester que cest bien le cas.

Version = contrle
Si vous comptez distribuer votre application via la boutique Android ou dautres supports, vous devrez srement ajouter les attributs android:versionCode et android:versionName llment manifest an de faciliter le processus de mise jour des applications. android:versionName est une chane lisible reprsentant le nom ou le numro de version de votre application. Vous pouvez, par exemple, utiliser des valeurs comme "3.0" ou "System V", en fonction de vos prfrences. android:versionCode est un entier cens reprsenter le numro de version de lapplication. Le systme lutilise pour savoir si votre version est plus rcente quune autre "rcent" tant dni par "la valeur dandroid:versionCode est plus leve". Pour obtenir cette valeur, vous pouvez convertir le contenu dandroid:versionName en nombre ou simplement incrmenter la valeur chaque nouvelle version.

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

II

4
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 sufsamment concis pour ne pas noyer le lecteur dans les dtails. Cependant, le programme "Bonjour tous !" classique nest absolument pas interactif (il se contente dcrire ces mots sur la console) et est donc assez peu stimulant. Ce chapitre prsentera donc un projet qui utilisera malgr tout un bouton et lheure courante pour montrer le fonctionnement dune activit Android simple.

Terminaux virtuels et cibles


Pour construire les projets, nous supposons que vous avez tlcharg le SDK (et, ventuellement, le plugin ADT dEclipse). Avant de commencer, nous devons prsenter la notion de "cible" car elle risque de vous surprendre et elle est relativement importante dans le processus de dveloppement.

22

Lart du dveloppement Android

Comme son nom lindique, un AVD (Android Virtual Device), est un terminal virtuel par opposition aux vrais terminaux Android comme le G1 ou le Magic de HTC. Les AVD sont utiliss par lmulateur fourni avec le SDK et vous permettent de tester vos programmes avant de les dployer sur les vritables terminaux. Vous devez indiquer lmulateur un terminal virtuel an quil puisse prtendre quil est bien le terminal dcrit par cet AVD.

Cration dun AVD


Pour crer un AVD, vous pouvez lancer la commande android create avd ou utiliser Eclipse ; dans les deux cas, vous devez indiquer une cible qui prcise la classe de terminaux qui sera simule par lAVD. lheure o cette dition est publie, il existe trois cibles :

une qui dsigne un terminal Android 1.1, comme un G1 HTC de base qui naurait pas t mis jour ; une deuxime qui dsigne un terminal Android 1.5, sans le support de Google Maps ; une troisime qui dsigne un terminal Android 1.5 disposant du support de Google Maps, ce qui est le cas de la majorit des terminaux Android actuels.

Si vous dveloppez des applications utilisant Google Maps, vous devez donc utiliser un ADV ayant la cible 3. Sinon la cible 2 conviendra parfaitement. Actuellement, la plupart des G1 ayant t mis jour avec Android 1.5, la cible 1 nest plus trs utile. Vous pouvez crer autant dAVD que vous le souhaitez du moment que vous avez assez despace disque disponible sur votre environnement de dveloppement : si vous avez besoin dun pour chacune des trois cibles, libre vous ! Noubliez pas, cependant, que linstallation dune application sur un AVD naffecte pas les autres AVD que vous avez crs.

Choix dune cible


Lorsque vous crez un projet (avec android create project ou partir dEclipse), vous devez galement indiquer la classe de terminal vise par celui-ci. Les valeurs possibles tant les mmes que ci-dessus, crer un projet avec une cible 3 donne les indications suivantes :

Vous avez besoin dAndroid 1.5. Vous avez besoin de Google Maps.

Lapplication nale ne sinstallera donc pas sur les terminaux qui ne correspondent pas ces critres.

Chapitre 4

Cration dun squelette dapplication

23

Voici quelques rgles pour vous aider grer les cibles :

Demandez-vous ce dont vous avez rellement besoin. Ne crez un projet avec une cible 3 que si vous utilisez Google Maps, notamment. Sinon une cible 2 est prfrable vous exigerez toujours Android 1.5, mais votre application pourra sexcuter sur des terminaux qui ne disposent pas de Google Maps. Testez autant de cibles que possible. Vous pourriez tre tent par la cible 1 pour viser le plus grand nombre de terminaux Android ; cependant, vous devrez alors tester votre application sur un AVD ayant une cible 1 et un AVD ayant une cible 2 (et il serait galement souhaitable de la tester avec un AVD ayant une cible 3, au cas o). Vriez quune nouvelle cible na pas t ajoute par une nouvelle version dAndroid. Il devrait y avoir quelques nouvelles valeurs avec chaque version majeure (2.0 ou 1.6, par exemple), voire pour les versions intermdiaires (1.5r1 ou 1.5r2). Assurez-vous de tester votre application sur ces nouvelles cibles chaque fois que cela est possible car certains peuvent utiliser ces nouvelles versions de terminaux ds quelles sortent. Le fait de tester avec des AVD, quelle que soit la cible, ne peut pas se substituer aux tests sur du vrai matriel. Les AVD sont conus pour vous fournir des "environnements jetables", permettant de tester un grand nombre denvironnements, mme ceux qui nexistent pas encore rellement. Cependant, vous devez mettre votre application lpreuve dau moins un terminal Android. En outre, la vitesse de votre mulateur peut ne pas correspondre celle du terminal selon votre systme, elle peut tre plus rapide ou plus lente.

Commencer par le dbut


Avec Android, tout commence par la cration dun projet. En Java classique, vous pouvez, si vous le souhaitez, vous contenter dcrire un programme sous la forme dun unique chier, le compiler avec javac puis lexcuter avec Java. Android est plus complexe mais, pour faciliter les choses, Google a fourni des outils daide la cration dun projet. Si vous utilisez un IDE compatible avec Android, comme Eclipse et le plugin Android (fourni avec le SDK), vous pouvez crer un projet directement partir de cet IDE (menu Fichier > Nouveau > Projet, puis choisissez Android > Android Project). Si vous vous servez doutils non Android, vous pouvez utiliser le script android, qui se trouve dans le rpertoire tools/ de linstallation du SDK en lui indiquant que vous souhaitez crer un projet (create project). Il faut alors lui indiquer la version de la cible, le rpertoire racine du projet, le nom de lactivit et celui du paquetage o tout devra se trouver :

24

Lart du dveloppement Android

android create project \ --target 2 \ --path chemin/vers/mon/projet \ --activity Now \ --package com.commonsware.android.skeleton

Cette commande cre automatiquement les chiers que nous avons dcrits au Chapitre 2 et que nous utiliserons dans le reste de ce chapitre. Vous pouvez galement tlcharger les rpertoires des projets exemples de ce livre sous la forme de chiers ZIP partir du site web de Pearson1. Ces projets sont prts tre utiliss : vous navez donc pas besoin dutiliser android create project lorsque vous aurez dcompress ces exemples.

Lactivit
Le rpertoire src/ de votre projet contient une arborescence de rpertoires Java classique, cre daprs le paquetage Java que vous avez utilis pour crer le projet (com.commonsware.android.skeleton produit donc src/com/commonsware/android/ skeleton). Dans le rpertoire le plus bas, vous trouverez un chier source nomm Now.java, dans lequel sera stock le code de votre premire activit. Celle-ci sera constitue dun unique bouton qui afchera lheure laquelle on a appuy dessus pour le dernire fois (ou lheure de lancement de lapplication si on na pas encore appuy sur ce bouton). Ouvrez Now.java dans un diteur de texte et copiez-y le code suivant :
package com.commonsware.android.skeleton; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import java.util.Date; public class Now extends Activity implements View.OnClickListener { Button btn; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); btn = new Button(this); btn.setOnClickListener(this); updateTime(); setContentView(btn); }

1. http://pearson.fr.

Chapitre 4

Cration dun squelette dapplication

25

public void onClick(View view) { updateTime(); } private void updateTime() { btn.setText(new Date().toString()); } }

Si vous avez tlcharg les chiers partir du site web de Pearson, vous pouvez vous contenter dutiliser directement le projet Skeleton/Now. Examinons maintenant chacune des parties de ce code.

Dissection de lactivit
La dclaration de paquetage doit tre identique celle que vous avez utilise pour crer le projet. Comme pour tout projet Java, vous devez importer les classes auxquelles vous faites rfrence. La plupart de celles qui sont spciques Android se trouvent dans le paquetage android :
package com.commonsware.android.skeleton; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import java.util.Date;

Notez bien que toutes les classes de Java SE ne sont pas utilisables par les programmes Android. Consultez le guide de rfrence des classes 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.

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

26

Lart du dveloppement Android

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 an 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 nous congurons la vue du contenu de lactivit pour que ce soit le bouton lui-mme (avec setContentView()).
Tous les widgets drivent de la classe de base View. Bien que lon construise gnralement linterface graphique partir dune hirarchie de vues, nous nutiliserons ici quune seule vue.

Info

Nous prsenterons ce Bundle icicle magique au Chapitre 16. Pour linstant, considrons-le comme un gestionnaire opaque, que toutes les activits reoivent lors de leur cration.
public void onClick(View view) { updateTime(); }

Avec Swing, un clic sur un JButton dclenche un ActionEvent qui est transmis lActionListener congur pour ce bouton. Avec Android, en revanche, un clic sur un bouton provoque lappel de la mthode onClick() sur linstance OnClickListener congure pour le bouton. Lcouteur reoit la vue qui a dclench le clic (ici, il sagit du bouton). Dans notre cas, nous nous contentons dappeler la mthode prive updateTime() :
private void updateTime() { btn.setText(new Date().toString()); }

Chapitre 4

Cration dun squelette dapplication

27

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 (avec tools/emulator dans votre installation du SDK Android), pour obtenir une gure analogue celle de la Figure 4.1. Noubliez pas de prciser un AVD avec loption -avd.
Figure 4.1 Lcran daccueil dAndroid.

2. Installez le paquetage (avec tools/adb install /racine/projet/bin/Now-debug .apk). 3. Consultez la liste des applications installes sur lmulateur et recherchez celle qui sappelle Now (voir Figure 4.2). 4. Ouvrez cette application. Vous devriez voir apparatre un cran dactivit comme celui de la Figure 4.3.

28

Lart du dveloppement Android

Figure 4.2 Le lanceur dapplications dAndroid.

Figure 4.3 Dmonstration de lactivit Now.

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

5
Utilisation des layouts XML
Bien quil soit techniquement possible de crer et dattacher des composants widgets une activit en utilisant uniquement du code Java comme nous lavons fait au Chapitre 4, on prfre gnralement employer un chier de positionnement (layout) cod en XML. Linstanciation dynamique des widgets est rserve aux scnarios plus 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 spcication des relations existant entre les composants widgets et avec leurs conteneurs (voir Chapitre 7) 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.

30

Lart du dveloppement Android

Chaque chier XML contient une arborescence dlments prcisant le layout des widgets et les conteneurs qui composent une View. Les attributs de ces lments sont des proprits qui dcrivent laspect dun widget ou le comportement dun conteneur. Un lment Button avec un attribut android:textStyle = "bold", par exemple, signie que le texte apparaissant sur ce bouton sera en gras. Loutil aapt du SDK dAndroid utilise ces layouts ; il est appel automatiquement par la chane de production du projet (que ce soit via Eclipse ou par le traitement du chier build.xml de Ant). Cest aapt qui produit le chier source R.java du projet, qui vous permet daccder directement aux layouts et leurs composants widgets depuis votre code Java, comme nous le verrons bientt.

Pourquoi utiliser des layouts XML ?


La plupart de ce qui peut tre fait avec des chiers de positionnement XML peut galement tre ralis avec du code Java. Vous pourriez, par exemple, utiliser setTypeface() pour quun bouton afche son texte en gras au lieu dutiliser une proprit dans un chier XML. Ces chiers sajoutant tous ceux que vous devez dj grer, il faut donc quil y ait une bonne raison de les utiliser. La principale est probablement le fait quils permettent de crer des outils de dnition des vues : le constructeur dinterfaces graphiques dun IDE comme Eclipse ou un assistant ddi la cration des interfaces graphiques dAndroid, comme DroidDraw 1, par exemple. Ces logiciels pourraient, en principe, produire du code Java au lieu dun document XML, mais il est bien plus simple de relire la dnition dune interface graphique an de la modier lorsque cette dnition est exprime dans un format structur comme XML au lieu dtre code dans un langage de programmation. En outre, sparer ces dnitions XML du code Java rduit les risques quune modication du code source perturbe lapplication. XML est un bon compromis entre les besoins des concepteurs doutils et ceux des programmeurs. En outre, lutilisation de XML pour la dnition des interfaces graphiques est devenue monnaie courante. XAML2 de Microsoft, Flex3 dAdobe et XUL4 de Mozilla utilisent toutes une approche quivalente de celle dAndroid : placer les dtails des positionnements dans un chier XML en permettant de les manipuler partir des codes sources ( laide de JavaScript pour XUL, par exemple). De nombreux frameworks graphiques moins connus, comme ZK5, utilisent galement XML pour la dnition de leurs vues. Bien que
1. 2. 3. 4. 5.

http://droiddraw.org/. http://windowssdk.msdn.microsoft.com/en-us/library/ms752059.aspx . http://www.adobe.com/products/ex/. http://www.mozilla.org/projects/xul/. http://www.zkoss.org/.

Chapitre 5

Utilisation des layouts XML

31

"suivre le troupeau" ne soit pas toujours le meilleur choix, cette politique a lavantage de faciliter la transition vers Android partir dun autre langage de description des vues reposant sur XML.

Contenu dun chier layout


Voici le bouton de lapplication du chapitre prcdent, converti en un chier XML que vous trouverez dans le rpertoire chap5/Layouts/NowRedux de larchive des codes sources, tlchargeable sur le site www.pearson.fr, sur la page ddie cet ouvrage. Pour faciliter la recherche des codes des exemples, cette archive est dcoupe selon les chapitres du livre.
<?xml version="1.0" encoding="utf-8"?> <Button xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/button" android:text="" android:layout_width="fill_parent" android:layout_height="fill_parent"/>

Ici, le nom de llment XML est celui de la classe du widget, Button. Ce dernier tant fourni par Android, il suft dutiliser simplement son nom de classe. Dans le cas dun widget personnalis, driv dandroid.view.View, il faudrait utiliser un nom pleinement quali, contenant le nom du paquetage (com.commonsware.android.MonWidget, par exemple). Llment racine doit dclarer lespace de noms XML dAndroid :
xmlns:android="http://schemas.android.com/apk/res/android"

Tous les autres lments sont des ls de la racine et hritent de cette dclaration. Comme lon souhaite pouvoir faire rfrence ce bouton partir de notre code Java, il faut lui associer un identiant avec lattribut android:id. Nous expliquerons plus prcisment ce mcanisme plus loin dans ce chapitre. Les autres attributs sont les proprits de cette instance de Button :

android:text prcise le texte qui sera afch au dpart sur le bouton (ici, il sagit dune chane vide). android:layout_width et android:layout_height prcisent que la largeur et la hauteur du bouton rempliront le "parent", cest--dire ici lcran entier ces attributs seront prsents plus en dtail au Chapitre 7.

Ce widget tant le seul contenu de notre activit, nous navons besoin que de cet lment. Les vues plus complexes ncessitent une arborescence dlments, an de reprsenter les widgets et les conteneurs qui contrlent leur positionnement. Dans la suite de ce livre, nous utiliserons des positionnements XML chaque fois que cela est ncessaire : vous

32

Lart du dveloppement Android

trouverez donc des dizaines dexemples plus complexes que vous pourrez utiliser comme point de dpart pour vos propres projets.

Identiants des widgets


De nombreux widgets et conteneurs ne peuvent apparatre que dans le chier de positionnement et ne seront pas utiliss par votre code Java. Le plus souvent, un label statique (TextView), par exemple, na besoin dtre dans le chier XML que pour indiquer lemplacement o il doit apparatre dans linterface. Ce type dlment na donc pas besoin dun attribut android:id pour lui donner un nom. En revanche, tous les lments dont vous avez besoin dans votre source Java doivent possder cet attribut. La convention consiste utiliser le format @+id/nom_unique comme valeur didentiant, o nom_unique reprsente le nom local du widget, qui doit tre unique. Dans lexemple de la section prcdente, lidentiant du widget Button tait @+id/button. Android utilise galement quelques valeurs android:id spciques, de la forme @android:id/.... Nous les rencontrerons dans diffrents chapitres de ce livre, notamment aux Chapitres 8 et 10.

Utilisation des widgets dans le code Java


Une fois que vous avez douloureusement congur les widgets et les conteneurs dans un chier de positionnement XML nomm main.xml et que vous lavez plac dans le 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 chier XML est dsormais accessible partir de la classe R. Tous les positionnements dnis se trouvent sous R.layout, indiqus par le nom de base du chier R.layout.main dsigne donc main.xml. Pour accder nos widgets, nous utilisons ensuite la mthode findViewById(), en lui passant lidentiant numrique du widget concern. Cet identiant a t produit par Android dans la classe R et est de la forme R.id.qquechose (o qquechose est le widget que vous recherchez). Ces widgets sont des sous-classes de View, exactement comme linstance de Button que nous avions cre au Chapitre 4.

Chapitre 5

Utilisation des layouts XML

33

Fin de lhistoire
Dans le premier exemple Now, le texte du bouton afchait lheure laquelle on avait appuy dessus pour la dernire fois (ou lheure laquelle lactivit avait t lance). Lessentiel du code fonctionne encore, mme dans cette nouvelle version (que nous 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 chier XML de positionnement (avec setContentView(R.layout.main)). Le chier source R.java sera mis jour lorsque le projet sera recompil, an dinclure une rfrence au chier de positionnement (main.xml du rpertoire res/layout). La seconde diffrence est quil faut retrouver linstance de notre bouton en appelant la mthode findViewById(). Comme lidentiant de ce bouton est @+id/button, nous pouvons dsigner son identiant numrique par R.id.button. Il reste ensuite mettre en place lcouteur dvnement et congurer son label.

34

Lart du dveloppement Android

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

6
Utilisation des widgets de base
Chaque kit de dveloppement graphique possde des widgets de base : champs de saisie, labels, boutons, etc. Celui dAndroid ny fait pas exception et leur tude fournit une bonne introduction au fonctionnement des widgets dans les activits Android.

Labels
Le label (TextView pour Android) est le widget le plus simple. Comme dans la plupart des kits de dveloppement, les labels sont des chanes de textes non modiables par les utilisateurs. Ils servent gnralement identier les widgets qui leur sont adjacents ("Nom : ", par exemple, plac ct dun champ de saisie). En Java, un label est une instance de la classe TextView. Cependant, ils seront le plus souvent crs dans les chiers layout XML en ajoutant un lment TextView dot dune proprit android:text pour dnir le texte qui lui est associ. Si vous devez changer des labels en fonction dun certain critre linternationalisation, par exemple , vous pouvez utiliser la place une rfrence de ressource dans le code XML, comme nous

36

Lart du dveloppement Android

lexpliquerons au Chapitre 9. Un lment TextView possde de nombreux autres attributs, notamment :


android:typeface pour dnir le type de la police du label (monospace, par exemple) ; android:textStyle pour indiquer si le texte doit tre en gras (bold), en italique (italic) ou les deux (bold_italic) ; android:textColor pour dnir la couleur du texte du label, au format RGB hexadcimal (#FF0000 pour un texte rouge, par exemple).

Voici le contenu du chier de positionnement du projet Basic/Label :


<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Vous vous attendiez quelque chose de plus profond ?" />

Comme le montre la Figure 6.1, ce chier seul, avec le squelette Java fourni par la chane de production dAndroid (android create project), produira lapplication voulue.
Figure 6.1 Lapplication LabelDemo.

Boutons
Nous avons dj utilis le widget Button au Chapitre 4. Button tant une sous-classe de TextView, tout ce qui a t dit dans la section prcdente concernant le formatage du texte sapplique galement au texte dun bouton.

Chapitre 6

Utilisation des widgets de base

37

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

Le rsultat, qui utilise simplement lactivit produite automatiquement, est prsent la Figure 6.2.
Figure 6.2 Lapplication ImageViewDemo.

38

Lart du dveloppement Android

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. 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 ajoutet-il une nouvelle ligne ?).

Outre ces proprits, vous pouvez congurer les champs pour quils utilisent des mthodes de saisie spcialises, avec les attributs android:numeric pour imposer une saisie uniquement numrique, android:password pour masquer la saisie dun mot de passe et android:phoneNumber pour la saisie des numros de tlphone. Pour crer une mthode de saisie particulire (an, par exemple, de saisir des codes postaux ou des numros de scurit sociale), il faut implmenter linterface InputMethod puis congurer le champ pour quil utilise cette mthode, laide de lattribut android:inputMethod. Voici, par exemple, la description dun EditText tir du projet Basic/Field :
<?xml version="1.0" encoding="utf-8"?> <EditText xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/field" android:layout_width="fill_parent" android:layout_height="fill_parent" android:singleLine="false" />

Vous remarquerez que la valeur dandroid:singleLine est false, ce qui signie que les utilisateurs pourront saisir plusieurs lignes de texte dans ce champ. Le chier FieldDemo.java de ce projet remplit le champ de saisie avec un peu de prose :
package com.commonsware.android.basic; import android.app.Activity; import android.os.Bundle;

Chapitre 6

Utilisation des widgets de base

39

import android.widget.EditText; public class FieldDemo extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); EditText fld=(EditText)findViewById(R.id.field); fld.setText("Publie sous les termes de la licence Apache 2.0 " + "(la \"Licence\") ; pour utiliser ce fichier " + "vous devez respecter la Licence dont vous pouvez " + "obtenir une copie a lURL " + "http://www.apache.org/licenses/LICENSE-2.0"); } }

La Figure 6.3 montre le rsultat obtenu.


Figure 6.3 Lapplication FieldDemo.

Info

Lmulateur dAndroid nautorise quune seule application dun mme paquetage Java dans le lanceur (application Launcher). Comme tous les exemples de ce chapitre appartiennent au paquetage com.commonsware.android.basic, il napparatra quun seul exemple la fois dans le lanceur.

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

40

Lart du dveloppement Android

Cases cocher
La case cocher classique peut tre dans deux tats : coche ou dcoche. Un clic sur la case inverse son tat pour indiquer un choix ("Livrer ma commande en urgence", par exemple). Le widget CheckBox dAndroid permet dobtenir ce comportement. Comme il drive de la classe TextView, les proprits de celles-ci comme android:textColor permettent galement de formater ce widget. Dans votre code Java, vous pouvez utiliser les mthodes suivantes :

isChecked() pour savoir si la case est coche ; setChecked() pour forcer la case dans ltat coch ou dcoch ; toggle() pour inverser ltat de la case, comme si lutilisateur avait cliqu dessus.

Vous pouvez galement enregistrer un objet couteur (il sagira, ici, dune instance dOnCheckedChangeListener) pour tre prvenu des changements dtat de la case. Voici la dclaration XML dune case cocher, tire du projet Basic/CheckBox :
<?xml version="1.0" encoding="utf-8"?> <CheckBox xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/check" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cette case est: decochee" />

Le chier CheckBoxDemo.java correspondant rcupre cette case cocher et congure son comportement :
public class CheckBoxDemo extends Activity implements CompoundButton.OnCheckedChangeListener { CheckBox cb; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); cb=(CheckBox)findViewById(R.id.check); cb.setOnCheckedChangeListener(this); } public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { cb.setText("Cette case est cochee"); } else { cb.setText("Cette case est decochee"); } } }

Chapitre 6

Utilisation des widgets de base

41

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

Figure 6.5 La mme application avec la case coche.

42

Lart du dveloppement Android

Boutons radio
Comme dans les autres outils de dveloppement, les boutons radio dAndroid ont deux tats, telles les cases cocher, mais peuvent tre regroups de sorte quun seul bouton radio puisse tre coch par groupe un instant donn. Comme CheckBox, RadioButton hrite de la classe CompoundButton, qui drive ellemme de TextView. Toutes les proprits standard de TextView pour la police, le style, la couleur, etc. sappliquent donc galement aux boutons radio. Vous pouvez par consquent appeler isChecked() sur un RadioButton pour savoir sil est coch, toggle() pour le slectionner, etc. exactement comme avec une CheckBox. La plupart du temps, les widgets RadioButton sont placs dans un conteneur RadioGroup qui permet de lier les tats des boutons quil regroupe an quun seul puisse tre slectionn un instant donn. En affectant un identiant android:id au RadioGroup dans le chier de description XML, ce groupe devient accessible au code Java, qui peut alors lui appliquer les mthodes suivantes :

check() pour tester un bouton radio partir de son identiant (avec groupe.check (R.id.radio1)) ; clearCheck() pour dcocher tous les boutons du groupe ; getCheckedRadioButtonId() pour obtenir lidentiant du bouton radio actuellement coch (cette mthode renvoie 1 si aucun bouton nest coch).

Voici, par exemple, une description XML dun groupe de boutons radio, tire de lexemple Basic/RadioButton :
<?xml version="1.0" encoding="utf-8"?> <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <RadioButton android:id="@+id/radio1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Caillou" /> <RadioButton android:id="@+id/radio2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Ciseaux" /> <RadioButton android:id="@+id/radio3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Papier" /> </RadioGroup>

Chapitre 6

Utilisation des widgets de base

43

La Figure 6.6 montre le rsultat obtenu en utilisant le projet Java de base, fourni par Android.
Figure 6.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 :

Le contrle de la squence de focus : android:nextFocusDown ; android:nextFocusLeft ; android:nextFocusRight ; android:nextFocusUp.

44

Lart du dveloppement Android

android:visibility, qui contrle la visibilit initiale du widget. android:background, qui permet de fournir au widget une couleur de fond au format RGB (#00FF00 pour vert, par exemple).

Mthodes utiles
La mthode setEnabled() permet de basculer entre ltat actif et inactif du widget, alors que isEnabled() permet de tester si un widget est actif. On utilise souvent ces deux mthodes pour dsactiver certains widgets en fonction de choix effectus laide de 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 ls daprs son identiant. getRootView() renvoie la racine de larborescence (celle que vous avez fournie lactivit via un appel setContentView()).

7
Conteneurs
Les conteneurs permettent de disposer un ensemble de widgets (et, ventuellement, des conteneurs ls) pour obtenir la prsentation de votre choix. Si, par exemple, vous prfrez placer les labels gauche et les champs de saisie droite, vous aurez besoin dun conteneur. Si vous voulez que les boutons OK et Annuler soient lun ct de lautre, en bas droite du 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 an de disposer dun lment racine dans lequel les placer. La plupart des kits de dveloppement graphiques utilisent des gestionnaires de disposition des widgets (layout managers) qui sont, le plus souvent, organiss sous forme de 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. Dans ce chapitre, nous tudierons trois conteneurs parmi les plus courants : LinearLayout (le modle des botes), RelativeLayout (un modle de positionnement relatif) et TableLayout (le modle en grille) ; nous prsenterons galement ScrollView, un conteneur conu pour faciliter la mise en place des conteneurs avec barres de dlement. Le chapitre suivant prsentera dautres conteneurs plus sotriques.

46

Lart du dveloppement Android

Penser de faon linaire


Comme on la dj mentionn, LinearLayout est un modle reposant sur des botes les widgets ou les conteneurs ls sont aligns en colonnes ou en lignes, les uns aprs les autres, exactement comme avec FlowLayout en Java Swing, et vbox et hbox en Flex et XUL. Avec Flex et XUL, la bote est lunit essentielle de disposition des widgets. Avec Android, vous pouvez utiliser LinearLayout exactement de la mme faon, en vous passant des autres conteneurs. Obtenir la disposition que vous souhaitez revient alors principalement identier les imbrications et les proprits des diffrentes botes leur alignement par rapport aux autres botes, par exemple.

Concepts et proprits
Pour congurer un LinearLayout, vous pouvez agir sur cinq paramtres : lorientation, le modle de remplissage, le poids, la gravit et le remplissage.

Orientation
Lorientation prcise si le LinearLayout reprsente une ligne ou une colonne. Il suft dajouter la proprit android:orientation llment LinearLayout du chier XML en xant sa valeur horizontal pour une ligne ou vertical pour une colonne. Cette orientation peut tre modie en cours dexcution en appelant la mthode setOrientation()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. Pour rsoudre ce problme, tous les widgets dun LinearLayout doivent fournir une valeur pour les proprits android:layout_width et android:layout_height. Ces valeurs peuvent sexprimer de trois faons diffrentes :

Une dimension prcise, comme 125 px, pour indiquer que le widget devra occuper exactement 125 pixels. wrap_content, pour demander que le widget occupe sa place naturelle sauf sil est trop gros, auquel cas Android coupera le texte entre les mots pour quil puisse tenir. fill_parent, pour demander que le widget occupe tout lespace disponible de son conteneur aprs que les autres widgets eurent t placs.

Chapitre 7

Conteneurs

47

Les valeurs les plus utilises sont les deux dernires, car elles sont indpendantes de la taille de lcran ; Android peut donc ajuster la disposition pour quelle tienne dans lespace disponible.

Poids
Que se passera-t-il si deux widgets doivent se partager lespace disponible ? Supposons, par exemple, que nous ayons deux champs de saisie multilignes en colonne et que nous voulions quils occupent tout lespace disponible de la colonne aprs le placement de tous les autres widgets. Pour ce faire, en plus dinitialiser android:layout_width (pour les lignes) ou android:layout_height (pour les colonnes) avec fill_parent, il faut galement donner android:layout_weight, une valeur qui indique la proportion despace libre qui sera affecte au widget. Si cette valeur est la mme pour les deux widgets (1, par exemple), lespace libre sera partag quitablement entre eux. Si la valeur est 1 pour un widget et 2 pour lautre, le second utilisera deux fois plus despace libre que le premier, etc.

Gravit
Par dfaut, les widgets salignent partir de la gauche et du haut. Si vous crez une ligne avec un LinearLayout horizontal, cette ligne commencera donc se remplir partir du bord gauche de lcran. Si ce nest pas ce que vous souhaitez, vous devez indiquer une gravit laide de la proprit android:layout_gravity dun widget (ou en appelant la mthode setGravity() sur celui-ci) an dindiquer au widget et son conteneur comment laligner par rapport lcran. Pour une colonne de widgets, les gravits les plus courantes sont left, center_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 la mthode setPadding() de lobjet Java correspondant au widget). La valeur de remplissage prcise lespace situ entre le contour de la "cellule" du widget et son contenu rel. Elle est analogue aux marges dun document dans un traitement de texte

48

Lart du dveloppement Android

la taille de page peut tre de 21 29,7 cm, mais des marges de 2 cm connent le texte dans une surface de 19 27,7 cm. La proprit android:padding permet de prciser le mme remplissage pour les quatre cts du widget ; son contenu tant alors centr dans la zone qui reste. Pour utiliser des valeurs diffrentes en fonction des cts, utilisez les proprits android:paddingLeft, android:paddingRight, android:paddingTop et android:paddingBottom (voir Figure 7.1).
Figure 7.1 Relations entre un widget, sa cellule et ses valeurs de remplissage.

La valeur de ces proprits est une dimension, comme 5px pour demander un remplissage de 5 pixels.

Exemple
Voici le chier de description XML de lexemple Containers/Linear, qui montre les proprits de llment LinearLayout :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <RadioGroup android:id="@+id/orientation" android:orientation="horizontal" android:layout_width="wrap_content"

Chapitre 7

Conteneurs

49

android:layout_height="wrap_content" android:padding="5px"> <RadioButton android:id="@+id/horizontal" android:text="horizontal" /> <RadioButton android:id="@+id/vertical" android:text="vertical" /> </RadioGroup> <RadioGroup android:id="@+id/gravity" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="5px"> <RadioButton android:id="@+id/left" android:text="gauche" /> <RadioButton android:id="@+id/center" android:text="centre" /> <RadioButton android:id="@+id/right" android:text="droite" /> </RadioGroup> </LinearLayout>

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

50

Lart du dveloppement Android

import android.text.TextWatcher; import android.widget.LinearLayout; import android.widget.RadioGroup; import android.widget.EditText; public class LinearLayoutDemo extends Activity implements RadioGroup.OnCheckedChangeListener { RadioGroup orientation; RadioGroup gravity; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); orientation=(RadioGroup)findViewById(R.id.orientation); orientation.setOnCheckedChangeListener(this); gravity=(RadioGroup)findViewById(R.id.gravity); gravity.setOnCheckedChangeListener(this); } public void onCheckedChanged(RadioGroup group, int checkedId) { if (group==orientation) { if (checkedId==R.id.horizontal) { orientation.setOrientation(LinearLayout.HORIZONTAL); } else { orientation.setOrientation(LinearLayout.VERTICAL); } } else if (group==gravity) { if (checkedId==R.id.left) { gravity.setGravity(Gravity.LEFT); } else if (checkedId==R.id.center) { gravity.setGravity(Gravity.CENTER_HORIZONTAL); } else if (checkedId==R.id.right) { gravity.setGravity(Gravity.RIGHT); } } } }

Dans onCreate(), nous recherchons nos deux conteneurs RadioGroup et nous enregistrons un couteur pour chacun deux an dtre prvenu du changement dtat des boutons radio (setOnCheckedChangeListener(this)). Lactivit implmentant linterface OnCheckedChangeListener, elle se comporte elle-mme comme un couteur.

Chapitre 7

Conteneurs

51

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 modie la gravit. La Figure 7.2 montre ce quafche lapplication lorsquelle est lance dans lmulateur.
Figure 7.2 Lapplication LinearLayoutDemo lors de son lancement.

Si lon clique sur le bouton vertical, le RadioGroup du haut sajuste en consquence (voir Figure 7.3).
Figure 7.3 La mme application, aprs avoir cliqu sur le bouton vertical.

52

Lart du dveloppement Android

Si lon clique sur les boutons centre ou droite, le RadioGroup du bas sajuste galement (voir Figures 7.4 et 7.5).
Figure 7.4 La mme application, avec les boutons vertical et centre cochs.

Figure 7.5 La mme application, avec les boutons vertical et droite cochs.

Tout est relatif


Comme son nom lindique, le RelativeLayout place les widgets relativement aux autres widgets du conteneur et de son conteneur parent. Vous pouvez ainsi placer le widget X en dessous et gauche du widget Y ou faire en sorte que le bord infrieur du widget Z soit

Chapitre 7

Conteneurs

53

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
Il faut pouvoir faire rfrence dautres widgets dans le chier de description XML et disposer dun moyen dindiquer leurs positions relatives.

Positions relatives un conteneur


Les relations les plus simples mettre en place sont celles qui lient la position dun widget celle de son conteneur :

android:layout_alignParentTop prcise que le haut du widget doit tre align avec celui du conteneur. android:layout_alignParentBottom prcise que le bas du widget doit tre align avec celui du conteneur. android:layout_alignParentLeft prcise que le bord gauche du widget doit tre align avec le bord gauche du conteneur. android:layout_alignParentRight prcise que le bord droit du widget doit tre align avec le bord droit du conteneur. android:layout_centerHorizontal prcise que le widget doit tre centr horizontalement dans le conteneur. android:layout_centerVertical prcise que le widget doit tre centr verticalement dans le conteneur. android:layout_centerInParent prcise que le widget doit tre centr horizontalement et verticalement dans le conteneur.

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

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 : 1. Associez des identiants (attributs android:id) tous les lments que vous aurez besoin de dsigner, sous la forme @+id/....
1. http://www.onjava.com/pub/a/onjava/2002/09/18/relativelayout.html.

54

Lart du dveloppement Android

2. Dsignez un widget en utilisant son identiant, priv du signe plus (@id/...). Si, par exemple, le widget A est identi par @+id/widget_a, le widget B peut le dsigner dans lune de ses proprits par @id/widget_a.

Positions relatives aux autres widgets


Quatre proprits permettent de contrler la position dun widget par rapport aux autres :

android:layout_above indique que le widget doit tre plac au-dessus de celui qui est dsign dans cette proprit. android:layout_below indique que le widget doit tre plac sous celui qui est dsign dans cette proprit. android:layout_toLeftOf indique que le widget doit tre plac gauche de celui qui est dsign dans cette proprit. android:layout_toRightOf indique que le widget doit tre plac droite de celui qui est dsign dans cette proprit.

Cinq autres proprits permettent de contrler lalignement dun widget par rapport un autre :

android:layout_alignTop indique que le haut du widget doit tre align avec le haut du widget dsign dans cette proprit. android:layout_alignBottom indique que le bas du widget doit tre align avec le bas du widget dsign dans cette proprit. android:layout_alignLeft indique que le bord gauche du widget doit tre align avec le bord gauche du widget dsign dans cette proprit. android:layout_alignRight indique que le bord droit du widget doit tre align avec le bord droit du widget dsign dans cette proprit. android:layout_alignBaseline indique que les lignes de base des deux widgets doivent tre alignes.

La dernire proprit de cette liste permet daligner des labels et des champs an que le texte semble "naturel". En effet, les champs de saisie tant matrialiss par une bote, contrairement aux labels, android:layout_alignTop alignerait le haut de la bote du champ avec le haut du label, ce qui ferait apparatre le texte du label plus haut dans lcran que le texte saisi dans le champ. Si lon souhaite que le widget B soit plac droite du widget A, llment XML du widget B doit donc contenir android:layout_toRight = "@id/widget_a" (o @id/ widget_a est lidentiant du widget A).

Chapitre 7

Conteneurs

55

Ordre dvaluation
Lordre dvaluation complique encore les choses. En effet, Android ne lit quune seule fois le chier XML et calcule donc en squence la taille et la position de chaque widget. Ceci a deux consquences :

On ne peut pas faire rfrence un widget qui na pas t dni plus haut dans le chier. Il faut vrier que lutilisation de la valeur fill_parent pour android:layout_width ou android:layout_height ne "consomme" pas tout lespace alors que lon na pas encore dni tous les widgets.

Exemple
titre dexemple, tudions un "formulaire" classique, compos dun champ, dun label et de deux boutons, OK et Annuler. Voici le chier de disposition XML du projet Containers/Relative :
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="5px"> <TextView android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="URL:" android:paddingTop="15px"/> <EditText android:id="@+id/entry" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_toRightOf="@id/label" android:layout_alignBaseline="@id/label"/> <Button android:id="@+id/ok" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/entry" android:layout_alignRight="@id/entry" android:text="Ok" /> <Button android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@id/ok" android:layout_alignTop="@id/ok" android:text="Annuler" /> </RelativeLayout>

56

Lart du dveloppement Android

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 5 pixels entre les limites du conteneur et son contenu (android:padding = "5px"). Puis nous dnissons un label assez basique, hormis son remplissage de 15 pixels (android:padding = "15px"), que nous expliquerons plus loin. Nous ajoutons ensuite le champ que nous voulons placer droite du label, avec sa ligne de base aligne avec celle du label et nous faisons en sorte quil occupe le reste de cette "ligne". Ces trois caractristiques sont gres par trois proprits :

android:layout_toRight = "@id/label" ; android:layout_alignBaseline = "@id/label" ; android:layout_width = "fill_parent".

Si nous navions pas utilis le remplissage de 15 pixels pour le label, le haut du champ serait coup cause du remplissage de 5 pixels du conteneur lui-mme. En effet, la proprit android: layout_alignBaseline = "@id/label" se contente daligner les lignes de base du label et du champ. Par dfaut, le haut du label est align avec le haut de son parent, or il est plus petit que le champ puisque ce dernier est entour dune bote. Le champ dpendant de la position du label qui a dj t dnie (puisquil apparat avant lui dans le chier XML), le champ serait trop haut et son bord suprieur serait rogn par le remplissage du conteneur. Vous rencontrerez probablement ce genre de problme lorsque vous voudrez mettre en place des RelativeLayout pour quils apparaissent exactement comme vous le souhaitez. La solution ce casse-tte consiste, comme nous lavons vu, ajouter 15 pixels de remplissage au-dessus du label, an de le pousser sufsamment vers le bas pour que le champ ne soit pas coup. Voici quelques "solutions" qui ne marchent pas :

Vous ne pouvez pas utiliser android:layout_alignParentTop sur le champ car il ne peut pas y avoir deux proprits qui tentent en mme temps de dnir la position verticale du champ. Ici, android:layout_alignParentTop entrerait en conit avec la proprit android:layout_alignBaseline = "@id/label", qui apparat plus bas, et cest cette dernire qui lemporterait. Vous devez donc soit aligner le haut, soit aligner les lignes de base, mais pas les deux. Vous ne pouvez pas dabord dnir le champ puis placer le label sa gauche car on ne peut pas faire de "rfrence anticipe" vers un widget il doit avoir t dni avant de pouvoir y faire rfrence.

Revenons notre exemple. Le bouton OK est plac sous le champ (android:layout_below = "@id/entry") et son bord droit est align avec le bord droit du champ

Chapitre 7

Conteneurs

57

(android:layout_alignRight = "@id/entry"). Le bouton Annuler est plac gauche du bouton OK (android:layout_toLeft = "@id/ok") et son bord suprieur est align avec celui de son voisin (android:layout_alignTop = "@id/ok"). La Figure 7.6 montre le rsultat afch dans lmulateur lorsque lon se contente du code Java produit automatiquement.
Figure 7.6 Lapplication RelativeLayoutDemo.

Tabula Rasa
Si vous aimez les tableaux HTML ou les feuilles de calcul, vous apprcierez le conteneur TableLayout dAndroid car il vous permet de positionner les widgets dans une grille. Vous pouvez ainsi dnir le nombre de lignes et de colonnes, les colonnes qui peuvent se rduire ou sagrandir en fonction de leur contenu, etc. TableLayout fonctionne de concert avec le conteneur TableRow. Alors que TableLayout contrle le comportement global du conteneur, les widgets eux-mmes sont placs dans un ou plusieurs TableRow, raison dun par ligne de la grille.

Concepts et proprits
Pour utiliser ce conteneur, il faut savoir grer les widgets en lignes et en colonnes, et traiter ceux qui sont placs lextrieur des lignes.

58

Lart du dveloppement Android

Placement des cellules dans les lignes


Cest vous, le dveloppeur, qui dclarez les lignes en plaant les widgets comme des ls dun lment TableRow, lui-mme ls dun TableLayout. Vous contrlez donc directement la faon dont apparaissent les lignes dans le tableau. Cest Android qui dtermine automatiquement le nombre de colonnes mais, en fait, vous le contrlez de faon indirecte. Il y aura au moins autant de colonnes quil y a de widgets dans la ligne la plus longue. Sil y a trois lignes, par exemple une ligne avec deux widgets, une avec trois widgets et une autre avec quatre widgets , il y aura donc au moins quatre colonnes. Cependant, un widget peut occuper plusieurs colonnes si vous utilisez la proprit android:layout_span en lui prcisant le nombre de colonnes sur lesquelles doit stendre le widget concern. Cette proprit ressemble donc lattribut colspan utilis dans les tableaux HTML :
<TableRow> <TextView android:text="URL:" /> <EditText android:id="@+id/entry" android:layout_span="3"/> </TableRow>

Avec ce fragment XML, le champ stendra sur trois colonnes. Gnralement, les widgets sont placs dans la premire colonne disponible. Dans lextrait prcdent, par exemple, le label irait dans la premire colonne (la colonne 0 car leur numrotation commence 0) et le champ stendrait sur les trois colonnes suivantes (les colonnes 1 3). Vous pouvez galement placer un widget sur une colonne prcise en vous servant de la proprit android:layout_column et en lui indiquant le numro de colonne voulu :
<TableRow> <Button android:id="@+id/cancel" android:layout_column="2" android:text="Annuler" /> <Button android:id="@+id/ok" android:text="Ok" /> </TableRow>

Avec cet extrait, le bouton Annuler sera plac dans la troisime colonne (la colonne 2) et le bouton OK, dans la colonne disponible suivante, cest--dire la quatrime.

Chapitre 7

Conteneurs

59

Fils de TableLayout qui ne sont pas des lignes


Gnralement, les seuls ls 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 xes fill_parent pour remplir le mme espace que la ligne la plus longue. Un cas dutilisation de cette conguration consiste se servir dun widget View (<View android:layout_height = "2px" android:background = "#0000FF" />) pour crer une barre de sparation bleue de 2 pixels et de la mme largeur que le tableau.

Rduire, tirer et refermer


Par dfaut, la taille de chaque colonne sera la taille "naturelle" de son widget le plus large (en tenant compte des widgets qui stendent sur plusieurs colonnes). Parfois, cependant, cela ne donne pas le rsultat escompt et il faut alors intervenir plus prcisment sur le comportement de la colonne. Pour ce faire, vous pouvez utiliser la proprit android:stretchColumns de llment TableLayout, dont la valeur peut tre un seul numro de colonne (dbutant zro) ou une liste de numros de colonnes spars par des virgules. Ces colonnes seront alors tires pour occuper tout lespace disponible de la ligne, ce qui est utile lorsque votre contenu est plus troit que lespace restant. Inversement, la proprit android:shrinkColumns de TableLayout, qui prend les mmes valeurs, permet de rduire la largeur effective des colonnes en dcoupant leur contenu en plusieurs lignes (par dfaut, le contenu des widgets nest pas dcoup). Cela permet dviter quun contenu trop long pousse certaines colonnes droite de lcran. Vous pouvez galement tirer parti de la proprit android:collapseColumns de TableLayout, en indiquant l aussi un numro ou une liste de numros de colonnes. Celles-ci seront alors initialement "refermes", ce qui signie quelles napparatront pas, bien quelles fassent partie du tableau. partir de votre programme, vous pouvez refermer ou rouvrir les colonnes laide de la mthode setColumnCollapsed()du widget 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

60

Lart du dveloppement Android

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

On obtient alors le rsultat montr la Figure 7.7.


Figure 7.7 Lapplication TableLayoutDemo.

Chapitre 7

Conteneurs

61

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

62

Lart du dveloppement Android

<TextView android:text="#884400" android:paddingLeft="4px" android:layout_gravity="center_vertical" </TableRow> <TableRow> <View android:layout_height="80px" android:background="#aa8844" /> <TextView android:text="#aa8844" android:paddingLeft="4px" android:layout_gravity="center_vertical" </TableRow> <TableRow> <View android:layout_height="80px" android:background="#ffaa88" /> <TextView android:text="#ffaa88" android:paddingLeft="4px" android:layout_gravity="center_vertical" </TableRow> <TableRow> <View android:layout_height="80px" android:background="#ffffaa" /> <TextView android:text="#ffffaa" android:paddingLeft="4px" android:layout_gravity="center_vertical" </TableRow> <TableRow> <View android:layout_height="80px" android:background="#ffffff" /> <TextView android:text="#ffffff" android:paddingLeft="4px" android:layout_gravity="center_vertical" </TableRow> </TableLayout> </ScrollView>

/>

/>

/>

/>

/>

Sans le ScrollView, la grille occuperait au moins 560 pixels (sept lignes de 80 pixels chacune, selon la dnition de llment View). Certains terminaux peuvent avoir des crans capables dafcher autant dinformations, mais la plupart seront plus petits. Le ScrollView permet alors de conserver la grille tout en en prsentant quune partie la fois. La Figure 7.8 montre ce quafchera lmulateur dAndroid au lancement de lactivit.

Chapitre 7

Conteneurs

63

Figure 7.8 Lapplication ScrollViewDemo.

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

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

66

Lart du dveloppement Android

Sadapter aux circonstances


Dans labsolu, les adaptateurs offrent une interface commune pour diffrentes API. Plus prcisment, dans le cas dAndroid, ils fournissent une interface commune au modle de donnes sous-jacent dun widget de slection comme une liste droulante. Cette utilisation des interfaces Java est assez classique (voir, par exemple, les adaptateurs de modles pour JTable en Java/Swing), et Java nest pas le seul environnement fournir ce type dabstraction (le framework XML de Flex accepte indiffremment du code XML en ligne ou tlcharg partir dInternet). Les adaptateurs dAndroid se chargent de fournir la liste des donnes dun widget de slection et de convertir les diffrents lments en vues spciques pour quelles safchent 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 rednir lafchage par dfaut. En Java/Swing, par exemple, si vous souhaitez quune liste implmente par une JList soit, en ralit, une liste cocher (o les diffrentes lignes sont composes dune case cocher et dun label et o les clics modient ltat de cette liste), vous nirez invitablement par appeler la mthode setCellRenderer() pour disposer dun objet ListCellRenderer qui, son tour, permet de convertir le contenu dune liste en widgets composites JCheckBox-plus-JLabel.

Utilisation dArrayAdapter
Ladaptateur le plus simple est ArrayAdapter puisquil suft denvelopper un tableau ou une instance de java.util.List pour disposer dun adaptateur prt fonctionner :
String[] items = {"ceci", "est", "une", "liste", "vraiment", "stupide"}; new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);

Le constructeur dArrayAdapter attend trois paramtres :


le contexte dutilisation (gnralement, il sagit de linstance de lactivit) ; lidentiant de ressource de la vue utiliser ; le tableau ou la liste dlments afcher.

Par dfaut, ArrayAdapter appellera la mthode toString() des objets de la liste et 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, safcheront dans la liste, le spinner ou tout widget qui utilise cet ArrayAdapter. Vous pouvez confectionner vos propres vues en crant une sousclasse dArrayAdapter pour rednir sa mthodegetView() :

Chapitre 8

Widgets de slection

67

public View getView(int position, View convertView, ViewGroup parent) { if (convertView==null) { convertView=new TextView(this); } convertView.setText(buildStringFor(position)); return(convertView); }

Ici, getView() reoit trois paramtres :


Lindice de llment du tableau que lon veut afcher dans la vue. Une vue existante qui sera modie avec les donnes cette position (si ce paramtre vaut null, vous devrez crer votre propre instance). Le widget qui contiendra cette vue, sil faut linstancier.

Dans lexemple prcdent, ladaptateur renvoie quand mme un objet TextView mais utilise un comportement diffrent pour savoir quelle chane sera place dans la vue. Le Chapitre 9 prsentera des ListView plus labores.

Autres adaptateurs essentiels


Voici dautres adaptateurs dont vous aurez certainement besoin :

CursorAdapter convertit un Cursor, gnralement fourni par un content provider, en un objet pouvant safcher dans une vue de slection. SimpleAdapter convertit les donnes trouves dans les ressources XML. ActivityAdapter et ActivityIconAdapter fournissent les noms ou les icnes des activits qui peuvent tre appeles lors dune intention particulire.

Listes des bons et des mchants


Le widget classique dAndroid pour les listes sappelle ListView. Pour disposer dune liste compltement fonctionnelle, il suft dinclure un objet ListView dans votre prsentation, dappeler setAdapter() pour fournir les donnes et les vues lles, puis dattacher un couteur via setOnItemSelectedListener() pour tre prvenu de toute modication de la slection. Cependant, si votre activit est pilote par une seule liste, il peut tre prfrable que cette activit soit une sous-classe de ListActivity plutt que de la classe de base Activity traditionnelle. Si votre vue principale est uniquement constitue de la liste, vous navez mme pas besoin de fournir de layout ListActivity construira pour vous une liste qui occupera tout lcran. Vous pouvez toutefois personnaliser cette prsentation condition didentier cette ListView par @android:id/list, an que ListActivity sache quelle est la liste principale de lactivit.

68

Lart du dveloppement Android

Voici, par exemple, le chier de disposition du projet Selection/List :


<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content"/> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:drawSelectorOnTop="false" /> </LinearLayout>

Comme vous pouvez le constater, il sagit simplement dune liste surmonte dun label qui devra afcher en permanence la slection courante. Le code Java permettant de congurer cette liste et de la connecter au label est le suivant :
public class ListViewDemo extends ListActivity { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items)); selection=(TextView)findViewById(R.id.selection); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items[position]); } }

Chapitre 8

Widgets de slection

69

Vous pouvez congurer ladaptateur dune ListActivity par un appel setListAdapter() ici, on fournit un ArrayAdapter qui enveloppe un tableau de chanes quelconques. Pour tre prvenu des changements dans la liste de slection, on rednit onListItemClick() pour quelle agisse de faon approprie en tenant compte de la vue lle et de la position qui lui sont passes en paramtre (ici, elle crit dans le label le texte situ cette position). Le second paramtre de notre ArrayAdapter android.R.layout.simple_list_item_1 contrle laspect des lignes. La valeur utilise dans lexemple prcdent fournit une ligne Android standard : grande police, remplissage important et texte en blanc. Le rsultat est montr la Figure 8.1.
Figure 8.1 Lapplication ListViewDemo.

Modes de slection
Par dfaut, ListView est simplement congure pour recevoir les clics sur les entres de la liste. Cependant, on a parfois besoin quune liste mmorise un ou plusieurs choix de lutilisateur ; ListView permet galement de le faire, au prix de quelques modications. Dans le code Java, vous devez dabord appeler la mthode setChoiceMode() de lobjet ListView an de congurer le mode de slection en lui passant en paramtre la constante CHOICE_MODE_SINGLE ou CHOICE_MODE_MULTIPLE (pour obtenir lobjet ListView, il suft dappeler la mthode getListView() partir dune ListActivity).

70

Lart du dveloppement Android

Puis, au lieu de passer en paramtre android.R.layout.simple_list_item_1 au constructeur dArrayAdapter, vous devrez lui passer soit android.R.layout.simple_list_item_single_choice, soit android.R.layout.simple_list_item_multiple_choice pour mettre en place, respectivement, une liste choix unique ou choix multiples. Vous obtiendrez alors un rsultat comme celui des Figures 8.2 ou 8.3.
Figure 8.2 Liste choix unique.

Figure 8.3 Liste choix multiple.

Chapitre 8

Widgets de slection

71

Pour connatre les choix de lutilisateur, utilisez la mthode getCheckedItemPositions() de la ListView.

Contrle du Spinner
Le Spinner dAndroid est lquivalent des botes droulantes que lon trouve dans certains kits de dveloppement (JComboBox en Java/Swing, par exemple). Appuyer sur le bouton central du pad du terminal fait surgir une bote de slection permettant lutilisateur de faire son choix. On peut ainsi choisir dans une liste sans occuper tout lcran comme avec une ListView, mais au prix dun clic supplmentaire ou dun pointage sur lcran. Comme pour ListView, on fournit ladaptateur pour les donnes et les vues lles via setAdapter() et on accroche un couteur avec setOnItemSelectedListener(). Si lon souhaite personnaliser la vue dafchage de la bote droulante, il faut congurer ladaptateur, pas le widget Spinner. Pour ce faire, on a donc besoin de la mthode setDropDownViewResource() an de fournir lidentiant de la vue concerne. Voici par exemple le chier de disposition du projet Selection/Spinner, qui permet de mettre en place une vue simple contenant un Spinner :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Spinner android:id="@+id/spinner" android:layout_width="fill_parent" android:layout_height="wrap_content" android:drawSelectorOnTop="true" /> </LinearLayout>

Il sagit de la mme vue que celle de la section prcdente, mais avec Spinner la place de ListView. La proprit android:drawSelectorOnTop indique que la che permettant de drouler la slection se trouvera droite du Spinner.

72

Lart du dveloppement Android

Voici le code Java permettant de remplir et dutiliser le Spinner :


public class SpinnerDemo extends Activity implements AdapterView.OnItemSelectedListener { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); selection=(TextView)findViewById(R.id.selection); Spinner spin=(Spinner)findViewById(R.id.spinner); spin.setOnItemSelectedListener(this); ArrayAdapter<String> aa=new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, items); aa.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item); spin.setAdapter(aa); } public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { selection.setText(items[position]); } public void onNothingSelected(AdapterView<?> parent) { selection.setText(""); } }

Ici, cest lactivit elle-mme qui sert dcouteur de slection (spin.setOnItemSelectedListener(this)), ce qui est possible car elle implmente linterface OnItemSelectedListener. On congure ladaptateur non seulement avec une liste de mots quelconques mais galement avec une ressource spcique qui servira la vue droulante (via aa.setDropDownViewResource()). Vous remarquerez galement que lon utilise la vue prdnie android.R.layout.simple_spinner_item pour afcher les lments du Spinner. Enn, on implmente les mthodes de rappels ncessaires dOnItemSelectedListener pour que le contenu du label volue en fonction du choix de lutilisateur. On obtient ainsi le rsultat prsent aux Figures 8.4 et 8.5.

Chapitre 8

Widgets de slection

73

Figure 8.4 Lapplication SpinnerDemo lors de son lancement.

Figure 8.5 La mme application avec afchage 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.

74

Lart du dveloppement Android

Voici les proprits qui, combines, dterminent le nombre et la taille des colonnes :

android:numColumns indique le nombre de colonnes ; si sa valeur est auto_fit, Android calculera ce nombre en fonction de lespace disponible et de la valeur des autres proprits. android:verticalSpacing et son homologue android:horizontalSpacing prcisent lespace sparant les lments de la grille. android:columnWidth indique la largeur de chaque colonne en pixels. android:stretchMode indique, pour les grilles dont la valeur dandroid: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 310 pixels (trois colonnes de 100 pixels et deux sparations de 5 pixels). 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 10 pixels.

Pour le reste, GridView fonctionne exactement comme nimporte quel autre widget de slection on utilise setAdapter() pour fournir les donnes et les vues lles, on appelle setOnItemSelectedListener() pour enregistrer un couteur de choix, etc. Voici, par exemple, le chier de disposition XML du projet Selection/Grid :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <GridView android:id="@+id/grid" android:layout_width="fill_parent" android:layout_height="fill_parent" android:verticalSpacing="35px" android:horizontalSpacing="5px" android:numColumns="auto_fit" android:columnWidth="100px"

Chapitre 8

Widgets de slection

75

android:stretchMode="columnWidth" android:gravity="center" /> </LinearLayout>

Cette grille occupe tout lcran, sauf la partie rserve au label qui afche la slection courante. Le nombre de colonnes est calcul par Android (android:numColumns = "auto_fit") partir dun espacement horizontal de 5 pixels (android:horizontalSpacing = "5px") et dune largeur de colonne de 100 pixels (android:columnWidth = "100px"). Les colonnes absorberont lespace restant disponible (android:stretchMode = "columnWidth"). Le code Java permettant de congurer cette grille est le suivant :
public class GridDemo extends Activity implements AdapterView.OnItemSelectedListener { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); selection=(TextView)findViewById(R.id.selection); GridView g=(GridView)findViewById(R.id.grid); g.setAdapter(new FunnyLookingAdapter(this, android.R.layout.simple_list_item_1, items)); g.setOnItemSelectedListener(this); } public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { selection.setText(items[position]); } public void onNothingSelected(AdapterView<?> parent) { selection.setText(""); } private class FunnyLookingAdapter extends ArrayAdapter { Context ctxt; FunnyLookingAdapter(Context ctxt, int resource, String[] items) { super(ctxt, resource, items); this.ctxt=ctxt; }

76

Lart du dveloppement Android

public View getView(int position, View convertView, ViewGroup parent) { TextView label=(TextView)convertView; if (convertView==null) { convertView=new TextView(ctxt); label=(TextView)convertView; } label.setText(items[position]); return(convertView); } } }

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

Chapitre 8

Widgets de slection

77

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

78

Lart du dveloppement Android

comme nimporte quel EditText, pour tre prvenu lorsque le texte a t modi. Ce type dvnement est dclench par une saisie manuelle ou par une slection dans la liste des propositions. Voici, par exemple, le chier de description du projet Selection/AutoComplete :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <AutoCompleteTextView android:id="@+id/edit" android:layout_width="fill_parent" android:layout_height="wrap_content" android:completionThreshold="3"/> </LinearLayout>

Le code Java correspondant est le suivant :


public class AutoCompleteDemo extends Activity implements TextWatcher { TextView selection; AutoCompleteTextView edit; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); selection=(TextView)findViewById(R.id.selection); edit=(AutoCompleteTextView)findViewById(R.id.edit); edit.addTextChangedListener(this);

Chapitre 8

Widgets de slection

79

edit.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, items)); } public void onTextChanged(CharSequence s, int start, int before, int count) { selection.setText(edit.getText()); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { // impose par linterface, mais inutilise } public void afterTextChanged(Editable s) { // impose par linterface, mais inutilise } }

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

80

Lart du dveloppement Android

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

Figure 8.10 La mme application, aprs avoir choisi le texte suggr.

Chapitre 8

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 dle selon laxe horizontal et o llment slectionn est mis en surbrillance. Sur un terminal Android, lutilisateur peut parcourir les diffrents choix laide des boutons gauche et droit du pad. Gallery prend moins de place lcran que ListView, tout en montrant plusieurs choix la fois (pour autant quils soient sufsamment courts). Par rapport Spinner, Gallery montre galement plusieurs choix simultanment. Lexemple canonique dutilisation de Gallery consiste parcourir une galerie de photos lutilisateur peut ainsi prvisualiser les imagettes correspondant une collection de photos ou dicnes an den choisir une. Du point de vue du code, un objet Gallery fonctionne quasiment comme un Spinner ou un GridView. Il dispose de plusieurs proprits :

android:spacing indique le nombre de pixels sparant les diffrents lments de la liste. android:spinnerSelector prcise ce qui indiquera une slection il peut sagir dune rfrence un objet Drawable (voir le chapitre sur les ressources) ou une valeur RGB de la forme #AARRGGBB ou quivalente. android:drawSelectorOnTop indique si la barre de slection (ou le Drawable) doit tre dessine avant (false) ou aprs (true) le dessin du ls slectionn. Si cette proprit vaut true, assurez-vous que le slecteur soit sufsamment transparent pour que lon puisse apercevoir le ls derrire lui ; sinon les utilisateurs ne pourront pas voir ce quils ont choisi.

9
Samuser avec les listes
Lhumble ListView est lun des widgets les plus importants et les plus utiliss dAndroid. Que lon choisisse un contact tlphonique, un courrier faire suivre ou un ebook lire, cest de ce widget dont on se servira le plus souvent. Mais il serait videmment plus agrable dnumrer autre chose que du texte simple. La bonne nouvelle est que les listes peuvent tre aussi amusantes quon le souhaite... dans les limites de lcran dun mobile, videmment. Cependant, cette dcoration implique un peu de travail et met en uvre certaines fonctionnalits dAndroid que nous prsenterons dans ce chapitre.

Premires tapes
Gnralement, un widget ListView dAndroid est une simple liste de texte robuste mais austre. Cela est d au fait que nous nous contentons de lui fournir un tableau de mots et que nous demandons Android dutiliser une disposition simple pour afcher ces mots sous forme de liste. Cependant, vous pouvez galement crer une liste dicnes, dicnes et de texte, de cases cocher et de texte, etc. Tout cela dpend des donnes que vous fournissez ladaptateur et de laide que vous lui apportez pour crer un ensemble plus riche dobjets View pour chaque ligne.

84

Lart du dveloppement Android

Supposons, par exemple, que vous vouliez produire une liste dont chaque ligne est constitue dune icne suivie dun texte. Vous pourriez utiliser une disposition de ligne comme celle du projet FancyLists/Static :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:id="@+id/icon" android:layout_width="22px" android:paddingLeft="2px" android:paddingRight="2px" android:paddingTop="2px" android:layout_height="wrap_content" android:src="@drawable/ok" /> <TextView android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="44sp" /> </LinearLayout>

On utilise ici un conteneur LinearLayout pour crer une ligne contenant une icne gauche et un texte (utilisant une grande police agrable lire) droite. Cependant, par dfaut, Android ne sait pas que vous souhaitez utiliser cette disposition avec votre ListView. Pour tablir cette connexion, vous devez donc indiquer ladaptateur lidentiant de ressource de cette disposition personnalise :
public class StaticDemo extends ListActivity { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new ArrayAdapter<String>(this, R.layout.row, R.id.label, items));

Chapitre 9

Samuser avec les listes

85

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

On peut remarquer que cette structure gnrale est identique celle du projet Selection/ List du Chapitre 8. Le point essentiel de cet exemple est que lon a indiqu ArrayAdapter que lon voulait utiliser notre propre disposition de ligne (R.layout.row) et que le TextView contenant le mot est dsign par R.id.label dans cette disposition. Noubliez pas que, pour dsigner une disposition (row.xml), il faut prxer le nom de base du chier de description par R.layout (R.layout.row). On obtient ainsi une liste avec des icnes droite. Ici, comme le montre la Figure 9.1, toutes les icnes sont les mmes.
Figure 9.1 Lapplication StaticDemo.

Prsentation dynamique
Cette technique fournir une disposition personnalise pour les lignes permet de traiter trs lgamment les cas simples, mais elle ne suft plus pour les scnarios plus compliqus comme ceux qui suivent :

Chaque ligne utilise une disposition diffrente (certaines ont une seule ligne de texte, dautres deux, par exemple).

86

Lart du dveloppement Android

Vous devez congurer chaque ligne diffremment (par exemple pour mettre des icnes diffrentes en fonction des cas).

Dans ces situations, la meilleure solution consiste crer une sous-classe de lAdapter voulu, rednir getView() et construire soi-mme les lignes. La mthode getView() doit renvoyer un objet View reprsentant la ligne situe la position fournie par ladaptateur. Reprenons par exemple le code prcdent pour obtenir, grce getView(), des icnes diffrentes en fonction des lignes une icne pour les mots courts, une autre pour les mots longs. Ce projet se trouve dans le rpertoire FancyLists/Dynamic des exemples :
public class DynamicDemo extends ListActivity { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new IconicAdapter(this)); selection=(TextView)findViewById(R.id.selection); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items[position]); } class IconicAdapter extends ArrayAdapter { Activity context; IconicAdapter(Activity context) { super(context, R.layout.row, items); this.context=context; } public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater=context.getLayoutInflater(); View row=inflater.inflate(R.layout.row, null); TextView label=(TextView)row.findViewById(R.id.label); label.setText(items[position]);

Chapitre 9

Samuser avec les listes

87

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

Le principe consiste rednir getView() pour quelle renvoie une ligne dpendant de lobjet afcher, qui est indiqu par lindice position dans lAdapter. Si vous examinez le code de cette implmentation, vous remarquerez que lon utilise un objet LayoutInflater, ce qui mrite une petite explication.

Quelques mots sur lination


Dans notre cas, "ination" dsigne le fait de convertir une description XML dans 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 ls et on recommence. Heureusement, lquipe qui a cr Android a encapsul ce lourd traitement dans la classe LayoutInflater. Pour nos listes personnalises, par exemple, nous voulons obtenir des Views pour chaque ligne de la liste et nous pouvons donc utiliser la notation XML pour dcrire laspect des lignes. Dans lexemple prcdent, nous transformons la description R.layout.row que nous avions cre dans la section prcdente. Cela nous donne un objet View qui, en ralit, nest autre que notre LinearLayout contenant un ImageView et un TextView, exactement comme cela est spci par R.layout.row. Cependant, au lieu de crer nous-mmes tous ces objets et de les lier ensemble, le code XML et la classe LayoutInflater grent pour nous les "dtails scabreux".

Revenons nos moutons


Nous avons donc utilis LayoutInflater pour obtenir un objet View reprsentant la ligne. Cette ligne est "vide" car le chier de description statique ne sait pas quelles sont les donnes quelle recevra. Il vous appartient donc de la personnaliser et de la remplir comme vous le souhaitez avant de la renvoyer. Cest la raison pour laquelle :

On place le texte du label dans notre widget label en utilisant le mot situ la position passe en paramtre la mthode.

88

Lart du dveloppement Android

On regarde si ce mot fait plus de quatre caractres, auquel cas on recherche le widget ImageView de licne et on remplace sa ressource de base par une autre.

On dispose dsormais dune liste ListView contenant des icnes diffrentes, variant selon les entres correspondantes de la liste (voir Figure 9.2).
Figure 9.2 Lapplication DynamicDemo.

Il sagit bien sr dun exemple assez articiel, 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 efcace. En effet, chaque fois que lutilisateur fait dler lcran, on doit crer tout un lot de nouveaux objets View pour les nouvelles lignes qui safchent. Le framework dAndroid ne mettant pas automatiquement en cache les objets View existants, il faut en recrer de nouveaux, mme pour des lignes que lon avait cres trs peu de temps auparavant. Ce nest donc pas trs efcace, ni du point de vue de lutilisateur, qui risque de constater que la liste est lente, ni du point de vue de la batterie chaque action du CPU consomme de lnergie. Ce traitement supplmentaire est, par ailleurs, aggrav par la charge que lon impose au ramasse-miettes (garbage collector) puisque celui-ci doit dtruire tous les objets que lon cre. Par consquent, moins le code est efcace, plus la batterie du tlphone se dcharge vite et moins lutilisateur est content. On doit donc passer par quelques astuces pour viter ces dfauts.

Chapitre 9

Samuser avec les listes

89

Utilisation de convertView
La mthode getView() reoit en paramtre un objet View nomm, par convention, convertView. Parfois, cet objet est null, auquel cas vous devez crer une nouvelle View pour la ligne (par ination), comme nous lavons expliqu plus haut. Si convertView nest pas null, en revanche, il sagit en fait de lune des View que vous avez dj cres. Ce sera notamment le cas lorsque lutilisateur fait dler la ListView : mesure que de nouvelles lignes apparaissent, Android tentera de rutiliser les vues des lignes qui ont disparu lautre extrmit, vous vitant ainsi de devoir les reconstruire totalement. En supposant que chaque ligne ait la mme structure de base, vous pouvez utiliser findViewById() pour accder aux diffrents widgets qui composent la ligne, modier 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) { 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; }

90

Lart du dveloppement Android

public View getView(int position, View convertView, ViewGroup parent) { View row=convertView; if (row==null) { LayoutInflater inflater=context. getLayoutInflater(); row=inflater.inflate(R.layout.row, null); } TextView label=(TextView)row.findViewById(R.id.label); label.setText(items[position]); ImageView icon=(ImageView)row.findViewById(R.id.icon); if (items[position].length()>4) { icon.setImageResource(R.drawable.delete); } else { icon.setImageResource(R.drawable.ok); } return(row); } } }

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

Chapitre 9

Samuser avec les listes

91

second examen de votre code. Ne tentez pas de tout rgler dun coup, sous peine de vous noyer dans un ocan de View.

Utilisation du patron de conception "support"


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

92

Lart du dveloppement Android

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

ViewWrapper ne dtient pas seulement les widgets ls : elle les recherche uniquement si elle ne les dtient pas dj. Si vous crez un wrapper et que vous nayez jamais besoin dun ls prcis, il ny aura donc jamais aucun appel de findViewById() pour le retrouver et vous naurez jamais payer le prix de ces cycles CPU inutiles. Le patron "support" permet galement deffectuer les traitements suivants :

Il regroupe au mme endroit le transtypage de tous nos widgets, au lieu de le dissminer dans chaque appel findViewById(). Il permet de mmoriser dautres informations sur les lignes, comme leur tat, que nous ne voulons pas insrer dans le modle sous-jacent.

Lutilisation de ViewWrapper consiste simplement crer une instance de cette classe chaque fois que lon cre une ligne par ination et attacher cette instance la vue de la ligne via setTag(), comme dans cette version de getView() :
public class ViewWrapperDemo extends ListActivity { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new IconicAdapter(this)); selection=(TextView)findViewById(R.id.selection); } private String getModel(int position) { return(((IconicAdapter)getListAdapter()).getItem(position)); }

Chapitre 9

Samuser avec les listes

93

public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(getModel(position)); } class IconicAdapter extends ArrayAdapter<String> { Activity context; IconicAdapter(Activity context) { super(context, R.layout.row, items); this.context=context; } public View getView(int position, View convertView, ViewGroup parent) { View row=convertView; ViewWrapper wrapper=null; if (row==null) { LayoutInflater inflater=context. getLayoutInflater(); row=inflater.inflate(R.layout.row, null); wrapper=new ViewWrapper(row); row.setTag(wrapper); } else { wrapper=(ViewWrapper)row.getTag(); } wrapper.getLabel().setText(getModel(position)); if (getModel(position).length()>4) { wrapper.getIcon().setImageResource(R.drawable.delete); } else { wrapper.getIcon().setImageResource(R.drawable.ok); } return(row); } } }

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

94

Lart du dveloppement Android

Crer une liste...


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

Chapitre 9

Samuser avec les listes

95

setListAdapter(new CheckAdapter(this, list)); selection=(TextView)findViewById(R.id.selection); } private RowModel getModel(int position) { return(((CheckAdapter)getListAdapter()).getItem(position)); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(getModel(position).toString()); } class CheckAdapter extends ArrayAdapter<RowModel> { Activity context; CheckAdapter(Activity context, ArrayList<RowModel> list) { super(context, R.layout.row, list); this.context=context; } public View getView(int position, View convertView, ViewGroup parent) { View row=convertView; ViewWrapper wrapper; RatingBar rate; if (row==null) { LayoutInflater inflater=context. getLayoutInflater(); row=inflater.inflate(R.layout.row, null); wrapper=new ViewWrapper(row); row.setTag(wrapper); rate=wrapper.getRatingBar(); RatingBar.OnRatingBarChangeListener l= new RatingBar.OnRatingBarChangeListener() { public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromTouch) { Integer myPosition=(Integer)ratingBar. getTag(); RowModel model=getModel(myPosition); model.rating=rating;

96

Lart du dveloppement Android

LinearLayout parent=(LinearLayout)ratingBar. getParent(); TextView label=(TextView)parent. findViewById(R.id.label); label.setText(model.toString()); } }; rate.setOnRatingBarChangeListener(l); } else { wrapper=(ViewWrapper)row.getTag(); rate=wrapper.getRatingBar(); } RowModel model=getModel(position); wrapper.getLabel().setText(model.toString()); rate.setTag(new Integer(position)); rate.setRating(model.rating); return(row); } } class RowModel { String label; float rating=2.0f; RowModel(String label) { this.label=label; } public String toString() { if (rating>=3.0) { return(label.toUpperCase()); } return(label); } } }

Les diffrences entre cette activit et la prcdente sont les suivantes :

Bien que nous utilisions toujours un tableau de String pour stocker la liste des mots, on le transforme en liste dobjets RowModel au lieu de le fournir un ArrayAdapter. Ici, le RowModel est un ersatz de modle mutable car il se contente de combiner un mot

Chapitre 9

Samuser avec les listes

97

et son tat de slection. Dans un vrai systme, il pourrait sagir dobjets remplis partir dun Cursor et les proprits auraient des signications mtier plus importantes.

Les mthodes utilitaires comme onListItemClick() doivent tre modies pour reter les diffrences entre un modle String pur et lutilisation dun RowModel. Dans getView(), la sous-classe dArrayAdapter (CheckAdapter) teste si convertView est null, auquel cas elle cre une nouvelle ligne par ination dun layout simple (voir le code qui suit) et lui attache un ViewWrapper. Pour la RatingBar de la ligne, on ajoute un couteur onRatingChanged() anonyme qui examine le marqueur de la ligne (getTag()) et le convertit en entier reprsentant la position dans lArrayAdapter de ce quafche cette ligne. La barre dvaluation peut alors obtenir le bon RowModel pour la ligne et mettre jour le modle en fonction de son nouvel tat. Elle modie galement le texte situ ct de la barre pour quil corresponde ltat de celle-ci. On sassure toujours que la RatingBar a le bon contenu et dispose dun marqueur (via setTag()) pointant vers la position de la ligne dans ladaptateur.

La disposition de la ligne est trs simple : elle contient une RatingBar et un label TextView placs dans un conteneur LinearLayout :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <RatingBar android:id="@+id/rate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:numStars="3" android:stepSize="1" android:rating="2" /> <TextView android:id="@+id/label" android:paddingLeft="2px" android:paddingRight="2px" android:paddingTop="2px" android:textSize="40sp" android:layout_width="fill_parent" android:layout_height="wrap_content"/> </LinearLayout>

Le ViewWrapper est tout aussi simple, car il se contente dextraire ces deux widgets de la View de la ligne :
class ViewWrapper { View base;

98

Lart du dveloppement Android

RatingBar rate=null; TextView label=null; ViewWrapper(View base) { this.base=base; } RatingBar getRatingBar() { if (rate==null) { rate=(RatingBar)base.findViewById(R.id.rate); } return(rate); } TextView getLabel() { if (label==null) { label=(TextView)base.findViewById(R.id.label); } return(label); } }

Les Figures 9.3 et 9.4 montrent ce quafche cette application.


Figure 9.3 Lapplication RateListDemo lors de son dmarrage.

Chapitre 9

Samuser avec les listes

99

Figure 9.4 La mme application, montrant un mot avec la note maximale.

Et la vrier deux fois


La liste dvaluation de la section prcdente fonctionne, mais son implmentation est trs lourde. Pire, lessentiel de ce code ennuyeux ne sera pas rutilisable, sauf dans des circonstances trs limites. Nous pouvons mieux faire. En fait, nous voudrions pouvoir crer un layout comme celui-ci :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content"/> <com.commonsware.android.fancylists.seven.RateListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:drawSelectorOnTop="false" /> </LinearLayout>

100

Lart du dveloppement Android

o toute la logique du code qui utilisait une ListView auparavant "fonctionnerait" avec la RateListView du layout :
public class RateListViewDemo extends ListActivity { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items)); selection=(TextView)findViewById(R.id.selection); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items[position]); } }

Les choses se compliquent un tout petit peu lorsquon ralise que, jusqu maintenant, les codes de ce chapitre nont jamais rellement modi la ListView elle-mme. Nous navons fait que travailler sur les adaptateurs, en rednissant getView(), en crant nos propres lignes par ination, etc. Si lon souhaite que RateListView prenne nimporte quel ListAdapter et fonctionne "comme il faut", en plaant les barres dvaluation sur les lignes comme il se doit, nous devons faire un peu de gymnastique. Plus prcisment, nous devons envelopper le ListAdapter "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; }

Chapitre 9

Samuser avec les listes

101

public int getCount() { return(delegate.getCount()); } public Object getItem(int position) { return(delegate.getItem(position)); } public long getItemId(int position) { return(delegate.getItemId(position)); } public View getView(int position, View convertView, ViewGroup parent) { return(delegate.getView(position, convertView, parent)); } public void registerDataSetObserver(DataSetObserver observer) { delegate.registerDataSetObserver(observer); } public boolean hasStableIds() { return(delegate.hasStableIds()); } public boolean isEmpty() { return(delegate.isEmpty()); } public int getViewTypeCount() { return(delegate.getViewTypeCount()); } public int getItemViewType(int position) { return(delegate.getItemViewType(position)); } public void unregisterDataSetObserver(DataSetObserver observer) { delegate.unregisterDataSetObserver(observer); } public boolean areAllItemsEnabled() { return(delegate.areAllItemsEnabled()); } public boolean isEnabled(int position) { return(delegate.isEnabled(position)); } }

102

Lart du dveloppement Android

Nous pouvons alors hriter dAdapterWrapper pour crer RateableWrapper, qui rednit la mthode getView() tout en laissant le ListAdapter dlgu faire "le vrai travail" :
public class RateableWrapper extends AdapterWrapper { Context ctxt=null; float[] rates=null; public RateableWrapper(Context ctxt, ListAdapter delegate) { super(delegate); this.ctxt=ctxt; this.rates=new float[delegate.getCount()]; for (int i=0;i<delegate.getCount();i++) { this.rates[i]=2.0f; } } public View getView(int position, View convertView, ViewGroup parent) { ViewWrapper wrap=null; View row=convertView; if (convertView==null) { LinearLayout layout=new LinearLayout(ctxt); RatingBar rate=new RatingBar(ctxt); rate.setNumStars(3); rate.setStepSize(1.0f); View guts=delegate.getView(position, null, parent); layout.setOrientation(LinearLayout.HORIZONTAL); rate.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT)); guts.setLayoutParams(new LinearLayout. LayoutParams( LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT)); RatingBar.OnRatingBarChangeListener l= new RatingBar.OnRatingBarChangeListener() { public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromTouch) { rates[(Integer)ratingBar.getTag()]=rating; } };

Chapitre 9

Samuser avec les listes

103

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

Lessentiel du traitement de notre liste dvaluation rside dans RateableWrapper. Cette classe place les barres dvaluation sur les lignes et mmorise leurs tats lorsquelles sont modies par lutilisateur. Pour stocker ces tats, on utilise un tableau de float dont la taille correspond au nombre de lignes que delegate rapporte pour cette liste. Limplmentation de la mthode getView() de RateableWrapper rappelle celle de 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). LayoutInflater 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; }

104

Lart du dveloppement Android

RatingBar getRatingBar() { if (rate==null) { rate=(RatingBar)base.getChildAt(0); } return(rate); } void setRatingBar(RatingBar rate) { this.rate=rate; } View getGuts() { if (guts==null) { guts=base.getChildAt(1); } return(guts); } void setGuts(View guts) { this.guts=guts; } }

Lorsque tout ceci est en place, le code de RateListView devient assez simple :
public class RateListView extends ListView { public RateListView(Context context) { super(context); } public RateListView(Context context, AttributeSet attrs) { super(context, attrs); } public RateListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setAdapter(ListAdapter adapter) { super.setAdapter(new RateableWrapper(getContext(), adapter)); } }

On hrite simplement de ListView et on rednit setAdapter() pour pouvoir envelopper dans notre propre RateableWrapper le ListAdapter fourni en paramtre.

Chapitre 9

Samuser avec les listes

105

Comme le montre la Figure 9.5, les rsultats sont identiques ceux de RateListDemo, sauf que les mots ayant la note maximale napparaissent plus en majuscules.
Figure 9.5 Lapplication RateListViewDemo.

La diffrence est la rutilisabilit. Nous pourrions crer un paquetage JAR de RateListView et linsrer dans nimporte quel projet Android qui en a besoin. Par consquent, bien que RateListView soit un peu plus complique crire, il ne faut plus le faire quune seule fois et le reste du code de lapplication est merveilleusement simple. Cette classe RateListView pourrait, bien sr, proposer des fonctionnalits supplmentaires, comme la possibilit de modier par programmation ltat des barres (en mettant jour le tableau de float et la RatingBar elle-mme), autoriser lappel dun autre code lorsque ltat dune barre est modi (via une fonction de rappel), etc. Nous les laissons en exercice au lecteur.

Adapter dautres adaptateurs


Toutes les classes adaptateurs peuvent suivre le modle de conception consistant rednir getView() pour crer les lignes de la liste. Cependant, CursorAdapter et ses sous-classes fournissent une implmentation par dfaut de getView(), qui inspecte la View qui lui est passe en paramtre pour la recycler et appelle newView() si elle est null, ou bindView() dans le cas contraire. Si vous tendez CursorAdapter, qui sert afcher le rsultat dune requte adresse une base de donnes ou un fournisseur de contenu, il est donc prfrable de rednir newView() et bindView() plutt que getView().

106

Lart du dveloppement Android

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

Lutilisation de Cursor sera dcrite au Chapitre 20.

10
Utiliser de jolis widgets et de beaux conteneurs
Les widgets et les conteneurs que nous avons prsents jusqu maintenant ne se trouvent pas seulement dans la plupart des kits de dveloppement graphiques (sous une forme ou sous une autre), mais sont galement trs utiliss dans le dveloppement des applications graphiques, quil sagisse dapplications web, pour les PC ou pour les tlphones. Nous allons maintenant nous intresser des widgets et des conteneurs un peu moins frquents, mais nanmoins trs utiles.

Choisir
Avec des terminaux ayant des capacits de saisie limites comme les tlphones, il est trs utile de disposer de widgets et de botes de dialogue capables danticiper ce que lutilisateur veut taper. Cela minimise le nombre de frappes au clavier et de touches lcran et rduit les risques derreur (la saisie dune lettre la place dun chiffre, par exemple).

108

Lart du dveloppement Android

Comme on la mentionn prcdemment, EditText peut forcer la saisie de nombres, de numros de tlphone, etc. 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 xer une date de dpart, sous la forme dune anne, dun mois et dun jour. Les mois vont de 0 (janvier) 11 (dcembre). Vous pouvez galement prciser un couteur (OnDateChangedListener ou OnDateSetListener) qui sera inform lorsquune nouvelle date a t choisie. Il vous appartient de stocker cette date quelque part, notamment si vous utilisez la bote de dialogue, car vous naurez pas dautre moyen dobtenir ensuite la date choisie. De mme, TimePicker et TimePickerDialog permettent dagir comme suit :

Fixer lheure initiale que lutilisateur peut ensuite ajuster, sous la forme dune heure (de 0 23) et de minutes (de 0 59). Indiquer si le format de la date choisi utilise le mode sur 12 heures avec un indicateur AM/PM ou le mode 24 heures (ce qui, aux tats-Unis, est appel "temps militaire" et qui est le mode utilis partout ailleurs dans le monde). Fournir un couteur (OnTimeChangedListener ou OnTimeSetListener) pour tre prvenu du choix dune nouvelle heure, qui vous sera fournie sous la forme dune heure et de minutes.

Le projet Fancy/Chrono utilise une disposition trs simple, forme dun label et de deux boutons ceux-ci font surgir les botes de dialogue pour choisir une date et une heure :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/dateAndTime" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/dateBtn" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Choisir une date" /> <Button android:id="@+id/timeBtn" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Choisir une heure" /> </LinearLayout>

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

109

La partie intressante se trouve dans le code Java :


public class ChronoDemo extends Activity { DateFormat fmtDateAndTime=DateFormat. getDateTimeInstance(); TextView dateAndTimeLabel; Calendar dateAndTime=Calendar.getInstance(); DatePickerDialog.OnDateSetListener d=new DatePickerDialog. OnDateSetListener() { public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { dateAndTime.set(Calendar.YEAR, year); dateAndTime.set(Calendar.MONTH, monthOfYear); dateAndTime.set(Calendar.DAY_OF_MONTH, dayOfMonth); updateLabel(); } }; TimePickerDialog.OnTimeSetListener t=new TimePickerDialog. OnTimeSetListener() { public void onTimeSet(TimePicker view, int hourOfDay, int minute) { dateAndTime.set(Calendar.HOUR_OF_DAY, hourOfDay); dateAndTime.set(Calendar.MINUTE, minute); updateLabel(); } }; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); Button btn=(Button)findViewById(R.id.dateBtn); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { new DatePickerDialog(ChronoDemo.this, d, dateAndTime.get(Calendar.YEAR), dateAndTime.get(Calendar.MONTH), dateAndTime.get(Calendar.DAY_OF_MONTH)).show(); } }); btn=(Button)findViewById(R.id.timeBtn); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { new TimePickerDialog(ChronoDemo.this, t, dateAndTime.get(Calendar.HOUR_OF_DAY), dateAndTime.get(Calendar.MINUTE), true).show();

110

Lart du dveloppement Android

} }); dateAndTimeLabel=(TextView)findViewById(R.id.dateAndTime); updateLabel(); } private void updateLabel() { dateAndTimeLabel.setText(fmtDateAndTime .format(dateAndTime. getTime())); } }

Le "modle" de cette activit est simplement une instance de Calendar initialise avec la date et lheure courantes et place dans la vue via un formateur DateFormat. La mthode updateLabel() prend le Calendar courant, le formate et le place dans le TextView correspondant au label. Chaque bouton est associ un couteur OnClickListener qui se charge dafcher une bote de dialogue DatePickerDialog ou TimePickerDialog selon le bouton sur lequel on clique. On passe un couteur OnDateSetListener DatePickerDialog pour mettre jour le Calendar avec la nouvelle date (anne, mois, jour). On lui passe galement la dernire date choisie, en rcuprant les valeurs qui se trouvent dans le Calendar. 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 24 heures. Le rsultat de cette activit est prsent aux Figures 10.1, 10.2 et 10.3.
Figure 10.1 Lapplication ChronoDemo lors de son lancement.

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

111

Figure 10.2 La mme application, montrant le dialogue de choix de la date.

Figure 10.3 La mme application, montrant le dialogue de choix de lheure.

Le temps scoule comme un euve


Les widgets DigitalClock ou AnalogClock permettent dafcher lheure sans autoriser les utilisateurs la modier. Il suft simplement de les placer dans votre layout et de les laisser travailler. Le chier main.xml du projet Fancy/Clocks contient ces deux widgets :
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"

112

Lart du dveloppement Android

android:layout_width="fill_parent" android:layout_height="fill_parent" > <AnalogClock android:id="@+id/analog" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_alignParentTop="true" /> <DigitalClock android:id="@+id/digital" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_below="@id/analog" /> </RelativeLayout>

Sans avoir besoin de modier quoi que ce soit au squelette de code Java produit par android create project, on obtient lapplication prsente la Figure 10.4.
Figure 10.4 Lapplication ClocksDemo.

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

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

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

113

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, dnie par un entier allant de 0 (aucune progression) une valeur maximale dnie par setMax() qui indique que lopration sest termine. Par dfaut, une barre de progression part de la valeur 0, mais vous pouvez choisir une autre valeur de dpart via un appel sa mthode setProgress(). Si vous prfrez que la barre soit indtermine, passez la valeur true sa mthode setIndeterminate(). Dans le code Java, vous pouvez xer 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 Chapitre 15.

Utilisation donglets
La philosophie gnrale dAndroid consiste faire en sorte que les activits soient courtes et agrables. Lorsquil y a plus dinformations que ne peut raisonnablement en contenir lcran (bien que lon puisse utiliser des barres de dlement), il peut tre prfrable de produire ces informations supplmentaires par une autre activit, lance via une Intent, comme on lexplique au Chapitre 24. Cependant, ceci peut tre assez compliqu mettre en place. En outre, il arrive parfois que lon doive recueillir et traiter beaucoup dinformations en une seule opration. Dans une interface graphique classique, nous pourrions utiliser des onglets cette n, comme un JTabbedPane en Java/Swing. Avec Android, on dispose dsormais dun conteneur TabHost qui fonctionne exactement de la mme faon une portion de ce quafche lactivit est lie des onglets qui, lorsquon clique dessus, permettent de passer dune partie de la vue une autre. Une activit utilisera par exemple un onglet pour la saisie dun emplacement et un autre pour afcher cet emplacement sur une carte. Certains kits de dveloppement considrent simplement les onglets comme des objets cliquables, permettant de passer dune vue lautre. Dautres les considrent comme une combinaison de llment cliquable et du contenu qui apparat lorsquon clique dessus. Avec Android, les boutons et les contenus des onglets tant des entits distinctes, nous emploierons les termes "bouton de longlet" et "contenu de longlet" dans cette section.

114

Lart du dveloppement Android

Composants
Pour mettre en place les onglets dans une vue, vous avez besoin des widgets et des conteneurs suivants :

TabHost est le conteneur gnral pour les boutons et les contenus des onglets. TabWidget implmente la ligne des boutons des onglets, qui contient les labels et, ventuellement, des icnes. FrameLayout est le conteneur des contenus des onglets : chaque contenu donglet est un ls du FrameLayout.

Cette approche ressemble celle de XUL, utilise par Mozilla : les lments tabbox, tabs et tabpanels de XUL correspondent respectivement TabHost, TabWidget et FrameLayout.

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

Lidentiant android:id du TabWidget doit tre @android:id/tabs. Vous devez rserver un espace de remplissage dans le FrameLayout pour les boutons des onglets. Si vous souhaitez utiliser TabActivity, lidentiant android:id du TabHost doit tre @android:id/tabhost.

TabActivity, comme ListActivity, enveloppe un motif dinterface graphique classique (une activit compose entirement donglets) dans une sous-classe dactivit. Vous nen avez pas ncessairement besoin une activit classique peut trs bien utiliser galement des onglets. Pour une raison que jignore, TabWidget ne semble pas allouer son espace dans le conteneur TabHost ; en dautres termes, quelle que soit la valeur de la proprit android:layout_height de TabWidget, FrameLayout lignore et le place au-dessus du TabHost, provoquant ainsi le masquage des boutons des onglets par leurs contenus. Par consquent, vous devez rserver assez despace dans FrameLayout (avec android:paddingTop) pour "pousser" le contenu des onglets en dessous des boutons. En outre, TabWidget semble toujours se dessiner en laissant de la place pour des icnes, mme si lon nen utilise pas. Avec cette version du kit de dveloppement, vous devez donc rserver au moins 62 pixels ou ventuellement plus en fonction des icnes que vous utilisez. Voici le chier de description dune activit utilisant des onglets, tir du projet Fancy/Tab :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent"

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

115

android:layout_height="fill_parent"> <TabHost android:id="@+id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingTop="62px"> <AnalogClock android:id="@+id/tab1" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_centerHorizontal="true" /> <Button android:id="@+id/tab2" android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="Bouton semi-aleatoire" /> </FrameLayout> </TabHost> </LinearLayout>

Vous remarquerez que les lments TabWidget et FrameLayout sont des ls directs de TabHost et que llment FrameLayout a lui-mme un ls reprsentant les diffrents onglets. Ici, il y en a deux : une horloge et un bouton. Dans un scnario plus compliqu, les onglets seraient regroups dans un conteneur (un LinearLayout, par exemple) avec leurs propres contenus.

Code Java
Le code Java doit indiquer au TabHost quelles sont les vues qui reprsentent les contenus des onglets et quoi doivent ressembler les boutons de ces onglets. Tout ceci est encapsul dans des objets TabSpec. On rcupre une instance de TabSpec via la mthode newTabSpec() du TabHost, on la remplit puis on lajoute au TabHost dans le bon ordre. Les deux mthodes essentielles de TabSpec sont les suivantes :

setContent(), qui permet dindiquer le contenu de cet onglet. Gnralement, il sagit de lidentiant android:id de la vue que lon veut montrer lorsque longlet est choisi. setIndicator(), qui permet de fournir le titre du bouton de longlet. Cette mthode est surcharge pour permettre de fournir galement un objet Drawable reprsentant licne de longlet.

116

Lart du dveloppement Android

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

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

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

117

Figure 10.5 Lapplication TabDemo afchant son premier onglet.

Figure 10.6 La mme application afchant son second onglet.

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

118

Lart du dveloppement Android

utiliser une autre variante de setContent() qui prend en paramtre une instance de TabHost.TabContentFactory qui est simplement une mthode de rappel qui sera appele automatiquement : il suft de fournir une implmentation de createTabContent() et de lutiliser pour construire et renvoyer la vue qui deviendra le contenu de longlet. Cette approche est prsente dans le projet Fancy/DynamicTab. La description de linterface de lactivit met en place les onglets et nen dnit quun seul, contenant un simple bouton :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TabHost android:id="@+id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingTop="62px"> <Button android:id="@+id/buttontab" android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="Bouton semi-aleatoire" /> </FrameLayout> </TabHost> </LinearLayout>

Nous voulons maintenant ajouter de nouveaux onglets mesure quon clique sur ce bouton, ce qui se ralise en quelques lignes de code :
public class DynamicTabDemo extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); final TabHost tabs=(TabHost)findViewById(R.id.tabhost); tabs.setup(); TabHost.TabSpec spec=tabs.newTabSpec("buttontab"); spec.setContent(R.id.buttontab); spec.setIndicator("Bouton"); tabs.addTab(spec);

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

119

tabs.setCurrentTab(0); Button btn=(Button)tabs.getCurrentView().findViewById(R.id.buttontab); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { TabHost.TabSpec spec=tabs.newTabSpec("tag1"); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return(new AnalogClock(DynamicTabDemo.this)); } }); spec.setIndicator("Horloge"); tabs.addTab(spec); } }); } }

On cre un objet TabHost.TabSpec dans la mthode de rappel setOnClickListener() de notre bouton en lui passant en paramtre une fabrique TabHost.TabContentFactory anonyme. Cette fabrique, son tour, renvoie la vue qui sera utilise pour longlet ici une horloge analogique. Le code de construction de cette vue pourrait tre bien plus labor et utiliser, par exemple, un LayoutInflater pour crer une vue partir dun chier de description XML. La Figure 10.7 montre que lactivit nafche quun seul onglet lorsquelle est lance. La Figure 10.8 montre plusieurs onglets, crs en cours dexcution.
Figure 10.7 Lapplication DynamicTabDemo avec son unique onglet initial.

120

Lart du dveloppement Android

Figure 10.8 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 conguration est simple mettre en place, mais ce nest pas le seul : vous pouvez galement intgrer une autre activit partir de votre application via une Intent. Les Intent permettent de prciser ce que vous voulez raliser, puis de demander Android de trouver un moyen de laccomplir, ce qui force souvent lactivit se 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 Chapitre 25.

Tout faire basculer


Parfois, on souhaite bncier de lavantage des onglets (ne voir que certaines vues la fois) sans pour autant utiliser leur prsentation graphique (parce que, par exemple, les onglets prennent trop de place lcran). On peut ainsi prfrer passer dune vue lautre par un mouvement du doigt sur lcran ou en secouant le terminal. La bonne nouvelle est que le mcanisme interne des onglets pour basculer entre les vues est disponible dans le conteneur ViewFlipper, qui peut tre utilis diffremment dun onglet traditionnel.

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

121

ViewFlipper hrite de FrameLayout, que nous avons utilis plus haut pour dcrire le fonctionnement interne dun TabWidget. Cependant, il ne montre que la premire vue lle au dpart : cest vous quil appartient de mettre en place le basculement entre les vues, soit manuellement par une action de lutilisateur, soit automatiquement par un timer. Voici, par exemple, le chier de description du projet Fancy/Flipper1, qui utilise un Button et un ViewFlipper :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:id="@+id/flip_me" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Basculemoi !" /> <ViewFlipper android:id="@+id/details" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textColor="#FF00FF00" android:text="Premier panneau" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textColor="#FFFF0000" android:text="Second panneau" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textColor="#FFFFFF00" android:text="Troisieme panneau" /> </ViewFlipper> </LinearLayout>

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

122

Lart du dveloppement Android

Pour basculer manuellement entre les vues, nous devons ajouter un couteur au bouton pour que le basculement ait lieu lorsquon clique dessus :
public class FlipperDemo extends Activity { ViewFlipper flipper; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); flipper=(ViewFlipper)findViewById(R.id.details); Button btn=(Button)findViewById(R.id.flip_me); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { flipper.showNext(); } }); } }

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

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

123

Figure 10.10 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 modiant son texte et sa couleur chaque clic. Cependant, vous pouvez imaginer que le contenu du ViewFlipper pourrait tre bien plus compliqu inclure, par exemple, tout ce que lon peut mettre dans un TabView. Comme pour un TabWidget, le contenu dun ViewFlipper peut ne pas tre connu lors de la compilation et, comme pour un TabWidget, il est relativement simple dajouter du contenu la vole. Voici, par exemple, le layout dune autre activit, celle du projet Fancy/Flipper2 :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ViewFlipper android:id="@+id/details" android:layout_width="fill_parent" android:layout_height="fill_parent" > </ViewFlipper> </LinearLayout>

Vous remarquerez que llment ViewFlipper na aucun contenu au moment de la compilation. Notez galement quil ny a pas de bouton pour basculer entre les contenus nous reviendrons sur ce point dans un instant.

124

Lart du dveloppement Android

Pour le contenu du ViewFlipper, nous crerons de gros boutons contenant, chacun, un ensemble de mots quelconques. Nous congurerons galement le ViewFlipper pour quil boucle automatiquement sur ces widgets, en utilisant une animation pour la transition :
public class FlipperDemo2 extends Activity { static String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; ViewFlipper flipper; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); flipper=(ViewFlipper)findViewById(R.id.details); flipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_left_in)); flipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_left_out)); for (String item : items) { Button btn=new Button(this); btn.setText(item); flipper.addView(btn, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); } flipper.setFlipInterval(2000); flipper.startFlipping(); } }

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

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

125

Apache 2.0. Comme leur nom lindique, les widgets sont "pousss" gauche pour entrer ou sortir de la zone visible. Aprs avoir parcouru tous les mots en les transformant en autant de boutons ls de lobjet ViewFlipper, nous congurons ce dernier pour quil bascule automatiquement entre ses ls (flipper.setFlipInterval(2000);) et nous lanons le basculement (flipper.startFlipping();). Le rsultat est une suite sans n de boutons apparaissant puis disparaissant vers la gauche au bout de 2 secondes, en tant remplacs chaque fois par le bouton suivant de la squence. Lensemble revient au premier bouton aprs la disparition du dernier (voir Figure 10.11).
Figure 10.11 Lapplication Flipper2, avec une transition anime.

Ce basculement automatique est utile pour les panneaux dinformation ou les autres situations dans lesquelles vous voulez afcher beaucoup dinformations dans un espace rduit. Ces diffrentes vues basculant automatiquement de lune lautre, il serait risqu de demander aux utilisateurs dinteragir avec elles une vue pourrait disparatre au milieu dune interaction.

Fouiller dans les tiroirs


Depuis longtemps, les dveloppeurs Android rclamaient un conteneur de type tiroir, fonctionnant comme celui de lcran daccueil, qui contient les icnes pour lancer les applications. Limplmentation ofcielle existait dans le code open-source mais ntait pas

126

Lart du dveloppement Android

intgre dans le SDK... jusqu Android 1.5, qui fournit dsormais 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 ls direct dun lment LinearLayout. Voici un exemple tir du projet Fancy/DrawerDemo, avec un SlidingDrawer plac dans un FrameLayout :
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FF4444CC" > <SlidingDrawer android:id="@+id/drawer" android:layout_width="fill_parent" android:layout_height="fill_parent" android:handle="@+id/handle" android:content="@+id/content"> <ImageView android:id="@id/handle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/tray_handle_normal" /> <Button android:id="@id/content" android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="Im in here!" /> </SlidingDrawer> </FrameLayout>

Le SlidingDrawer doit contenir :


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

Chapitre 10

Utiliser de jolis widgets et de beaux conteneurs

127

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

Figure 10.13 La mme application avec le tiroir ouvert.

128

Lart du dveloppement Android

Comme on pourrait sy attendre, on peut ouvrir et refermer le tiroir en traitant les vnements "touchs" partir du code Java. Il existe deux groupes de mthodes : les premires agissent instantanment (open(), close() et toggle()), les autres utilisent une animation (animateOpen(), animateClose() et animateToggle()). Le tiroir se verrouille avec lock() et se dverrouille avec unlock() ; lorsquil est verrouill, le tiroir ne rpond pas aux touchs sur lcran. Vous pouvez galement, si vous le souhaitez, enregistrer trois types de mthodes de rappel :

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

Le SlidingDrawer du lanceur, par exemple, change licne de sa poigne pour quelle signie "ouvrir", "fermer" ou "supprimer" (lorsque lon touche pendant un certain temps une icne du bureau). Pour ce faire, il utilise notamment des mthodes de rappel comme celles que nous venons de citer. SlidingDrawer peut tre vertical ou horizontal. Cependant, cette orientation reste 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 Chapitre 19.

Autres conteneurs intressants


Android fournit galement le conteneur AbsoluteLayout, dont le contenu est dispos en fonction de coordonnes spciques on lui indique o placer un ls en prcisant ses coordonnes X, Y, et Android le positionne cet endroit sans poser de question. Ceci a lavantage de fournir un positionnement prcis ; en revanche, cela signie galement que les vues nauront un aspect correct que sur des crans dune certaine dimension, moins dcrire beaucoup de code pour ajuster les coordonnes en fonction de la taille de lcran. Les crans Android pouvant avoir nimporte quelle taille et ces tailles voluant continuellement, lutilisation dAbsoluteLayout risque de devenir assez problmatique. Android dispose galement dune nouvelle variante de liste, ExpandableListView, qui fournit une reprsentation arborescente simplie autorisant deux niveaux de profondeur : les groupes et les ls. Les groupes contiennent les ls, qui sont les "feuilles" de larbre. Cette liste ncessite un nouvel ensemble dadaptateurs car la famille ListAdapter ne fournit aucune information de groupage pour les lments dune liste.

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

130

Lart du dveloppement Android

Variantes de menus
Android distingue les "menus doptions" des "menus contextuels". Les premiers se dclenchent en appuyant sur le bouton "Menu" du terminal, tandis que les seconds souvrent lorsquon maintient une pression du doigt sur le widget qui dispose de ce menu. En outre, les menus doption fonctionnent selon deux modes : icne et tendu. Lorsque lutilisateur appuie sur le bouton Menu, le menu est en mode icne et nafche que les six premiers choix sous la forme de gros boutons faciles slectionner, disposs en ligne en bas de lcran. Si ce menu compte plus de six choix, le sixime bouton afche "Plus" cliquer sur cette option fait passer le menu en mode tendu, qui afche tous les choix restants. Lutilisateur peut bien sr faire dler le menu an deffectuer nimporte quel choix.

Les menus doptions


Au lieu de construire le menu doptions de votre activit dans onCreate(), comme le reste de votre interface graphique, vous devez implmenter la mthode onCreateOptionsMenu() 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)), an quAndroid puisse lui ajouter les choix ncessaires. Puis vous pouvez ajouter les vtres, comme on lexpliquera bientt. Si vous devez modier le menu au cours de lactivit (pour, par exemple, dsactiver un choix devenu inadquat), il suft de manipuler linstance de Menu que vous avez transmise onCreateOptionsMenu() ou dimplmenter la mthode onPrepareOptionsMenu(), qui est appele avant chaque afchage du menu. Pour ajouter des choix au menu, utilisez la mthode add(). Celle-ci est surcharge pour pouvoir recevoir des combinaisons des paramtres suivants :

Un identiant de groupe (un entier), qui doit tre NONE lorsque vous ne regroupez pas un ensemble doptions de menu, utilisable avec setGroupCheckable(). Un identiant de choix (galement un entier) servant identier la slection dans la mthode de rappel onOptionsItemSelected() lorsquune option du menu a t choisie. Un identiant dordre (encore un entier), indiquant lemplacement du choix dans le menu lorsque ce dernier contient des options ajoutes par Android pour linstant, contentez-vous dutiliser NONE. Le texte du choix, sous la forme dune String ou dun identiant de ressource.

Toutes les mthodes add() renvoient une instance de MenuItem qui vous permet ensuite de modier tous les rglages du choix concern (son texte, par exemple). Vous pouvez

Chapitre 11

Utilisation des menus

131

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 par setAlphabeticShortcut() et setNumericShortcut(). Le menu est plac en mode raccourci alphabtique en passant le paramtre true sa mthode setQwertyMode(). Les identiants de choix et de groupe sont des cls servant dverrouiller certaines fonctionnalits supplmentaires des menus :

Un appel MenuItem#setCheckable() avec un identiant de choix ajoute une case cocher ct du titre de ce choix. La valeur de cette case bascule lorsque lutilisateur slectionne ce choix. Un appel Menu#setGroupCheckable() avec un identiant de groupe permet de rendre un ensemble de choix mutuellement exclusifs en leur associant des boutons radio, an de ne pouvoir cocher quune seule option du groupe un instant donn.

Vous pouvez galement appeler addIntentOptions() pour remplir le menu avec des choix correspondant aux activits dun Intent (voir Chapitre 25). Enn, il est possible de crer des sous-menus la vole, en appelant addSubMenu() avec les mmes paramtres que ceux daddMenu(). Android appellera alors onCreatePanelMenu() en lui passant lidentiant du choix du sous-menu, ainsi quune autre instance de Menu reprsentant le sous-menu lui-mme. Comme avec onCreateOptionsMenu(), vous devez tablir un chanage avec la mthode de la superclasse, puis ajouter les choix au sous-menu. La seule restriction est que vous ne pouvez pas imbriquer sans n les 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 onOptionsItemSelected(). Vous recevrez alors lobjet MenuItem correspondant ce choix. Un motif de conception classique consiste utiliser une instruction switch() avec lidentiant du menu (item.getItemId()), an dexcuter laction approprie. Notez quonOptionsItemSelected() est utilise indpendamment du fait que loption choisie soit un choix du menu de base ou dun sous-menu.

Menus contextuels
Le fonctionnement des menus contextuels est quasiment identique celui des menus doptions. Les deux diffrences principales concernent leur remplissage et la faon dont vous serez inform des choix effectus par lutilisateur. Vous devez dabord indiquer le ou les widgets de votre activit qui disposeront de menus contextuels. Pour cela, il suft dappeler la mthode registerForContextMenu() partir de lactivit, en lui passant en paramtre un objet View le widget qui a besoin dun menu contextuel.

132

Lart du dveloppement Android

Puis vous devez implmenter la mthode onCreateContextMenu(), qui, entre autres choses, reoit lobjet View que vous aviez fourni registerForContextMenu(). Si votre activit doit construire plusieurs menus contextuels, ceci permet didentier le menu concern. La mthode onCreateContextMenu() reoit en paramtre le ContextMenu lui-mme, la View laquelle est associ le menu contextuel et un objet ContextMenu.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. Par consquent, vous ne pouvez pas compter sur lobjet ContextMenu fourni ; il faut reconstruire le menu pour quil corresponde aux besoins de votre activit. Pour tre prvenu de la slection dun choix de menu contextuel, implmentez la mthode onContextItemSelected() de lactivit. Dans cette mthode de rappel, vous nobtiendrez que linstance du MenuItem qui a t choisi : si lactivit a plusieurs menus contextuels, vous devez donc vous assurer que les identiants des lments de menus sont uniques an de pouvoir les traiter de faon approprie. Vous pouvez galement appeler la mthode getMenuInfo() du MenuItem an dobtenir lobjet ContextMenu.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 modie de Selection/List (voir Chapitre 9) avec un menu associ. Les menus tant dnis dans le code Java, le chier de description XML nest pas modi et ne sera donc pas reproduit ici. Le code Java comprend en revanche quelques nouveauts :
public class MenuDemo extends ListActivity { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; public static final int EIGHT_ID = Menu.FIRST+1; public static final int SIXTEEN_ID = Menu.FIRST+2; public static final int TWENTY_FOUR_ID = Menu.FIRST+3; public static final int TWO_ID = Menu.FIRST+4; public static final int THIRTY_TWO_ID = Menu.FIRST+5;

Chapitre 11

Utilisation des menus

133

public static final int FORTY_ID = Menu.FIRST+6; public static final int ONE_ID = Menu.FIRST+7; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items)); selection=(TextView)findViewById(R.id.selection); registerForContextMenu(getListView()); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items[position]); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { populateMenu(menu); } @Override public boolean onCreateOptionsMenu(Menu menu) { populateMenu(menu); return(super.onCreateOptionsMenu(menu)); } @Override public boolean onOptionsItemSelected(MenuItem item) { return(applyMenuChoice(item) || super.onOptionsItemSelected(item)); } @Override public boolean onContextItemSelected(MenuItem item) { return(applyMenuChoice(item) || super.onContextItemSelected(item)); } private void populateMenu(Menu menu) { menu.add(Menu.NONE, ONE_ID, Menu.NONE, "1 Pixel"); menu.add(Menu.NONE, TWO_ID, Menu.NONE, "2 Pixels"); menu.add(Menu.NONE, EIGHT_ID, Menu.NONE, "8 Pixels"); menu.add(Menu.NONE, SIXTEEN_ID, Menu.NONE, "16 Pixels"); menu.add(Menu.NONE, TWENTY_FOUR_ID, Menu.NONE, "24 Pixels"); menu.add(Menu.NONE, THIRTY_TWO_ID, Menu.NONE, "32 Pixels"); menu.add(Menu.NONE, FORTY_ID, Menu.NONE, "40 Pixels"); }

134

Lart du dveloppement Android

private boolean applyMenuChoice(MenuItem item) { switch (item.getItemId()) { case ONE_ID: getListView().setDividerHeight(1); return(true); case EIGHT_ID: getListView().setDividerHeight(8); return(true); case SIXTEEN_ID: getListView().setDividerHeight(16); return(true); case TWENTY_FOUR_ID: getListView().setDividerHeight(24); return(true); case TWO_ID: getListView().setDividerHeight(2); return(true); case THIRTY_TWO_ID: getListView().setDividerHeight(32); return(true); case FORTY_ID: getListView().setDividerHeight(40); return(true); } return(false); } }

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

Chapitre 11

Utilisation des menus

135

Dans applyMenuChoice(), nous testons si lun des choix du menu a t slectionn, auquel cas nous xons lpaisseur du sparateur de la liste la valeur correspondante. Comme le montre la Figure 11.1, lactivit initiale a le mme aspect que ListDemo.
Figure 11.1 Lapplication MenuDemo lors de son lancement.

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

136

Lart du dveloppement Android

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

Si lon choisit une valeur (16 pixels, par exemple), lpaisseur des traits de sparation de la liste est modie, comme le montre la Figure 11.4.
Figure 11.4 La mme application dans une version peu esthtique.

La Figure 11.5 montre que lon peut faire apparatre le menu contextuel en "touchant et maintenant" un lment de la liste.

Chapitre 11

Utilisation des menus

137

Figure 11.5 La mme application, avec son menu contextuel.

L encore, le choix dune option modiera lpaisseur du trait de sparation.

Encore de lination
Nous avons vu au Chapitre 9 que lon pouvait dcrire les vues via des chiers XML et les transformer par ination en objets View au moment de lexcution. Android permet de faire de mme avec les menus, qui peuvent tre dcrits dans des chiers XML et 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 chiers XML de description des menus sont placs dans le rpertoire res/menu/, ct des autres types de ressources du projet. Comme pour les layouts, vous pouvez utiliser plusieurs chiers de menus XML, chacun ayant son propre nom et lextension .xml. Voici, par exemple, le contenu du chier sample.xml, extrait du projet Menus/Inflation :
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/close" android:title="Fermer" android:orderInCategory="3"

138

Lart du dveloppement Android

android:icon="@drawable/eject" /> <item android:id="@+id/no_icon" android:orderInCategory="2" android:title="Sans Icne" /> <item android:id="@+id/disabled" android:orderInCategory="4" android:enabled="false" android:title="Inactif" /> <group android:id="@+id/other_stuff" android:menuCategory="secondary" android:visible="false"> <item android:id="@+id/later" android:orderInCategory="0" android:title="Avant-dernier" /> <item android:id="@+id/last" android:orderInCategory="1" android:title="Dernier" /> </group> <item android:id="@+id/submenu" android:orderInCategory="3" android:title="Sous-menu"> <menu> <item android:id="@+id/non_ghost" android:title="Visible" android:visible="true" android:alphabeticShortcut="v" /> <item android:id="@+id/ghost" android:title="Fantome" android:visible="false" android:alphabeticShortcut="f" /> </menu> </item> </menu>

Voici quelques remarques propos des dnitions des menus en XML :


Llment racine doit sappeler menu. Les lments ls de menu sont des objets item et group, ces derniers reprsentant une collection dobjets item pouvant tre manipuls comme un groupe. Les sous-menus sont dclars en ajoutant un lment menu comme ls dun lment item et en utilisant ce nouvel lment pour dcrire le contenu du sous-menu. Pour dtecter quun choix a t slectionn ou faire rfrence un choix ou un groupe partir du code Java, noubliez pas dutiliser lattribut android:id, exactement comme pour les chiers XML des vues.

Chapitre 11

Utilisation des menus

139

Options des menus et XML


Les lments item et group peuvent contenir plusieurs attributs correspondant aux mthodes de Menu et MenuItem.

Titre
Le titre dun choix de menu est fourni par lattribut android:title dun lment item. Ce titre peut tre une chane littrale ou une rfrence vers une ressource chane (@string/truc, par exemple).

Icne
Les choix de menus peuvent possder des icnes qui sont fournies sous la forme dune rfrence vers une ressource Drawable (@drawable/eject, par exemple) via lattribut android:icon dun lment item.

Ordre
Par dfaut, lordre des choix du menu est dtermin par celui de leur apparition dans le chier XML. Vous pouvez modier cet ordre laide de lattribut android: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 chier XML.

Activation
Les item et les group peuvent tre rendus actifs ou inactifs dans le chier XML, laide de leur attribut android:enabled. Par dfaut, ils sont considrs comme actifs. Les objets item et group inactifs apparaissent dans le menu mais ne peuvent pas tre slectionns. Vous pouvez modier ltat dun item en cours dexcution grce la mthode setEnabled() de MenuItem et celui dun group via la mthode setGroupEnabled() de Menu.

Visibilit
De mme, les objets item et group peuvent tre visibles ou non, selon la valeur de leur attribut android:visible. Par dfaut, ils sont visibles ; les objets item et group invisibles napparaissent pas dans le menu. Vous pouvez modier cet tat en cours dexcution en appelant la mthode setVisible() dun MenuItem ou la mthode setGroupVisible() dun Menu. Dans le chier XML prsent plus haut, le groupe other_stuff est invisible. Si nous le rendons visible dans le code Java, les deux choix du groupe apparatront comme par magie.

140

Lart du dveloppement Android

Raccourci
Les item dun menu peuvent avoir des raccourcis en appuyant sur une seule lettre (android:alphabeticShortcut) ou un chiffre (android:numericShortcut), vous pouvez slectionner cet item sans utiliser lcran tactile, le pad ou le curseur pour naviguer dans le menu.

Crer un menu par ination


Lutilisation dun menu dni dans un chier XML est relativement simple puisquil suft de crer un MenuInflater et de lui demander de crer lobjet Menu :
@Override public boolean onCreateOptionsMenu(Menu menu) { theMenu=menu; new MenuInflater(getApplication()) .inflate(R.menu.sample, menu); return(super.onCreateOptionsMenu(menu)); }

12
Polices de caractres
Invitablement, un dveloppeur dapplications doit rpondre la question : "Comment changer cette police de caractres ?" La rponse dpend des polices fournies avec la 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 spcicits.

Sachez apprcier ce que vous avez


Nativement, Android connat trois polices sous les noms raccourcis "sans", "serif" et "monospace". Elles forment la srie Droid, cre par Ascender1 pour lOpen Handset Alliance. Pour utiliser ces polices, il suft de les dsigner dans le chier de description XML. Le layout suivant, par exemple, est extrait du projet Fonts/FontSampler :
<?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"

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

142

Lart du dveloppement 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" android:textSize="20sp" /> <TextView android:id="@+id/custom"

Chapitre 12

Polices de caractres

143

android:text="Bonjour !" android:textSize="20sp" /> </TableRow> </TableLayout>

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

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

144

Lart du dveloppement Android

Figure 12.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"). Par consquent, si vous essayez dutiliser une police et quelle semble ne pas fonctionner, cest srement parce quelle est incompatible avec Android, pour une raison ou pour une autre. En outre, vous avez intrt mettre en minuscules les noms des chiers de polices, an de respecter les conventions utilises par vos autres ressources.

Le problme des glyphes


Les polices TrueType peuvent tre assez lourdes, notamment lorsquelles fournissent un sous-ensemble important des caractres Unicode. La police Handmade Typewriter que nous avons utilise ci-dessus, par exemple, pse plus de 70 Ko et les polices libres DejaVu peuvent atteindre 500 Ko chacune. Mme compresses, elles augmentent donc signicativement 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

Chapitre 12

Polices de caractres

145

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

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

Un navigateur, et en vitesse !
Pour les oprations simples, WebView nest pas trs diffrent des autres widgets dAndroid on le fait surgir dans un layout, on lui indique dans le code Java lURL vers laquelle il doit naviguer et cest ni.

148

Lart du dveloppement Android

Le layout du projet WebKit/Browser, par exemple, ne contient quun WebView :


<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <WebView android:id="@+id/webkit" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>

Comme avec tous les autres widgets, vous devez lui indiquer comment remplir lespace (ici, il occupe tout lespace restant). Le code Java est tout aussi simple :
package com.commonsware.android.webkit; import android.app.Activity; import android.os.Bundle; import android.webkit.WebView; public class BrowserDemo1 extends Activity { WebView browser; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); browser=(WebView)findViewById(R.id.webkit); browser.loadUrl("http://commonsware.com"); } }

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

Chapitre 13

Intgrer le navigateur de WebKit

149

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

Comme le montre la Figure 13.1, lactivit obtenue ressemble un navigateur web sans barre de dlement.
Figure 13.1 Lapplication Browser1.

Comme avec le navigateur classique dAndroid, vous pouvez faire dler la page en la tirant avec le doigt, et le pad directionnel permet de vous positionner successivement sur tous ses lments actifs. Il manque toutefois certaines parties pour en faire un vritable navigateur : une barre de navigation, notamment. Vous pourriez tre tent de remplacer lURL de ce code source par une autre, comme celle de la page daccueil de Google ou toute autre page utilisant JavaScript. Cependant, par dfaut, un widget WebView dsactive JavaScript : pour lactiver, vous devez appeler sa mthode getSettings().setJavaScriptEnabled(true);. Nous reviendrons sur ce point un peu plus loin dans ce chapitre.

150

Lart du dveloppement Android

Chargement immdiat
Il existe deux moyens de faire entrer du contenu dans un widget WebView. Le premier, comme nous lavons vu, consiste indiquer une URL au navigateur et lui faire afcher cette page via loadUrl(). Le navigateur accdera Internet par les moyens mis disposition du terminal cet instant prcis (Wi, rseau cellulaire, partage de donnes par Bluetooth, pigeons voyageurs bien entrans, etc.). Lautre solution consiste utiliser loadData() en lui fournissant le code HTML que lon veut afcher dans le navigateur. Cette dernire mthode est notamment utile pour :

afcher un manuel install sous forme de chier avec lapplication ; afcher des extraits HTML rcuprs par un autre traitement la description dune entre dun ux Atom, par exemple ; produire une interface utilisateur entirement en HTML au lieu dutiliser les widgets Android.

Il existe deux variantes de loadData(). La plus simple permet de fournir sous forme de chanes un contenu, son type MIME et lencodage utilis. Pour un document HTML classique, le type MIME sera gnralement text/html et lencodage, UTF-8. Si vous remplacez lappel de loadUrl() par le code suivant dans lexemple prcdent, vous obtiendrez le rsultat prsent la Figure 13.2.
browser.loadData("<html><body>Bonjour !</body></html>", "text/html", "UTF-8");

Figure 13.2 Lapplication Browser2.

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

Chapitre 13

Intgrer le navigateur de WebKit

151

Navigation au long cours


Comme on la dj mentionn, le widget WebView ne comprend pas de barre de navigation, ce qui permet de lutiliser des endroits o cette barre serait inutile et consommerait inutilement de lespace lcran. Ceci tant dit, il est tout fait possible de lui ajouter des fonctionnalits de navigation, condition de fournir linterface graphique ncessaire. WebView dispose notamment des mthodes suivantes :

reload() permet de recharger la page courante. goBack() permet de revenir un pas en arrire dans lhistorique du navigateur, tandis que canGoBack() teste sil existe un historique pass. goForward() permet davancer dun pas dans lhistorique du navigateur, tandis que canGoForward() teste sil existe un historique futur. goBackOrForward() permet de reculer ou davancer dans lhistorique en fonction de son paramtre. Une valeur ngative reprsente le nombre de pas vers larrire, une valeur positive, le nombre de pas vers lavant. canGoBackOrForward() teste si le navigateur peut reculer ou avancer du nombre de pas indiqu dans lhistorique (en suivant la mme convention positif/ngatif que pour goBackOrForward()). clearCache() vide le cache du navigateur et clearHistory() nettoie son historique.

Amuser le client
Si vous utilisez un widget WebView pour crer une interface utilisateur locale (et non pour naviguer sur le Web), vous aurez besoin de le contrler certains moments, notamment lorsque les utilisateurs cliqueront sur des liens. Vous devrez vous assurer que ces liens sont correctement grs, soit en rechargeant votre propre contenu dans le WebView (en soumettant un Intent Android pour quil ouvre lURL dans un vrai navigateur), soit par dautres moyens (voir Chapitre 25). La mthode setWebViewClient(), qui prend en paramtre une instance dune 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(), onReceivedHttpAuthRequest(), etc.). Un hook classique est shouldOverrideUrlLoading(), qui prend en paramtre une URL et lobjet WebView lui-mme et qui doit renvoyer true si vous traitez la requte ou false si vous prfrez utiliser le traitement par dfaut (qui consiste rcuprer la page web dsigne par lURL). Pour une application de lecture de ux RSS, par exemple, le lecteur ne

152

Lart du dveloppement Android

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 code HTML, reprsentant un lien vers un contenu fourni par une activit, vous pouvez modier vous-mme le WebView. Modions notre premier exemple pour quil devienne lquivalent de la premire application de ce livre : un programme qui afche la date et lheure courantes lorsque lon clique sur un bouton. Le code Java de ce projet WebKit/Browser3 est le suivant :
public class BrowserDemo3 extends Activity { WebView browser; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); browser=(WebView)findViewById(R.id.webkit); browser.setWebViewClient(new Callback()); loadTime(); } void loadTime() { String page="<html><body><a href=\"clock\">" +new Date().toString() +"</a></body></html>"; browser.loadDataWithBaseURL("x-data://base", page, "text/html", "UTF-8", null); } private class Callback extends WebViewClient { public boolean shouldOverrideUrlLoading(WebView view, String url) { loadTime(); return(true); } } }

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

Chapitre 13

Intgrer le navigateur de WebKit

153

Figure 13.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. Vous pouvez modier les rglages dun widget WebView en fonction de vos besoins, via linstance de WebSettings qui est renvoye par la mthode getSettings() du widget. Beaucoup doptions de WebSettings sont ainsi votre disposition. Bien que la plupart semblent assez sotriques (setFantasyFontFamily(), par exemple), quelques-unes peuvent vous tre plus utiles :

Les mthodes setDefaultFontSize() (pour une taille en points) ou setTextSize() (qui utilise des constantes comme LARGER et SMALLEST pour exprimer une taille relative) permettent de modier la taille de la police. setJavaScriptEnabled() et setJavaScriptCanOpenWindowsAutomatically() permettent, respectivement, de dsactiver totalement JavaScript et de lempcher douvrir des fentres popup.

154

Lart du dveloppement Android

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 modications que vous apportez ne sont pas persistantes : vous devez donc les stocker quelque part (via le moteur de prfrences dAndroid, par exemple) si vous autorisez vos utilisateurs modier ces rglages au lieu de les coder en dur dans votre application.

14
Afchage de messages surgissant
Une activit (ou toute autre partie dune application Android) a parfois besoin de sexprimer. Toutes les interactions avec les utilisateurs ne seront pas soignes, bien propres et 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" (Mac OS X), en passant par un tlphone qui vibre. Android dispose de plusieurs moyens dalerter les utilisateurs par dautres systmes que ceux des activits classiques. Les notications, par exemple, sont intimement lies aux Intents et aux services et seront donc prsentes au Chapitre 32. Nous tudierons ici deux mthodes permettant de faire surgir des messages : les toasts et les alertes.

156

Lart du dveloppement Android

Les toasts
Un toast est un message transitoire, ce qui signie quil safche et disparat de lui-mme, sans intervention de lutilisateur. En outre, il ne modie pas le focus de lactivit courante : si lutilisateur est en train dcrire le prochain trait fondamental sur lart de la programmation, ses frappes au clavier ne seront donc pas captures par le message. Un toast tant transitoire, vous navez aucun moyen de savoir si lutilisateur la remarqu. Vous ne recevrez aucun accus de rception de sa part et le message ne restera pas afch sufsamment longtemps pour ennuyer lutilisateur. Un Toast est donc essentiellement conu pour diffuser des messages davertissement pour annoncer quune longue tche en arrire-plan sest termine, que la batterie est presque vide, etc. La cration dun toast est assez simple. La classe Toast fournit une mthode statique makeText() qui prend un objet String (ou un identiant dune ressource textuelle) en paramtre et qui renvoie une instance de Toast. Les autres paramtres de cette mthode sont lactivit (ou tout autre contexte) et une dure valant LENGTH_SHORT ou LENGTH_LONG pour exprimer la dure relative pendant laquelle le message restera visible. Si vous prfrez crer votre Toast partir dune View au lieu dune simple chane ennuyeuse, il suft de crer linstance via le constructeur (qui attend un Context) puis dappeler setView() pour lui indiquer la vue utiliser et setDuration() pour xer sa dure. Lorsque le toast est congur, il suft dappeler sa mthode show() pour quil safche.

Les alertes
Si vous prfrez utiliser le style plus classique des botes de dialogue, choisissez plutt AlertDialog. Comme toute bote de dialogue modale, un AlertDialog souvre, prend le focus et reste afch tant que lutilisateur ne le ferme pas. Ce type dafchage convient donc bien aux erreurs critiques, aux messages de validation qui ne peuvent pas tre afchs correctement dans linterface de base de lactivit ou toute autre information dont vous voulez vous assurer la lecture immdiate par lutilisateur. Pour crer un AlertDialog, le moyen le plus simple consiste utiliser la classe Builder, qui offre un ensemble de mthodes permettant de congurer un AlertDialog. Chacune de ces mthodes renvoie le Builder an de faciliter le chanage des appels. la n, il suft dappeler la mthode show() de lobjet Builder pour afcher la bote de dialogue. Voici les mthodes de conguration de Builder les plus utilises :

setMessage() permet de dnir le "corps" de la bote de dialogue partir dun simple message de texte. Son paramtre est un objet String ou un identiant dune ressource textuelle.

Chapitre 14

Afchage de messages surgissant

157

setTitle() et setIcon() permettent de congurer le texte et/ou licne qui apparatra dans la barre de titre de la bote de dialogue. setPositiveButton(), setNeutralButton() et setNegativeButton() permettent dindiquer les boutons qui apparatront en bas de la bote de dialogue, leur 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 congurations que celles proposes par Builder, appelez la mthode create() la place de show() : vous obtiendrez ainsi une instance dAlertDialog partiellement construite que vous pourrez congurer avant dappeler lune des mthodes show() de lobjet AlertDialog lui-mme. Aprs lappel de show(), la bote de dialogue safche et attend une saisie de lutilisateur.

Mise en uvre
Voici le chier de description XML du projet Messages/Message :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:id="@+id/alert" android:text="Declencher une alerte" android:layout_width="fill_parent" android:layout_height="wrap_content"/> <Button android:id="@+id/toast" android:text="Lever un toast" android:layout_width="fill_parent" android:layout_height="wrap_content"/> </LinearLayout>

Et voici son code Java :


public class MessageDemo extends Activity implements View.OnClickListener { Button alert; Button toast; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle);

158

Lart du dveloppement Android

setContentView(R.layout.main); alert=(Button)findViewById(R.id.alert); alert.setOnClickListener(this); toast=(Button)findViewById(R.id.toast); toast.setOnClickListener(this); } public void onClick(View view) { if (view==alert) { new AlertDialog.Builder(this) .setTitle("MessageDemo") .setMessage("Oups !") .setNeutralButton("Fermeture", new DialogInterface. OnClickListener() { public void onClick(DialogInterface dlg, int sumthin) { // On ne fait rien la bote se fermera ellemme } }) .show(); } else { Toast .makeText(this, "<Clac, Clac>", Toast.LENGTH_SHORT) .show(); } } }

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

Chapitre 14

Afchage de messages surgissant

159

Figure 14.1 Lapplication MessageDemo aprs avoir cliqu sur le bouton "Dclencher une alerte".

Figure 14.2 La mme application, aprs avoir cliqu sur le bouton "Lever un toast".

15
Utilisation des threads
Tout le monde souhaite que ses activits soient ractives. Rpondre rapidement un utilisateur (en moins de 200 millisecondes) est un bon objectif. Au minimum, il faut fournir une rponse en moins de 5 secondes ; sinon lActivityManager pourrait 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. Il y a deux moyens de traiter ce problme :

raliser les oprations coteuses dans un service en arrire-plan en se servant de notications pour demander aux utilisateurs de revenir votre activit ; effectuer le travail coteux dans un thread en arrire-plan.

Android dispose dune vritable panoplie de moyens pour mettre en place des threads en arrire-plan tout en leur permettant dinteragir proprement avec linterface graphique, qui, elle, sexcute dans un thread qui lui est ddi. Parmi eux, citons les objets Handler et le postage dobjets Runnable destination de la vue.

162

Lart du dveloppement Android

Les handlers
Le moyen le plus souple de crer un thread en arrire-plan avec Android consiste crer une instance dune sous-classe de Handler. Vous navez besoin que dun seul objet Handler par activit et il nest pas ncessaire de lenregistrer manuellement ou quoi que ce soit dautre la cration de linstance suft lenregistrer auprs du sous-systme de gestion des threads. Le thread en arrire-plan peut communiquer avec le Handler, qui effectuera tout son travail dans le thread de linterface utilisateur de votre activit. Cest important car les changements de cette interface la modication de ses widgets, par exemple ne doivent intervenir que dans le thread de linterface de lactivit. Vous avez deux possibilits pour communiquer avec le Handler : les messages et les objets Runnable.

Les messages
Pour envoyer un Message un Handler, appelez dabord la mthode obtainMessage() an dextraire lobjet Message du pool. Cette mthode est surcharge pour vous permettre de crer un Message vide ou des messages contenant des identiants de messages et des paramtres. Plus le traitement que doit effectuer le Handler est compliqu, plus il y a de chances que vous deviez placer des donnes dans le Message pour aider le Handler distinguer les diffrents vnements. Puis envoyez le Message au Handler en passant par sa le dattente des messages, laide de lune des mthodes de la famille sendMessage...() :

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

Pour traiter ces messages, votre Handler doit implmenter la mthode handleMessage(), qui sera appele pour chaque message qui apparat dans la le dattente. Cest l que le handler peut modier linterface utilisateur sil le souhaite. Cependant, il doit le faire rapidement car les autres oprations de linterface sont suspendues tant quil na pas termin.

Chapitre 15

Utilisation des threads

163

Le projet Threads/Handler, par exemple, cre une ProgressBar et la modie via un Handler. Voici son chier de description XML :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ProgressBar android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>

La ProgressBar, qui a une largeur et une hauteur normales, utilise galement la proprit style, qui ne sera pas dcrite dans ce livre. Pour linstant, il suft de considrer quelle indique que la barre de progression devra tre trace horizontalement, en montrant la proportion de travail effectu. Voici le code Java :
package com.commonsware.android.threads; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.ProgressBar; public class HandlerDemo extends Activity { ProgressBar bar; Handler handler=new Handler() { @Override public void handleMessage(Message msg) { bar.incrementProgressBy(5); } }; boolean isRunning=false; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); bar=(ProgressBar)findViewById(R.id.progress); } public void onStart() { super.onStart(); bar.setProgress(0); Thread background=new Thread(new Runnable() {

164

Lart du dveloppement Android

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

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

Chapitre 15

Utilisation des threads

165

Figure 15.1 Lapplication HandlerDemo.

Les runnables
Si vous prfrez ne pas vous ennuyer avec les objets Message, vous pouvez galement passer des objets Runnable au Handler, qui les excutera dans le thread de linterface utilisateur. Handler fournit un ensemble de mthodes post...() pour faire passer les objets Runnable an quils soient traits.

Excution sur place


Les mthodes post() et postDelayed() de Handler, qui permettent dajouter des objets Runnable dans la le dattente des vnements, peuvent galement tre utilises avec les vues. Ceci permet de simplier lgrement le code car vous pouvez alors vous passer dun objet Handler. Toutefois, vous perdrez galement un peu de souplesse. En outre, la classe Handler existe depuis longtemps dans Android et a probablement t mieux teste que cette technique.

O est pass le thread de mon interface utilisateur ?


Parfois, vous pouvez ne pas savoir si vous tes en train dexcuter le thread de linterface utilisateur de votre application. Si vous fournissez une partie de votre code sous la forme dune archive JAR, par exemple, vous pouvez ne pas savoir si ce code est excut dans le thread de linterface ou dans un thread en arrire-plan. Pour rsoudre ce problme, la classe Activity fournit la mthode runOnUiThread(). Elle fonctionne comme les mthodes post() de Handler et View car elle met dans une le un

166

Lart du dveloppement Android

Runnable pour quil sexcute dans le thread de linterface si vous ny tes pas dj. Dans le cas contraire, elle appelle immdiatement le Runnable. Vous disposez ainsi du meilleur des deux mondes : aucun dlai si vous tes dans le thread de linterface et aucun problme si vous ny tes pas.

Dsynchronisation
Avec AsyncTask, Android 1.5 a introduit une nouvelle faon de concevoir les oprations en arrire-plan. Grce cette seule classe bien conue, Android soccupera du dcoupage du travail entre le thread de linterface et un thread en arrire-plan. En outre, il allouera et dsallouera lui-mme le thread en arrire-plan et grera une petite le de tches, ce qui accentue encore le ct "prt lemploi" dAsyncTask.

La thorie
Il existe un dicton bien connu des vendeurs : "Lorsquun client achte un foret de 12 mm dans un magasin, il ne veut pas un foret de 13 mm : il veut percer des trous de 13 mm." Les magasins ne peuvent pas vendre les trous, ils vendent donc les outils (des perceuses et des forets) qui facilitent la cration des trous. De mme, les dveloppeurs Android qui se battent avec la gestion des threads en arrireplan ne veulent pas vraiment des threads en arrire-plan : ils souhaitent quun certain travail seffectue en dehors du thread de linterface, an que les utilisateurs ne soient pas bloqus et que les activits ne reoivent pas la redoutable erreur "application not 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) ; rednir une ou plusieurs mthodes dAsyncTask pour raliser le travail en arrireplan ainsi que toute opration associe la tche et qui doit seffectuer dans le thread de linterface (la mise jour de la progression, par exemple) ; le moment venu, crer une instance de la sous-classe dAsyncTask et appeler execute() pour quelle commence son travail.

Vous navez pas besoin :


de crer votre propre thread en arrire-plan ; de terminer ce thread au moment voulu ;

Chapitre 15

Utilisation des threads

167

dappeler des mthodes pour que des traitements seffectuent dans le thread de linterface.

AsyncTask, gnricit et paramtres variables


La cration dune sous-classe dAsyncTask nest pas aussi simple que, par exemple, limplmentation de linterface Runnable car AsyncTask est une classe gnrique ; vous devez donc lui indiquer trois types de donnes :

Le type de linformation qui est ncessaire pour le traitement de la tche (les URL tlcharger, par exemple). Le type de linformation qui est passe la tche pour indiquer sa progression. Le type de linformation qui est passe au code aprs la tche lorsque celle-ci sest termine.

En outre, les deux premiers types sont, en ralit, utiliss avec des paramtres variables, ce qui signie que votre sous-classe dAsyncTask les utilise via des tableaux. Tout cela devrait devenir plus clair ltude dun exemple.

Les tapes dAsyncTask


Pour parvenir vos ns, vous pouvez rednir quatre mthodes dAsyncTask. La seule que vous devez rednir pour que votre classe soit utilisable sappelle 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 spcique. Cependant, noubliez pas que les tches doivent avoir une n il est dconseill dutiliser AsyncTask pour raliser une boucle innie. La mthode doInBackground() recevra en paramtre un tableau variable contenant des lments du premier des trois types mentionns ci-dessus les type des donnes 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. Cette mthode doit renvoyer une valeur du troisime type de donnes mentionn le rsultat de lopration en arrire-plan. Vous pouvez galement rednir onPreExecute(), qui est appele partir du thread de linterface utilisateur avant que le thread en arrire-plan nexcute doInBackground(). Dans cette mthode, vous pourriez par exemple initialiser une ProgressBar ou tout autre indicateur du dbut du traitement. De mme, vous pouvez rednir onPostExecute(), qui est appele partir du thread de linterface graphique lorsque doInBackground() sest termine. Cette mthode reoit en paramtre la valeur renvoye par doInBackground() (un indicateur de succs ou dchec,

168

Lart du dveloppement Android

par exemple). Vous pouvez donc utiliser cette mthode pour supprimer la ProgressBar et utiliser le travail effectu en arrire-plan pour mettre jour le contenu dune liste. onProgressUpdate() est la quatrime mthode que vous pouvez rednir. Si doInBackground() 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 arrireplan a progress en mettant jour une ProgressBar ou en continuant une animation, par exemple. Cette mthode reoit un tableau variable dlments du second type mentionn plus haut, contenant les donnes publies par doInBackground() via publishProgress().

Exemple de tche
Comme on la indiqu, limplmentation dune classe AsyncTask nest pas aussi simple que celle dune classe Runnable. Cependant, une fois passe la difcult de la gnricit et des paramtres variables, ce nest pas si compliqu que cela. Le projet Threads/Asyncer implmente une ListActivity qui utilise une AsyncTask :
package com.commonsware.android.async; import android.app.ListActivity; import android.os.AsyncTask; import android.os.Bundle; import android.os.SystemClock; import android.widget.ArrayAdapter; import android.widget.Toast; import java.util.ArrayList; public class AsyncDemo extends ListActivity { private static String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);

Chapitre 15

Utilisation des threads

169

setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList())); new AddStringTask().execute(); } class AddStringTask extends AsyncTask<Void, String, Void> { @Override protected Void doInBackground(Void... inutilise) { for (String item : items) { publishProgress(item); SystemClock.sleep(200); } return(null); } @Override protected void onProgressUpdate(String... item) { ((ArrayAdapter)getListAdapter()).add(item[0]); } @Override protected void onPostExecute(Void inutilise) { Toast .makeText(AsyncDemo.this, "Fini !", Toast.LENGTH_SHORT) .show(); } } }

Il sagit dune autre variante de la liste de mots lorem ipsum qui a dj t souvent utilise dans ce livre. Cette fois-ci, au lieu de simplement fournir la liste un ArrayAdapter, on simule la cration de ces mots dans un thread en arrire-plan laide de la classe AddStringTask, notre implmentation dAsyncTask. Les sections qui suivent passent en revue les diffrentes parties de ce code.

Dclaration dAddStringTask
class AddStringTask extends AsyncTask<Void, String, Void> {

On utilise ici la gnricit pour congurer les types de donnes spciques dont nous aurons besoin dans AddStringTask :

Nous navons besoin daucune information de conguration ; par consquent, le premier type est Void. Nous voulons passer onProgressUpdate() chaque chane "produite" par notre tche en arrire-plan, an de pouvoir lajouter la liste. Le second type est donc String. Nous ne renvoyons pas de rsultat proprement parler (hormis les mises jour) ; par consquent, le troisime type est Void.

170

Lart du dveloppement Android

La mthode doInBackground()
@Override protected Void doInBackground(Void... inutilise) { for (String item : items) { publishProgress(item); SystemClock.sleep(200); } return(null); }

La mthode doInBackground() tant appele dans un thread en arrire-plan, elle peut durer aussi longtemps quon le souhaite. Dans une vraie application, nous pourrions, par exemple, parcourir une liste dURL et toutes les tlcharger. Ici, nous parcourons notre liste statique de mots latins en appelant publishProgress() pour chacun deux, puis nous nous mettons en sommeil pendant le cinquime de seconde pour simuler un traitement. Comme nous avons choisi de nutiliser aucune information de conguration, nous navons normalement pas besoin de paramtre pour doInBackground(). Cependant, le contrat dimplmentation dAsyncTask prcise quil faut prendre un tableau variable dlments du premier type gnrique : cest la raison pour laquelle le paramtre de cette mthode est Void... inutilise. En outre, ayant choisi de ne rien renvoyer alors que le contrat stipule que nous devons renvoyer un objet du troisime type gnrique, le rsultat de cette mthode est de type Void et nous renvoyons null.

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

La mthode onProgressUpdate() est appele dans le thread de linterface et nous voulons quelle signale lutilisateur que lon est en train de charger les chanes. Ici, nous ajoutons simplement la chane lArrayAdapter, an quelle soit ajoute la n de la liste. Cette mthode attend un tableau variable dlments du deuxime type gnrique (String... ici). Comme nous ne lui passons quune seule chane par appel publishProgress(), on ne doit examiner que le premier lment du tableau item.

La mthode onPostExecute()
@Override protected void onPostExecute(Void inutilise) { Toast

Chapitre 15

Utilisation des threads

171

.makeText(AsyncDemo.this, "Fini !", Toast.LENGTH_SHORT) .show(); }

La mthode onPostExecute() est appele dans le thread de linterface et nous voulons quelle signale que lopration qui sexcutait en arrire-plan sest termine. Dans une vraie application, cela pourrait consister supprimer une ProgressBar ou stopper une animation, par exemple. Ici, nous nous contentons de "lever un toast". Son paramtre est Void inutilise pour respecter le contrat dimplmentation.

Lactivit
new AddStringTask().execute();

Pour utiliser AddStringTask, nous crons simplement une instance de cette classe et appelons sa mthode execute(). Ceci a pour effet de lancer la chane des vnements qui font raliser le travail par le thread en arrire-plan. Si AddStringTask avait demand des paramtres de conguration, nous naurions pas utilis Void comme premier type gnrique et le constructeur aurait attendu zro ou plusieurs paramtres de ce type. Ces valeurs auraient ensuite t passes doInBackground().

Le rsultat
Comme le montre la Figure 15.2, cette activit afche la liste qui se remplit "en temps rel" pendant quelques secondes, puis afche un toast pour indiquer que le traitement est termin.
Figure 15.2 Lapplication AsyncDemo, au milieu du chargement de la liste des mots.

172

Lart du dveloppement Android

viter les piges


Les threads en arrire-plan ne sont pas de mignons bbs bien sages, mme en passant par le systme des Handler dAndroid. Non seulement ils ajoutent de la complexit, mais ils ont un cot rel en termes de mmoire, de CPU et de batterie. Cest pour cette raison que vous devez tenir compte dun grand nombre de scnarios lorsque vous les utilisez. Parmi eux :

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

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

174

Lart du dveloppement Android

Lactivit de Schroedinger
En gnral, une activit se trouve toujours dans lun des quatre tats suivants :

Active. Lactivit a t lance par lutilisateur, elle sexcute au premier plan. Cest cet tat que lon pense quand on voque le fonctionnement dune activit. En pause. Lactivit a t lance par lutilisateur, elle sexcute et elle est visible, mais une notication ou un autre vnement occupe une partie de lcran. Pendant ce temps, lutilisateur voit lactivit mais peut ne pas tre capable dinteragir avec elle. Lorsquun appel tlphonique est reu, lutilisateur a lopportunit de prendre cet appel ou de lignorer, par exemple. Stoppe. Lactivit a t lance par lutilisateur, elle sexcute mais est cache par dautres activits qui ont t lances ou vers lesquelles le systme a bascul. Votre application ne pourra rien prsenter dintressant lutilisateur directement : elle ne peut passer que par une Notification. Morte. Lactivit na jamais t lance (le tlphone vient dtre rinitialis, par exemple) ou elle a t tue, probablement cause dun manque de mmoire.

Vie et mort dune activit


Android fera appel votre activit en considrant les transitions entre les quatre tats que nous venons de prsenter. Certaines transitions peuvent provoquer plusieurs appels votre activit, via les mthodes prsentes dans cette section ; parfois, Android tuera votre application sans lappeler. Tout ceci est assez ou et sujet modications : cest la raison pour laquelle vous devez consulter attentivement la documentation ofcielle dAndroid en plus de cette section pour dcider des vnements qui mritent attention et de ceux que vous pouvez ignorer. Notez que vous devez appeler les versions de la superclasse lorsque vous implmentez les mthodes dcrites ici ; sinon Android peut lever une exception.

onCreate() et onDestroy()
Tous les exemples que nous avons vus jusqu maintenant ont implment onCreate() dans leurs sous-classes dActivity. Cette mthode sera appele dans les trois cas suivants :

Lorsque lactivit est lance pour la premire fois (aprs le redmarrage du systme, par exemple), onCreate() est appele avec le paramtre null. Si lactivit sest excute, puis quelle a t tue, onCreate() sera appele avec, pour paramtre, le Bundle obtenu par onSaveInstanceState() (voir plus loin).

Chapitre 16

Gestion des vnements du cycle de vie dune activit

175

Si lactivit sest excute et que vous layez congure pour quelle utilise des ressources diffrentes en fonction des tats du terminal (mode portrait ou mode paysage, par exemple), elle sera recre et onCreate() sera donc appele.

Cest dans cette mthode que vous congurez linterface utilisateur et tout ce qui ne doit tre fait quune seule fois, quelle que soit lutilisation de lactivit. lautre extrmit du cycle de vie, onDestroy() peut tre appele lorsque lactivit prend n, soit parce quelle a appel finish() (qui "nit" lactivit), soit parce quAndroid a besoin de mmoire et la ferme prmaturment. onDestroy() peut ne pas tre appele si ce besoin de mmoire est urgent (la rception dun appel tlphonique, par exemple) et que lactivit se terminera quoi quil en soit. Par consquent, onDestroy() est 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 ux RSS, par exemple), onResume() est le bon moyen de rafrachir la vue courante et, si ncessaire, de lancer un thread en arrire-plan (via un Handler, par exemple) pour modier cette vue. Inversement, tout ce qui dtourne lutilisateur de votre activit essentiellement lactivation dune autre activit provoquera lappel donPause(). Vous pouvez proter de cette mthode pour annuler tout ce que vous aviez fait dans onResume() : arrter les threads en arrire-plan, librer les ressources en accs exclusif que vous auriez pu prendre (lappareil photo, par exemple), etc. Lorsque onPause() a t appele, Android se rserve le droit de tuer tout moment le processus de lactivit. Par consquent, vous ne devriez pas supposer que vous pourrez recevoir dautre vnement de la part de celle-ci.

176

Lart du dveloppement Android

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 utilisait la calculette devrait donc retrouver le ou les nombres sur lesquels il travaillait lorsquil la rutilise aprs une absence sauf sil avait lui-mme ferm la calculette. Pour que tout cela fonctionne, les activits doivent donc pouvoir sauvegarder, rapidement et efcacement, ltat de linstance de lapplication quelles excutent. En outre, comme elles peuvent tre tues tout moment, les activits peuvent devoir sauvegarder cet tat plus frquemment quon ne pourrait le supposer. Rciproquement, une activit qui redmarre doit rcuprer son tat antrieur an dapparatre dans la situation o elle se trouvait prcdemment. La sauvegarde de ltat dune instance est gre par la mthode onSaveInstanceState(), qui fournit un objet Bundle dans lequel lactivit peut placer les donnes quelle souhaite (le nombre afch par la calculette, par exemple). Limplmentation de cette mthode doit tre rapide nessayez pas den faire trop : placez simplement les donnes dans le Bundle et quittez la mthode. Vous pouvez rcuprer ltat de linstance dans les mthodes onCreate() et onRestoreInstanceState() : cest vous qui dcidez du moment dappliquer cet tat votre activit lune ou lautre de ces mthodes de rappel convient.

Partie

III

Stockage de donnes, services rseaux et API


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

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

Obtenir ce que vous voulez


Pour accder aux prfrences, vous pouvez : appeler la mthode getPreferences() partir de votre activit pour accder ses prfrences spciques ; appeler la mthode getSharedPreferences() partir de votre activit (ou dun autre Context de lapplication) pour accder aux prfrences de lapplication ;

180

Lart du dveloppement Android

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 valeur 0. getSharedPreferences() attend galement le nom dun ensemble de prfrences : en ralit, getPreferences() appelle getSharedPreferences() en lui passant le nom de classe de votre activit en paramtre. getDefaultSharedPreferences() prend en paramtre le Context des prfrences (cest--dire votre Activity). Toutes ces mthodes renvoient une instance de SharedPreferences qui fournit un 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.

Dnir vos prfrences


partir dun objet SharedPreferences, vous pouvez appeler edit() pour obtenir un "diteur" pour les prfrences. Cet objet dispose dun ensemble de mthodes modicatrices limage des mthodes daccs de lobjet parent SharedPreferences. Il fournit galement les mthodes suivantes : remove() pour supprimer une prfrence par son nom ; clear() pour supprimer toutes les prfrences ; commit() pour valider les modications effectues via lditeur. La dernire est importante : si vous modiez les prfrences avec lditeur et que vous nappeliez pas commit(), les modications disparatront lorsque lditeur sera hors de porte. Inversement, comme les prfrences acceptent des modications en direct, si lune des parties de votre application (une activit, par exemple) modie des prfrences partages, les autres parties (tel un service) auront immdiatement accs la nouvelle valeur.

Un mot sur le framework


partir de sa version 0.9, Android dispose dun framework de gestion des prfrences qui ne change rien ce que nous venons de dcrire. En fait, il existe surtout pour prsenter un ensemble cohrent de prfrences aux utilisateurs, an que les applications naient pas rinventer la roue. Llment central de ce framework est encore une structure de donnes XML. Vous pouvez dcrire les prfrences de votre application dans un chier XML stock dans le rpertoire res/xml/ du projet. partir de ce chier, Android peut prsenter une interface graphique pour manipuler ces prfrences, qui seront ensuite stockes dans lobjet SharedPreferences obtenu par getDefaultSharedPreferences().

Chapitre 17

Utilisation des prfrences

181

Voici, par exemple, le contenu dun chier XML des prfrences, extrait du projet Prefs/ Simple :
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key="@string/checkbox" android:title="Preference case a cocher" android:summary="Cochez ou decochez" /> <RingtonePreference android:key="@string/ringtone" android:title="Preference sonnerie" android:showDefault="true" android:showSilent="true" android:summary="Choisissez une sonnerie" /> </PreferenceScreen>

La racine de ce document XML est un lment PreferenceScreen (nous expliquerons plus loin pourquoi il sappelle ainsi). Comme le montre ce chier, PreferenceScreen peut contenir des dnitions de prfrences des sous-classes de Preference, comme CheckBoxPreference ou RingtonePreference. Comme lon pourrait sy attendre, elles permettent, respectivement, de cocher une case et de choisir une sonnerie. Dans le cas de RingtonePreference, vous pouvez autoriser les utilisateurs choisir la sonnerie par dfaut du systme ou "silence".

Laisser les utilisateurs choisir


Lorsque vous avez mis en place le chier XML des prfrences, vous pouvez utiliser une activit "quasi intgre" pour permettre aux utilisateurs de faire leur choix. Cette activit est "quasi intgre" car il suft den crer une sous-classe, de la faire pointer vers ce chier et de la lier au reste de votre application. Voici, par exemple, lactivit EditPreferences du projet Prefs/Simple :
package com.commonsware.android.prefs; import android.app.Activity; import android.os.Bundle; import android.preference.PreferenceActivity; public class EditPreferences extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } }

182

Lart du dveloppement Android

Comme vous pouvez le constater, il ny a pas grand-chose lire car il suft dappeler addPreferencesFromResource() en lui indiquant la ressource XML contenant les prfrences. Vous devrez galement ajouter cette activit votre chier AndroidManifest.xml :
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.prefs"> <application android:label="@string/app_name"> <activity android:name=".SimplePrefsDemo" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".EditPreferences" android:label="@string/app_name"> </activity> </application> </manifest>

Voici le code de SimplePrefsDemo, qui permet dappeler cette activit partir dun menu doptions :
@Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, EDIT_ID, Menu.NONE, "Modifier Prefs") .setIcon(R.drawable.misc) .setAlphabeticShortcut(m); menu.add(Menu.NONE, CLOSE_ID, Menu.NONE, "Fermeture") .setIcon(R.drawable.eject) .setAlphabeticShortcut(f); return(super.onCreateOptionsMenu(menu)); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case EDIT_ID: startActivity(new Intent(this, EditPreferences.class)); return(true); case CLOSE_ID: finish(); return(true); } return(super.onOptionsItemSelected(item)); }

Chapitre 17

Utilisation des prfrences

183

La Figure 17.1 montre linterface de conguration des prfrences de cette application.


Figure 17.1 Linterface des prfrences de SimplePrefsDemo.

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

Vous remarquerez quil nexiste pas de bouton "Save" ou "Commit" : les modications sont sauvegardes ds quelles sont faites.

184

Lart du dveloppement Android

Outre ce menu, lapplication SimplePrefsDemo afche galement les prfrences courantes via un TableLayout :
<?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TableRow> <TextView android:text="Case a cocher:" android:paddingRight="5px" /> <TextView android:id="@+id/checkbox" /> </TableRow> <TableRow> <TextView android:text="Sonnerie:" android:paddingRight="5px" /> <TextView android:id="@+id/ringtone" /> </TableRow> </TableLayout>

Les champs de cette table se trouvent dans la mthode onCreate() :


@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); checkbox=(TextView)findViewById(R.id.checkbox); ringtone=(TextView)findViewById(R.id.ringtone); }

Ils sont mis jour chaque appel donResume() :


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

Ceci signie que les champs seront modis louverture de lactivit et aprs la n de lactivit des prfrences (via le bouton "back", par exemple).

Chapitre 17

Utilisation des prfrences

185

La Figure 17.3 montre le contenu de cette table aprs le choix de lutilisateur.


Figure 17.3 Liste des prfrences de SimplePrefsDemo.

Ajouter un peu de structure


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

186

Lart du dveloppement Android

<CheckBoxPreference android:key="@string/checkbox" android:title="Preference case a cocher" android:summary="Cochez ou decochez" /> <RingtonePreference android:key="@string/ringtone" android:title="Preference sonnerie" android:showDefault="true" android:showSilent="true" android:summary="Choisissez une sonnerie" /> </PreferenceCategory> <PreferenceCategory android:title="Ecrans"> <PreferenceScreen android:key="detail" android:title="Ecran" android:summary="Preferences supplementaires dans une autre page"> <CheckBoxPreference android:key="@string/checkbox2" android:title="Une autre case" android:summary="On ou Off. Peu importe." /> </PreferenceScreen> </PreferenceCategory> </PreferenceScreen>

Utilis avec notre implmentation de PreferenceActivity, ce chier XML produit une liste dlments comme ceux de la Figure 17.4, regroups en catgories.
Figure 17.4 Linterface des prfrences de StructuredPrefsDemo, montrant les catgories et un marqueur dcran.

Chapitre 17

Utilisation des prfrences

187

En touchant lentre du sous-cran, celui-ci safche et montre les prfrences quil contient (voir Figure 17.5).
Figure 17.5 Le sous-cran de prfrences de StructuredPrefsDemo.

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

188

Lart du dveloppement Android

android:summary="Choisissez une sonnerie" /> </PreferenceCategory> <PreferenceCategory android:title="Ecrans"> <PreferenceScreen android:key="detail" android:title="Ecran" android:summary="Preferences supplementaires dans une autre page"> <CheckBoxPreference android:key="@string/checkbox2" android:title="Autre case a cocher" android:summary="On ou Off. Peu importe." /> </PreferenceScreen> </PreferenceCategory> <PreferenceCategory android:title="Preferences simples"> <EditTextPreference android:key="@string/text" android:title="Dialogue de saisie dun texte" android:summary="Cliquez pour ouvrir un champ de saisie" android:dialogTitle="Entrez un texte interessant" /> <ListPreference android:key="@string/list" android:title="Dialogue de choix" android:summary="Cliquez pour ouvrir une liste de choix" android:entries="@array/villes" android:entryValues="@array/codes_aeroports" android:dialogTitle="Choisissez une ville" /> </PreferenceCategory> </PreferenceScreen>

Pour le champ de texte (EditTextPreference), outre le titre et le rsum de la prfrence elle-mme, nous donnons galement un titre la bote de dialogue. Pour la liste (ListPreference), on fournit la fois un titre au dialogue et deux ressources de type tableau de chanes : lun pour les noms afchs, lautre pour les valeurs correspondantes qui doivent tre dans le mme ordre lindice du nom afch dtermine la valeur stocke dans lobjet SharedPreferences. Voici par exemple les tableaux utiliss pour cette liste :
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="villes"> <item>Philadelphia</item> <item>Pittsburgh</item> <item>Allentown/Bethlehem</item> <item>Erie</item> <item>Reading</item>

Chapitre 17

Utilisation des prfrences

189

<item>Scranton</item> <item>Lancaster</item> <item>Altoona</item> <item>Harrisburg</item> </string-array> <string-array name="codes_aeroports"> <item>PHL</item> <item>PIT</item> <item>ABE</item> <item>ERI</item> <item>RDG</item> <item>AVP</item> <item>LNS</item> <item>AOO</item> <item>MDT</item> </string-array> </resources>

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

Toucher lentre "Dialogue de saisie dun texte" provoque lafchage dun... dialogue de saisie dun texte ici, il est prrempli avec la valeur courante de la prfrence (voir Figure 17.7). Toucher lentre "Dialogue de choix" afche... une liste de choix sous forme de bote de dialogue prsentant les noms des villes (voir Figure 17.8).

190

Lart du dveloppement Android

Figure 17.7 Modication dune prfrence avec un champ de saisie.

Figure 17.8 Modication dune prfrence avec une liste.

18
Accs aux chiers
Bien quAndroid dispose de moyens de stockage structurs via les prfrences et les bases de donnes, un simple chier suft parfois. Android offre donc deux modles daccs aux chiers : lun pour les chiers fournis dans le paquetage de lapplication, un autre pour ceux qui sont crs sur le terminal par lapplication.

Allons-y !
Supposons que vous vouliez fournir des donnes statiques avec une application : une liste de mots pour un correcteur orthographique, par exemple. Le moyen le plus simple dy parvenir consiste placer ces donnes dans un chier situ dans le rpertoire res/raw: elles seront alors intgres au chier APK de lapplication comme une ressource brute au cours du processus dempaquetage. Pour accder ce chier, vous avez besoin dun objet Resources que vous pouvez obtenir partir de lactivit en appelant getResources(). Cet objet fournit la mthode openRawResource() pour rcuprer un InputStream sur le chier spci par son identiant (un entier). Cela fonctionne exactement comme laccs aux widgets avec findViewById() : si vous placez un chier mots.xml dans res/raw, son identiant dans le code Java sera R.raw.mots.

192

Lart du dveloppement Android

Comme vous ne pouvez obtenir quun InputStream, vous navez aucun moyen de modier ce chier : 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 modiable (une base de donnes, par exemple), mais cela impose davoir deux copies des donnes. Une autre solution consiste les conserver telles quelles et placer les modications dans un autre chier ou dans une base de donnes, puis les fusionner lorsque vous avez besoin dune vue complte de ces informations. Si votre application fournit une liste dURL, par exemple, vous pourriez grer un deuxime chier pour stocker les URL ajoutes par lutilisateur ou pour rfrencer celles quil a supprimes. Le projet Files/Static reprend lexemple ListViewDemo du Chapitre 8 en utilisant cette fois-ci un chier XML la place dun tableau dni directement dans le programme. Le chier de description XML est identique dans les deux cas :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:drawSelectorOnTop="false" /> </LinearLayout>

On a galement besoin dun autre chier XML contenant les mots de la liste :
<words> <word <word <word <word <word <word <word <word <word <word value="lorem" /> value="ipsum" /> value="dolor" /> value="sit" /> value="amet" /> value="consectetuer" /> value="adipiscing" /> value="elit" /> value="morbi" /> value="vel" />

Chapitre 18

Accs aux chiers

193

<word value="ligula" /> <word value="vitae" /> <word value="arcu" /> <word value="aliquet" /> <word value="mollis" /> <word value="etiam" /> <word value="vel" /> <word value="erat" /> <word value="placerat" /> <word value="ante" /> <word value="porttitor" /> <word value="sodales" /> <word value="pellentesque" /> <word value="augue" /> <word value="purus" /> </words>

Bien que cette structure XML ne soit pas exactement un modle de concision, elle sufra pour notre dmonstration. Le code Java doit maintenant lire ce chier, en extraire les mots et les placer quelque part pour que lon puisse remplir la liste :
public class StaticFileDemo extends ListActivity { TextView selection; ArrayList<String> items=new ArrayList<String>(); @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); selection=(TextView)findViewById(R.id.selection); try { InputStream in=getResources().openRawResource(R.raw.mots); DocumentBuilder builder=DocumentBuilderFactory .newInstance() .newDocumentBuilder(); Document doc=builder.parse(in, null); NodeList words=doc.getElementsByTagName("word"); for (int i=0;i<words.getLength();i++) { items.add(((Element)words.item(i)).getAttribute("value")); } in.close(); }

194

Lart du dveloppement Android

catch (Throwable t) { Toast .makeText(this, "Exception : " + t.toString(), 2000) .show(); } setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items)); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items.get(position).toString()); } }

Les diffrences entre cet exemple et celui du Chapitre 8 se situent essentiellement dans le corps de la mthode onCreate(). Ici, on obtient un InputStream pour le chier XML en appelant getResources().openRawResource(R.raw.mots), puis on se sert des fonctionnalits danalyse XML prdnies pour transformer le chier en document DOM, en extraire les lments word et placer les valeurs de leurs attributs value dans un objet ArrayList qui sera utilis par lArrayAdapter. Comme le montre la Figure 18.1, le rsultat de lactivit est identique celui lexemple du Chapitre 8 car la liste de mots est la mme.
Figure 18.1 Lapplication StaticFileDemo.

Chapitre 18

Accs aux chiers

195

Comme nous le verrons au chapitre suivant, il existe videmment des moyens encore plus simples dutiliser les chiers XML contenus dans le paquetage dune application : utiliser une ressource XML, par exemple. Cependant, bien que cet exemple utilise XML, le chier aurait pu simplement contenir un mot par ligne ou utiliser un format non reconnu nativement par le systme de ressources dAndroid.

Lire et crire
Lire et crire ses propres chiers de donnes spciques est quasiment identique ce que lon pourrait faire avec une application Java traditionnelle. La solution consiste utiliser les mthodes openFileInput() et openFileOutput() sur lactivit ou tout autre Context an dobtenir, respectivement, un InputStream et un OutputStream. partir de l, le processus nest pas beaucoup diffrent de celui des E/S Java classiques :

On enveloppe ces ux selon les besoins, par exemple avec un InputStreamReader ou un OutputStreamWriter si lon souhaite effectuer des E/S en mode texte. On lit ou on crit les donnes. On libre le ux avec close() lorsquon a termin.

Deux applications qui essaient de lire en mme temps un chier notes.txt via openFileInput() accderont chacune leur propre dition du chier. Si vous voulez quun mme chier soit accessible partir de plusieurs endroits, vous devrez srement crer un fournisseur de contenu, comme on lexplique au Chapitre 28. Notez galement quopenFileInput() et openFileOutput() ne prennent pas en paramtre des chemins daccs (chemin/vers/fichier.txt, par exemple), mais uniquement des noms de chiers. Le code suivant, extrait du projet Files/ReadWrite, montre la disposition de lditeur de texte le plus simple du monde :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <Button android:id="@+id/close" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fermer" /> <EditText android:id="@+id/editor" android:layout_width="fill_parent" android:layout_height="fill_parent" android:singleLine="false" /> </LinearLayout>

196

Lart du dveloppement Android

Les deux seuls lments de linterface sont un gros widget ddition de texte et un bouton "Fermer" plac en dessous. Le code Java est peine plus compliqu :
package com.commonsware.android.files; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; public class ReadWriteFileDemo extends Activity { private final static String NOTES="notes.txt"; private EditText editor; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); editor=(EditText)findViewById(R.id.editor); Button btn=(Button)findViewById(R.id.close); btn.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { finish(); } }); } public void onResume() { super.onResume(); try { InputStream in=openFileInput(NOTES); if (in!=null) { InputStreamReader tmp=new InputStreamReader(in); BufferedReader reader=new BufferedReader(tmp); String str; StringBuffer buf=new StringBuffer(); while ((str = reader.readLine())!= null) { buf.append(str + "\n"); }

Chapitre 18

Accs aux chiers

197

in.close(); editor.setText(buf.toString()); } } catch (java.io.FileNotFoundException e) { // Ok, nous ne lavons probablement pas encore cr } catch (Throwable t) { Toast .makeText(this, "Exception : "+ t.toString(), 2000) .show(); } } public void onPause() { super.onPause(); try { OutputStreamWriter out= new OutputStreamWriter(openFileOutput(NOTES, 0)); out.write(editor.getText().toString()); out.close(); } catch (Throwable t) { Toast .makeText(this, "Exception : "+ t.toString(), 2000) .show(); } } }

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

198

Lart du dveloppement Android

Le rsultat est un bloc-notes persistant : 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.

19
Utilisation des ressources
Les ressources sont des informations statiques, stockes en dehors du code Java. Dans les exemples de ce livre, vous avez dj souvent rencontr un type de ressource les chiers de description (layouts) , mais il en existe de nombreux autres dont vos applications peuvent tirer prot : les images et les chanes de caractres, par exemple.

Les diffrents types de ressources


Les ressources dun projet Android sont stockes dans des chiers situs sous le rpertoire res/ de larborescence. lexception des ressources brutes (res/raw/), tous les types de ressources sont analyss automatiquement, soit par le systme de paquetages dAndroid, soit par le systme du terminal ou de lmulateur. Si vous dcrivez, par exemple, linterface utilisateur dune activit via une ressource de type layout (dans res/layout), vous navez pas besoin danalyser vous-mme le contenu du chier XML Android sen chargera pour vous. Outre les layouts (que nous avons rencontrs pour la premire fois au Chapitre 5) et les ressources brutes (introduites au Chapitre 18), il existe plusieurs autres types de ressources :

Les animations (res/anim/) sont destines aux animations courtes qui font partie de linterface utilisateur : la simulation dune page qui se tourne quand on clique sur un bouton, par exemple.

200

Lart du dveloppement Android

Les images (res/drawable) permettent de placer des icnes statiques ou dautres images dans une interface utilisateur. Les chanes, les couleurs, les tableaux et les dimensions (res/values/) permettent dassocier des noms symboliques ces types de constantes et de les sparer du reste du code (pour linternationalisation et la localisation, notamment). Les chiers XML statiques (res/xml/) permettent de stocker vos propres donnes et structures.

Thorie des chanes


Placer les labels et les autres textes lextrieur du code source de lapplication est, 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 des informations au cours de lexcution. En outre, le formatage de texte simple, appel "texte styl", est galement disponible, ce qui permet de mlanger des mots en gras ou en italique avec du texte normal.

Chanes normales
En rgle gnrale, vous navez besoin pour les chanes normales que dun chier XML situ dans le rpertoire res/values (le plus souvent res/values/strings.xml). La racine de ce document est llment ressources, qui a autant de ls string quil y a de chanes encoder comme ressource. Llment string a un attribut name, contenant le nom unique de la chane. Le contenu de cet lment est le texte de la chane :
<resources> <string name="whisky">Portez ce vieux whisky...</string> <string name="zephir">Le vif zphir jubile...</string> </resources>

Le seul point pineux concerne la prsence de guillemets (") ou dapostrophes () dans le texte de la chane car vous devrez alors les protger en les prxant dun antislash (il fait beau aujourd\hui, par exemple). Dans le cas de lapostrophe, vous pouvez galement placer tout le texte entre guillemets ("il fait beau aujourdhui"). Vous pouvez ensuite faire rfrence cette chane depuis un chier de description (sous la forme @string/whisky, ou @string/zephir, par exemple) ou y accder depuis votre

Chapitre 19

Utilisation des ressources

201

code Java laide de getString(), en lui passant lidentiant de ressource de la chane, cest--dire son nom unique prx par R.string (comme getString(R.string.whisky)).

Formats de chanes
Comme les autres implmentations du langage Java, la machine virtuelle Dalvik dAndroid reconnat les formats de chanes. Ces formats sont des chanes contenant des marqueurs demplacements et seront remplacs lors de lexcution par des donnes variables (Mon nom est %1$s, par exemple). Les chanes normales stockes sous forme de ressources peuvent tre utilises comme des formats de chanes :
String strFormat=getString(R.string.mon_nom); String strResult=String.format(strFormat, "Tim"); ((TextView)findViewById(R.id.un_label)) .setText(strResult);

Texte styl
Pour enrichir du texte, vous pourriez utiliser des ressources brutes contenant du HTML, puis les placer dans un widget WebKit. Cependant, pour un formatage lger utilisant <b>, <i> et <u>, une ressource chane fera laffaire :
<resources> <string name="b">Ce texte est en <b>gras</b>.</string> <string name="i">Alors que celui-ci est en <i>italiques</i> !</string> </resources>

Vous pouvez ensuite y accder comme nimporte quelle autre chane normale, sauf que le rsultat de lappel getString() sera ici un objet implmentant linterface android.text.Spanned :
((TextView)findViewById(R.id.autre_label)) .setText(getString(R.string.i));

Formats styls
Les styles deviennent compliqus grer lorsquil sagit de les utiliser avec les formats de chanes. En effet, String.format() sapplique des objets String, pas des objets Spanned disposant dinstructions de formatage. Si vous avez vraiment besoin de formats de chanes styls, vous pouvez suivre ces tapes : 1. Dans la ressource chane, remplacez les chevrons par des entits HTML (Je suis &lt;b&gt;%1$s&lt;/b&gt;, par exemple). 2. Rcuprez la ressource comme dhabitude, bien quelle ne soit pas encore style (avec getString(R.string.format_style)).

202

Lart du dveloppement Android

3. Produisez le rsultat du formatage, en vous assurant de protger les valeurs de chanes que vous substituez, au cas o elles contiendraient des chevrons ou des esperluettes :
String.format(getString(R.string.format_style), TextUtils.htmlEncode(nom));

4. Convertissez le HTML encod en objet Spanned grce Html.fromHtml().


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

Pour voir tout ceci en action, examinons le chier de description du projet Resources/ Strings :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" > <Button android:id="@+id/format" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btn_name" /> <EditText android:id="@+id/name" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout> <TextView android:id="@+id/result" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>

Comme vous pouvez le constater, linterface utilisateur nest compose que dun bouton, dun champ et dun label. Le but est que lutilisateur entre son nom dans le champ puis clique sur le bouton pour que le label soit remplac par un message format contenant ce nom. Llment Button de ce chier faisant rfrence une ressource chane (@string/ btn_name), nous avons besoin dun chier de ressource chane (res/values/strings.xml) :
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">StringsDemo</string> <string name="btn_name">Nom :</string> <string name="funky_format">Je suis &lt;b&gt;%1$s&lt;/b&gt;</string> </resources>

Chapitre 19

Utilisation des ressources

203

La ressource app_name est automatiquement cre par le script activityCreator. La chane btn_name est le titre du bouton, tandis que le format de chane styl se trouve dans funky_format. Enn, nous avons besoin dun peu de Java pour relier tous les morceaux :
package com.commonsware.android.resources; import android.app.Activity; import android.os.Bundle; import android.text.TextUtils; import android.text.Html; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class StringsDemo extends Activity { EditText name; TextView result; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); name=(EditText)findViewById(R.id.name); result=(TextView)findViewById(R.id.result); Button btn=(Button)findViewById(R.id.format); btn.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { applyFormat(); } }); } private void applyFormat() { String format=getString(R.string.funky_format); String simpleResult=String.format(format, TextUtils.htmlEncode(name.getText().toString())); result.setText(Html.fromHtml(simpleResult)); } }

La manipulation de la ressource chane a lieu dans applyFormat(), qui est appele lorsque lon clique sur le bouton. On rcupre dabord notre format par un appel getString() chose que nous aurions pu faire dans onCreate() pour plus defcacit. Puis nous nous en servons pour formater la valeur du champ, ce qui nous renvoie une String puisque la ressource chane est encode en HTML. Vous remarquerez que nous

204

Lart du dveloppement Android

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. Enn, on convertit ce texte HTML en objet texte styl laide de Html.fromHtml() et nous modions notre label. La Figure 19.1 montre que le label de lactivit est vide lors de son lancement.
Figure 19.1 Lapplication StringsDemo aprs son lancement.

La Figure 19.2 montre ce que lon obtient aprs avoir saisi un nom et cliqu sur le bouton.
Figure 19.2 La mme application, aprs avoir entr un nom clbre.

Chapitre 19

Utilisation des ressources

205

Vous voulez gagner une image ?


Android reconnat les images aux formats PNG, JPEG et GIF, bien que GIF soit ofciellement dconseill ; il est gnralement prfrable dutiliser PNG. Les images peuvent tre utilises partout o lon attend un objet Drawable, comme pour limage et le fond dune ImageView. Lutilisation des images consiste simplement placer les chiers images dans res/drawable/ puis y faire rfrence comme des ressources. Dans les chiers de description XML, vous pouvez les dsigner par @drawable/nomfic, o nomfic est le nom de base du chier (le nom de la ressource correspondant au chier res/drawable/truc.png est donc @drawable/truc). Dans le code Java, il suft de prxer le nom de base du chier par R.drawable lorsque vous avez besoin de lidentiant de ressource (R.drawable.truc, par exemple). Si vous avez besoin dune URI vers une ressource image, vous pouvez utiliser deux formats diffrents pour indiquer son chemin :

android.resource://com.example.app/id, o com.example.app est le nom du paquetage Java utilis par lapplication dans AndroidManifest.xml et id est lidentiant de ressource numrique de la ressource (R.drawable.truc, par exemple). android.resource://com.example.app/raw/nom, o com.example.app est le nom du paquetage Java utilis par lapplication dans AndroidManifest.xml et nom est le nom de la ressource brute (tel truc pour res/drawable/truc.png).

Android est fourni avec quelques ressources images prdnies qui sont accessibles en Java via un prxe android.R.drawable an de les distinguer des ressources spciques aux applications (android.R.drawable.picture_frame, par exemple). Modions lexemple prcdent pour quil utilise une icne la place de la ressource chane du bouton. Ce projet, Resources/Image, ncessite dabord de modier lgrement le chier de description an dutiliser un ImageButton et de faire rfrence un Drawable nomm @drawable/icon :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" > <ImageButton android:id="@+id/format" android:layout_width="wrap_content"

206

Lart du dveloppement Android

android:layout_height="wrap_content" android:src="@drawable/icon" /> <EditText android:id="@+id/name" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout> <TextView android:id="@+id/result" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>

Puis nous devons placer un chier image dans res/drawable avec icon comme nom de base. Ici, nous utiliserons un chier PNG de 32 _ 32 pixels, issu de lensemble dicnes Nuvola1. Enn, nous modions le code Java pour remplacer le Button par un ImageButton :
package com.commonsware.android.resources; import android.app.Activity; import android.os.Bundle; import android.text.TextUtils; import android.text.Html; import android.view.View; import android.widget.Button; import android.widget.ImageButton; import android.widget.EditText; import android.widget.TextView; public class ImagesDemo extends Activity { EditText name; TextView result; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); name=(EditText)findViewById(R.id.name); result=(TextView)findViewById(R.id.result); ImageButton btn=(ImageButton)findViewById(R.id.format);

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

Chapitre 19

Utilisation des ressources

207

btn.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { applyFormat(); } }); } private void applyFormat() { String format=getString(R.string.funky_format); String simpleResult=String.format(format, TextUtils.htmlEncode(name.getText().toString())); result.setText(Html.fromHtml(simpleResult)); } }

Notre bouton contient dsormais licne dsire (voir Figure 19.3).


Figure 19.3 Lapplication ImagesDemo.

Les ressources XML


Au Chapitre 18, nous avons vu que nous pouvions intgrer au paquetage de lapplication des chiers XML sous forme de ressources brutes : il fallait ensuite y accder depuis le code Java pour les analyser et les utiliser. Il existe un autre moyen dempaqueter des documents XML statiques avec une application : les ressources XML. Il suft de placer un chier XML dans res/xml/ pour pouvoir y accder en appelant la mthode getXml() sur un objet Resources et en lui passant en paramtre un identiant

208

Lart du dveloppement Android

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

1. http://www.xmlpull.org/v1/doc/api/org/xmlpull/v1/package-summary.html.

Chapitre 19

Utilisation des ressources

209

XmlPullParser xpp=getResources().getXml(R.xml.words); while (xpp.getEventType()!=XmlPullParser.END_DOCUMENT) { if (xpp.getEventType()==XmlPullParser.START_TAG) { if (xpp.getName().equals("word")) { items.add(xpp.getAttributeValue(0)); } } xpp.next(); } } catch (Throwable t) { Toast .makeText(this, "Echec de la requete : "+ t.toString(), 4000) .show(); } setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items)); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items.get(position).toString()); } }

Dsormais, dans notre bloc try...catch, nous obtenons un XmlPullParser et nous bouclons jusqu la n du document. Si lvnement courant est START_TAG et que le nom de llment soit word (xpp.getName(). equals("word")), nous rcuprons le seul et unique attribut et nous lajoutons la liste de mots de notre widget. Comme nous avons un contrle total sur le chier XML (puisque cest nous qui lavons cr), nous pouvons supposer quil ny a exactement quun attribut si nous nen tions pas srs, nous pourrions compter le nombre dattributs avec getAttributeCount() et obtenir leur nom avec getAttributeName() au lieu de supposer aveuglment que lattribut dindice 0 est celui auquel on pense. Comme le montre la Figure 19.4, mis part le nom dans la barre de titre, le rsultat est identique lexemple du Chapitre 18.

210

Lart du dveloppement Android

Figure 19.4 Lapplication XMLResourceDemo.

Valeurs diverses
Dans le rpertoire res/values/, vous pouvez placer un ou plusieurs chiers XML dcrivant des ressources simples : des dimensions, des couleurs 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, congurer ces valeurs comme des objets Java constants et utiliser leurs noms symboliques, mais cela ne peut fonctionner que dans le code source Java, pas dans les chiers de description XML. En les plaant dans des chiers de ressources XML, vous pouvez rfrencer ces valeurs la fois dans du code Java et dans les chiers de description ; en outre, elles sont ainsi centralises au mme endroit, ce qui facilite leur maintenance. Les chiers ressources XML ont pour racine llment resources ; tous les autres lments sont des ls de cette racine.

Dimensions
Android utilise les dimensions en de nombreux endroits pour dcrire des distances, comme la valeur de remplissage dun widget. Bien que nous utilisions souvent le pixel comme unit de mesure (10px pour 10 pixels, par exemple), vous pouvez en choisir dautres :

in et mm indiquent, respectivement, des pouces et des millimtres, daprs la taille physique de lcran. pt reprsente des points, cest--dire 1/72e de pouce en termes typographiques, l aussi daprs la taille physique de lcran.

Chapitre 19

Utilisation des ressources

211

dp (device-independant pixel) et sp (scale-independant pixel) indiquent des pixels indpendants du terminal un pixel est gal un dp pour un cran de 160 dpi, le facteur dchelle reposant sur la densit de lcran (les pixels indpendants de lchelle tiennent galement compte de la taille de la police choisie par lutilisateur).

Pour encoder une dimension comme une ressource, ajoutez un lment dimen ayant un attribut name qui nomme de faon unique cette ressource. Le contenu de cet lment est un texte reprsentant la valeur de la dimension :
<resources> <dimen name="fin">10px</dimen> <dimen name="epais">1in</dimen> </resources>

Dans un chier de description, les dimensions peuvent tre rfrences par @dimen/nom, o nom est le nom unique de la ressource (fin ou epais, dans lexemple prcdent). Dans le code Java, il suft dutiliser le nom unique prx par R.dimen (Resources.getDimen (R.dimen.fin), par exemple).

Couleurs
Les couleurs Android sexpriment en valeurs RGB hexadcimales et peuvent prciser un canal alpha. Vous avez le choix entre des valeurs hexadcimales dun seul caractre ou de deux caractres, ce qui donne donc quatre formats possibles :

#RGB ; #ARGB ; #RRGGBB ; #AARRGGBB.

Ces valeurs sont les mmes que dans les feuilles de style CSS. Vous pouvez, bien sr, les placer comme des chanes de caractres dans le code Java ou les chiers de description. Cependant, il suft dajouter des lments color au chier de ressource an de les grer comme des ressources. Ces lments doivent possder un attribut name pour donner un nom unique la couleur et contenir la valeur RGB :
<resources> <color name="jaune_orange">#FFD555</color> <color name="vert_foret">#005500</color> <color name="ambre_fonce">#8A3324</color> </resources>

Dans un chier de description, ces couleurs peuvent tre dsignes par @color/nom, o nom est le nom unique de la couleur (ambre_fonce, par exemple). En Java, prxez ce nom unique par R.color (Resources.getColor(R.color.vert_foret), par exemple).

212

Lart du dveloppement Android

Tableaux
Les ressources tableaux sont conues pour contenir des listes de chanes simples, comme une liste de civilits (M., Mme, Mlle, Dr, etc.). Dans le chier ressource, vous avez besoin dun lment string-array ayant un attribut name pour donner un nom unique au tableau. Cet lment doit avoir autant de ls item quil y a dlments dans ce tableau. Le contenu de chacun de ces ls est la valeur de lentre correspondante :
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="villes"> <item>Philadelphia</item> <item>Pittsburgh</item> <item>Allentown/Bethlehem</item> <item>Erie</item> <item>Reading</item> <item>Scranton</item> <item>Lancaster</item> <item>Altoona</item> <item>Harrisburg</item> </string-array> <string-array name="codes_aeroports"> <item>PHL</item> <item>PIT</item> <item>ABE</item> <item>ERI</item> <item>RDG</item> <item>AVP</item> <item>LNS</item> <item>AOO</item> <item>MDT</item> </string-array> </resources>

Dans le code Java, vous pouvez ensuite utiliser Resources.getStringArray(R.array.nom) pour obtenir un String[] contenant tous les lments du tableau nom (Resources.getStringArray(R.array.codes_aeroports), par exemple).

Grer la diffrence
Un mme ensemble de ressources peut ne pas convenir toutes les situations dans lesquelles votre application est utilise. Un exemple vident est celui des ressources chanes et la gestion de linternationalisation (I18N) et de la localisation (L10N). Nutiliser que des chanes dune mme langue fonctionne parfaitement au moins pour le dveloppeur mais cela produit une application qui rduit le nombre de ses utilisateurs potentiels.

Chapitre 19

Utilisation des ressources

213

Les ressources peuvent galement diffrer sur dautres points :

Lorientation de lcran. Lcran est-il en mode portrait (vertical) ou en mode paysage (horizontal) ? Il peut galement tre carr, ce qui signie quil na donc pas dorientation particulire. La taille de lcran. Combien a-t-il de pixels ? Vous devez en tenir compte pour 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 ? Clavier. De quel clavier dispose lutilisateur (alphanumrique, numrique, les deux) ? Est-il toujours disponible ou est-ce une option ? Autres dispositifs de saisie. Le terminal a-t-il dautres moyens de saisie, comme un pad directionnel ou une molette ?

Pour grer toutes ces diffrences, Android utilise plusieurs rpertoires de ressources dont les noms contiennent le critre concern. Supposons, par exemple, que vous vouliez fournir des textes anglais et franais. Pour une application non traduite, vous placeriez normalement ces chanes dans le chier res/ values/strings.xml. Pour pouvoir reconnatre la fois langlais et le franais, vous devrez crer deux rpertoires, res/values-en et res/values-fr, o les deux lettres qui suivent le tiret reprsentent la langue souhaite selon le codage ISO-639-1 1. Les chanes anglaises seraient donc places dans le chier res/values-en/strings.xml et les franaises, dans res/values-fr/strings.xml. Android choisira alors le bon chier en fonction de la conguration du terminal de lutilisateur. Cela semble simple, nest-ce pas ? Les choses se compliquent lorsque vous avez besoin de plusieurs critres distincts pour vos ressources. Supposons, par exemple, que vous vouliez dvelopper une application la fois pour le G1 de T-Mobile et deux autres terminaux ctifs. Lun deux (le Fictif Un) dispose dun cran VGA gnralement en mode paysage (640 _ 480), dun clavier alphanumrique toujours ouvert, dun pad directionnel, mais pas dcran tactile. Lautre (le Fictif Deux) a le mme cran que le G1 (320 _ 480), un clavier numrique mais pas alphabtique, un pad directionnel mais pas dcran tactile. Pour tirer parti de ces diffrences dcrans et doptions de saisie, vous pourriez crer des chiers de description diffrents :

pour chaque combinaison de rsolution et dorientation ; pour les terminaux qui ont un cran tactile et ceux qui nen ont pas ; pour les terminaux qui ont des claviers alphanumriques et ceux qui nen ont pas.

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


1. http://fr.wikipedia.org/wiki/Liste_des_codes_ISO_639-1.

214

Lart du dveloppement Android

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

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

1. http://code.google.com/android/devel/resources-i18n.html#AlternateResources.

Chapitre 19

Utilisation des ressources

215

res/layout-land-stylus-qwerty-640x480 ; res/layout-land-stylus-qwerty-480x320 ; res/layout-land-stylus-12key-640x480 ; res/layout-land-stylus-12key-480x320 ; res/layout-land-stylus-nokeys-640x480 ; res/layout-land-stylus-nokeys-480x320 ; res/layout-land-finger-qwerty-640x480 ; res/layout-land-finger-qwerty-480x320 ; res/layout-land-finger-12key-640x480 ; res/layout-land-finger-12key-480x320 ; res/layout-land-finger-nokeys-640x480 ; res/layout-land-finger-nokeys-480x320. Pas de panique ! Nous allons abrger cette liste dans un petit moment !

En ralit, beaucoup de ces chiers de description seront identiques. Par exemple, nous voulons simplement que les descriptions des crans tactiles soient diffrentes de celles des crans non tactiles. Cependant, comme nous ne pouvons pas combiner les deux types dcrans, nous devrons thoriquement avoir des rpertoires distincts avec des contenus identiques pour les crans manipulables avec le doigt et ceux manipulables avec un stylet. Notez galement que rien nempche davoir un rpertoire avec un nom de base simple (res/layout). En ralit, cest mme srement prfrable au cas o une nouvelle version dAndroid introduirait dautres options de conguration que vous navez pas prises en compte disposer dune description par dfaut permettra alors votre application de 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 de 320 240, par exemple, les rpertoires 640x480 seront limins des candidats possibles car ils font spciquement appel une autre taille. 2. Deuximement, Android compte le nombre de correspondances pour chaque rpertoire et ne conserve que les rpertoires qui en ont le plus. 3. Enn, Android suit lordre de priorit des options en dautres termes, il parcourt le nom du rpertoire de gauche droite. Nous pouvons donc nous ramener aux congurations suivantes :

res/layout-port-notouch-qwerty-640x480 ; res/layout-port-notouch-qwerty ; res/layout-port-notouch-640x480 ;

216

Lart du dveloppement Android

res/layout-port-notouch ; res/layout-port-qwerty-640x480 ; res/layout-port-qwerty ; res/layout-port-640x480 ; res/layout-port ; res/layout-land-notouch-qwerty-640x480 ; res/layout-land-notouch-qwerty ; res/layout-land-notouch-640x480 ; res/layout-land-notouch ; res/layout-land-qwerty-640x480 ; res/layout-land-qwerty ; res/layout-land-640x480 ; res/layout-land.

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

res/layout-port-notouch-640x480 ; res/layout-port-notouch ; res/layout-land-notouch-640x480 ; res/layout-land-notouch ; res/layout-land ; res/layout.

Ici, 640x480 permet de diffrencier Fictif Un des deux autres, tandis que notouch distingue Fictif Deux du G1 de HTC.

20
Accs et gestion des bases de donnes locales
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 vidente pour quiconque a une exprience avec dautres SGBDR. Cependant, son API native nest pas JDBC, qui, dailleurs, serait trop lourd pour les terminaux limits en mmoire comme les tlphones. Par consquent, les programmeurs Android doivent apprendre une nouvelle API mais, comme nous allons le voir, ce nest pas trs difcile. Ce chapitre prsente les bases de lutilisation de SQLite dans le contexte du 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
1. http://www.sqlite.org.

218

Lart du dveloppement Android

environnements quAndroid, nous vous conseillons louvrage de Mike Owens, The Denitive Guide to SQLite1 (Apress, 2006). Les activits accdant gnralement une base de donnes via un fournisseur de contenu (content provider) ou un service, ce chapitre ne contient pas dexemple complet : vous trouverez un exemple de fournisseur de contenu faisant appel une base de donnes au Chapitre 28.

Prsentation rapide de SQLite


SQLite, comme son nom lindique, utilise un dialecte de SQL pour effectuer des requtes (SELECT), des manipulations de donnes (INSERT, etc.) et des dnitions de donnes (CREATE TABLE, etc.). certains moments, il scarte du standard SQL-92, comme la plupart des autres SGBDR, dailleurs. La bonne nouvelle est que SQLite est si efcace en terme de mmoire que le moteur dexcution dAndroid peut linclure dans son 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. Tant que vous pouvez prciser les types des colonnes dans une instruction CREATE TABLE et tant que SQLite les utilise comme indication, tout va pour le mieux. Vous pouvez mettre les donnes que vous voulez dans les colonnes que vous souhaitez. Vous voulez placer une chane dans une colonne INTEGER ? Pas de problme ! Et vice versa ? Cela marche aussi ! Cest ce que SQLite appelle "typage manifeste" ; il est dcrit de la faon suivante dans sa documentation2 : Avec le typage manifeste, le type dune donne est une proprit de la valeur elle-mme, pas de la colonne dans laquelle la valeur est stocke. SQLite permet donc de stocker une valeur de nimporte quel type dans nimporte quelle colonne, quel que soit le type dclar de cette colonne. Certaines fonctionnalits standard de SQL ne sont pas reconnues par SQLite, notamment les contraintes FOREIGN KEY, les transactions imbriques, RIGHT OUTER JOIN, FULL OUTER JOIN et certaines variantes de ALTER TABLE. Ces remarques mises part, vous disposez dun SGBDR complet, avec des triggers, des transactions, etc. Les instructions SQL de base, comme SELECT, fonctionnent exactement comme vous tes en droit de lattendre. Si vous tes habitu travailler avec un gros SGBDR comme Oracle, vous pourriez considrer que SQLite est un "jouet", mais noubliez pas que ces deux systmes ont t conus pour rsoudre des problmes diffrents et que vous ntes pas prs de voir une installation complte dOracle sur un tlphone.
1. http://www.amazon.com/Denitive-Guide-SQLite/dp/1590596730. 2. http://www.sqlite.org/different.html.

Chapitre 20

Accs et gestion des bases de donnes locales

219

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 spcications et les besoins de votre application. Cette sous-classe aura besoin de trois mthodes :

Un constructeur qui appelle celui de sa classe parente et qui prend en paramtre le Context (une Activity), le nom de la base de donnes, une ventuelle fabrique de curseur (le plus souvent, ce paramtre vaudra null) et un entier reprsentant la version du schma de la base. onCreate(), laquelle vous passerez lobjet SQLiteDatabase que vous devrez remplir avec les tables et les donnes initiales que vous souhaitez. onUpgrade(), laquelle vous passerez un objet SQLiteDatabase ainsi que lancien et le nouveau numro de version. Pour convertir une base dun ancien schma un nouveau, lapproche la plus simple consiste supprimer les anciennes tables et en crer de nouvelles. Le Chapitre 28 donnera tous les dtails ncessaires.

Le reste de ce chapitre est consacr la cration et la suppression des tables, linsertion des donnes, etc. Il prsentera galement un exemple de sous-classe de SQLiteOpenHelper. Pour utiliser votre sous-classe, crez une instance et demandez-lui dappeler getReadableDatabase() ou getWriteableDatabase() selon que vous vouliez ou non modier son contenu :
db=(new DatabaseHelper(getContext())).getWritableDatabase(); return (db == null)? false: true;

Cet appel renverra une instance de SQLiteDatabase qui vous servira ensuite interroger ou modier la base de donnes. Lorsque vous avez ni de travailler sur cette base (lorsque lactivit est ferme, par exemple), il suft dappeler la mthode close() de cette instance pour librer votre connexion.

Mettre la table
Pour crer des tables et des index, vous devez appeler la mthode execSQL() de lobjet SQLiteDatabase en lui passant linstruction du LDD (langage de dnition des donnes) que vous voulez excuter. En cas derreur, cette mthode renvoie null.

220

Lart du dveloppement Android

Vous pouvez, par exemple, utiliser le code suivant :


db.execSQL("CREATE TABLE constantes (_id INTEGER PRIMARY KEY AUTOINCREMENT, titre TEXT, valeur REAL);");

Cet appel cre une table constantes avec une colonne de cl primaire _id qui est un entier incrment automatiquement (SQLite lui affectera une valeur pour vous lorsque vous insrerez les lignes). Cette table contient galement deux colonnes de donnes : titre (un texte) et valeur (un nombre rel). SQLite crera automatiquement un index sur la colonne de cl primaire si vous le souhaitez, vous pouvez en ajouter dautres laide dinstructions CREATE INDEX. Le plus souvent, vous crerez les tables et les index ds la cration de la base de donnes ou, ventuellement, lorsquelle devra tre mise jour suite une nouvelle version de votre application. Si les schmas des tables ne changent pas, les tables et les index nont pas besoin dtre supprims mais, si vous devez le faire, il suft dutiliser execSQL() an dexcuter les instructions DROP INDEX et DROP TABLE.

Ajouter des donnes


Lorsque lon cre une base de donnes et une ou plusieurs tables, cest gnralement pour y placer des donnes. Pour ce faire, il existe principalement deux approches. Vous pouvez encore utiliser execSQL(), comme vous lavez fait pour crer les tables. Cette mthode permet en effet dexcuter nimporte quelle instruction SQL qui ne renvoie pas de rsultat, ce qui est le cas dINSERT, UPDATE, DELETE, etc. Vous pourriez donc utiliser ce code :
db.execSQL("INSERT INTO widgets (name, inventory)"+ "VALUES (Sprocket, 5)");

Une autre solution consiste utiliser insert(), update() et delete() sur lobjet SQLiteDatabase. Ces mthodes 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 galement de getAsInteger(), getAsString(), etc. La mthode insert() prend en paramtre le nom de la table, celui dune colonne pour lastuce de la colonne nulle et un objet ContentValues contenant les valeurs que vous voulez placer dans cette ligne. Lastuce de la colonne nulle est utilise dans le cas o linstance de ContentValues est vide la colonne indique pour cette astuce recevra alors explicitement la valeur NULL dans linstruction INSERT produite par insert().
ContentValues cv=new ContentValues(); cv.put(Constantes.TITRE, "Gravity, Death Star I"); cv.put(Constantes.VALEUR, SensorManager.GRAVITY_DEATH_STAR_I); db.insert("constantes", getNullColumnHack(), cv);

Chapitre 20

Accs et gestion des bases de donnes locales

221

La mthode update() prend en paramtre le nom de la table, un objet ContentValues contenant les colonnes et leurs nouvelles valeurs et, ventuellement, une clause WHERE et une liste de paramtres qui remplaceront les marqueurs prsents dans celle-ci. update() nautorisant que des valeurs xes pour mettre jour les colonnes, vous devrez utiliser execSQL() si vous souhaitez affecter des rsultats calculs. La clause WHERE et la liste de paramtres fonctionnent comme les paramtres positionnels qui existent galement dans dautres API de SQL :
// remplacements est une instance de ContentValues String[] params=new String[] {"snicklefritz"}; db.update("widgets", remplacements, "name=?", params);

La mthode delete() fonctionne comme update() et prend en paramtre le nom de la table et, ventuellement, une clause WHERE et une liste des paramtres positionnels pour cette clause.

Le retour de vos requtes


Comme pour INSERT, UPDATE et DELETE, vous pouvez utiliser plusieurs approches pour rcuprer les donnes dune base SQLite avec SELECT :

rawQuery() permet dexcuter directement une instruction SELECT. query() permet de construire une requte partir de ses diffrentes composantes.

Un sujet de confusion classique est la classe SQLiteQueryBuilder et le problme des curseurs et de leurs fabriques.

Requtes brutes
La solution la plus simple, au moins du point de vue de lAPI, consiste utiliser rawQuery() en lui passant simplement la requte SELECT. Cette dernire peut contenir des paramtres positionnels qui seront remplacs par les lments du tableau pass en second paramtre. Voici un exemple :
Cursor c=db.rawQuery("SELECT name FROM sqlite_master WHERE type=table AND name=constantes", null);

Ici, nous interrogeons une table systme de SQLite (sqlite_master) pour savoir si la table constantes existe dj. La valeur renvoye est un Cursor qui dispose de mthodes permettant de parcourir le rsultat (voir la section "Utilisation des curseurs"). Si vos requtes sont bien intgres votre application, cest une approche trs simple. En revanche, elle se complique lorsquune requte comprend des parties dynamiques que les paramtres positionnels ne peuvent plus grer. Si lensemble de colonnes que vous voulez

222

Lart du dveloppement Android

rcuprer nest pas connu au moment de la compilation, par exemple, concatner les noms des colonnes pour former une liste dlimite par des virgules peut tre ennuyeux cest l que query() entre en jeu.

Requtes normales
La mthode query() prend en paramtre les parties dune instruction SELECT an de construire la requte. Ces diffrentes composantes apparaissent dans lordre suivant dans la liste des paramtres : 1. Le nom de la table interroge. 2. La liste des colonnes rcuprer. 3. La clause WHERE, qui peut contenir des paramtres positionnels. 4. La liste des valeurs substituer ces paramtres positionnels. 5. Une ventuelle clause GROUP BY. 6. Une ventuelle clause ORDER BY. 7. Une ventuelle clause HAVING. part le nom de la table, ces paramtres peuvent valoir null lorsquils ne sont pas ncessaires :
String[] colonnes={"ID", "inventory"}; String[] params={"snicklefritz"}; Cursor result=db.query("widgets", colonnes, "name=?", params, null, null, null);

Utilisation des "builders"


Une autre possibilit consiste utiliser SQLiteQueryBuilder, qui offre bien plus de 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 SQLiteQueryBuilder :
@Override public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort) {

Chapitre 20

Accs et gestion des bases de donnes locales

223

SQLiteQueryBuilder qb=new SQLiteQueryBuilder(); qb.setTables(getTableName()); if (isCollectionUri(url)) { qb.setProjectionMap(getDefaultProjection()); } else { qb.appendWhere(getIdColumnName()+"="+url.getPathSegments().get(1)); } String orderBy; if (TextUtils.isEmpty(sort)) { orderBy=getDefaultSortOrder(); } else { orderBy=sort; } Cursor c=qb.query(db, projection, selection, selectionArgs, null, null, orderBy); c.setNotificationUri(getContext().getContentResolver(), url); return c; }

Les fournisseurs de contenu (content provider) seront expliqus en dtail dans la cinquime partie de ce livre. Ici, nous pouvons nous contenter de remarquer que : 1. Nous construisons un objet SQLiteQueryBuilder. 2. Nous lui indiquons la table concerne par la requte avec setTables(getTableName()). 3. Soit nous lui indiquons lensemble de colonnes renvoyer par dfaut (avec setProjectionMap()), soit nous lui donnons une partie de clause WHERE an didentier une ligne prcise de la table partir dun identiant extrait de lURI fournie lappel de query() (avec appendWhere()). 4. Enn, nous lui demandons dexcuter la requte en mlangeant les valeurs de dpart avec celles fournies query() (qb.query(db, projection, selection, 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.

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

224

Lart du dveloppement Android

connatre les noms des colonnes avec getColumnNames(), les convertir en numros de colonnes grce getColumnIndex() et obtenir la valeur dune colonne donne de la ligne courante via des mthodes comme getString(), getInt(), etc. ; excuter nouveau la requte qui a cr le curseur, avec requery() ; librer les ressources occupes par le curseur avec close().

Voici, par exemple, comment parcourir les entres de la table widgets rencontre dans les extraits prcdents :
Cursor result= db.rawQuery("SELECT ID, name, inventory FROM widgets"); result.moveToFirst(); while (!result.isAfterLast()) { int id=result.getInt(0); String name=result.getString(1); int inventory=result.getInt(2); // Faire quelque chose de ces valeurs... result.moveToNext(); } result.close();

Crer ses propres curseurs


Dans certains cas, vous pouvez vouloir utiliser votre propre sous-classe de Cursor plutt que limplmentation de base fournie par Android. Dans ces situations, vous pouvez vous servir des mthodes queryWithFactory() et rawQueryWithFactory(), qui prennent toutes les deux en paramtre une instance de SQLiteDatabase.CursorFactory. Cette fabrique, comme lon pourrait sy attendre, est responsable de la cration de nouveaux curseurs via son implmentation de newCursor(). Limplmentation et lutilisation de cette approche sont laisses en exercice au lecteur. En fait, vous ne devriez pas avoir besoin de crer vos propres classes de curseur au cours du dveloppement dune application Android classique.

Des donnes, des donnes, encore des donnes


Si vous avez lhabitude de dvelopper avec dautres SGBDR, vous avez srement aussi utilis des outils permettant dinspecter et de manipuler le contenu de la base de donnes et qui vont au-del de lAPI. Avec lmulateur dAndroid, vous avez galement deux possibilits. Premirement, lmulateur est cens fournir le programme sqlite3, accessible via la commande adb shell. Lorsque vous avez lanc cette commande, tapez simplement sqlite3 suivi du chemin vers le chier de votre base de donnes, qui est gnralement de la forme :
/data/data/votre.paquetage.app/databases/nom_base

Chapitre 20

Accs et gestion des bases de donnes locales

225

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 modications sur le terminal, vous devrez retransfrer cette base sur celui-ci. Pour rcuprer la base du terminal, utilisez la commande adb pull (ou son quivalent dans votre environnement de dveloppement) en lui fournissant le chemin de la base sur le terminal et celui de la destination sur votre machine. Pour stocker une base de donnes modie sur le terminal, utilisez la commande adb push en lui indiquant le chemin de cette base sur votre machine et le chemin de destination sur le terminal. Lextension SQLite Manager1 pour Firefox est lun des clients SQLite les plus accessibles (voir Figure 20.1), car elle est disponible sur toutes les plates-formes.

Figure 20.1 Lextension SQLite Manager de Firefox.

Vous trouverez galement dautres clients2 sur le site web de SQLite3.


1. https://addons.mozilla.org/en-US/refox/addon/5817. 2. http://www.sqlite.org/cvstrac/wiki?p=SqliteTools. 3. http://www.sqlite.org.

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

228

Lart du dveloppement Android

Limites extrieures
Tout le code Java existant ne fonctionne videmment pas avec Android. Un certain nombre de facteurs doivent tre pris en compte :

API pour la plate-forme. Est-ce que le code suppose que vous utilisez une JVM plus rcente que celle sur laquelle repose Android ou suppose-t-il lexistence dune API Java fournie avec J2SE mais qui nexiste pas dans Android, comme Swing ? Taille. Le code Java conu pour tre utilis sur les machines de bureau ou les serveurs ne se soucie pas beaucoup de lespace disque ni de la taille mmoire. Android, videmment, manque des deux. Lutilisation de code tiers, notamment lorsquil est empaquet sous forme de JAR, peut faire goner la taille de votre application.. Performances. Est-ce que le code Java suppose un CPU beaucoup plus puissant que ceux que vous pouvez trouver sur la plupart des terminaux Android ? Ce nest pas parce quun ordinateur de bureau peut lexcuter sans problme quun tlphone mobile moyen pourra faire de mme. Interface. Est-ce que le code Java suppose une interface en mode console, ou sagit-il dune API que vous pouvez envelopper dans votre propre interface ?

Une astuce pour rgler quelques-uns de ces problmes consiste utiliser du code Java open-source et modier ce code pour ladapter Android. Si, par exemple, vous nutilisez que 10 % dune bibliothque tierce, il est peut-tre plus intressant de recompiler ce sous-ensemble ou, au moins, dter les classes inutilises du JAR. La premire approche est plus sre dans la mesure o le compilateur vous garantit que vous ne supprimerez pas une partie essentielle du code, mais elle peut tre assez dlicate.

Ant et JAR
Vous avez deux possibilits pour intgrer du code tiers dans votre projet : utiliser du code source ou des JAR dj compils. Si vous choisissez la premire mthode, il suft de copier le code source dans larborescence de votre projet (sous le rpertoire src/) an quil soit plac ct de votre propre code, puis de laisser le compilateur faire son travail. Si vous choisissez dutiliser un JAR dont vous ne possdez peut-tre pas les sources, vous devrez expliquer votre chane de dveloppement comment lutiliser. Avec un IDE, il suft de lui donner la rfrence du JAR. Si, en revanche, vous utilisez le script Ant build.xml, vous devez placer le chier JAR dans le rpertoire libs/ cr par activityCreator, o le processus de construction dAnt ira le chercher. Dans une dition prcdente de ce livre, par exemple, nous prsentions un projet MailBuzz qui, comme son nom lindique, traitait du courrier lectronique. Ce projet utilisait les API

Chapitre 21

Tirer le meilleur parti des bibliothques Java

229

JavaMail et avait besoin de deux JAR JavaMail : mail-1.4.jar et activation-1.1.jar. Avec ces deux chiers dans le rpertoire libs/, le classpath demandait javac deffectuer une dition de liens avec ces JAR an que toutes les rfrences JavaMail dans le code de MailBuzz puissent tre correctement rsolues. Puis le contenu de ces JAR tait numr avec les classes compiles de MailBuzz lors de la conversion en instructions Dalvik laide de loutil dex. Sans cette tape, le code se serait peut-tre compil, mais il naurait pas trouv les classes JavaMail lexcution, ce qui aurait provoqu une exception. Cependant, la machine virtuelle Dalvik et le compilateur fournis avec Android 0.9 et les SDK plus rcents ne supportent plus certaines fonctionnalits du langage Java utilises par JavaMail et, bien que le code source de JavaMail soit disponible, sa licence open-source (Common Development and Distribution licence CDDL) pose certains problmes.

Suivre le script
la diffrence des autres systmes pour terminaux mobiles, Android nimpose aucune restriction sur ce qui peut sexcuter tant que cest du Java qui utilise la machine virtuelle Dalvik. Vous pouvez donc incorporer votre propre langage de script dans votre application, ce qui est expressment interdit sur dautres terminaux. 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 chier JAR de linterprteur dans votre rpertoire libs/. Malheureusement, le JAR 2.0b4 disponible au tlchargement sur le site de BeanShell ne fonctionne pas tel quel avec Android 0.9 et les SDK plus rcents, 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 congure les variables globales pour le script laide dInterpreter#set(). 3. On appelle Interpreter#eval() pour lancer le script et, ventuellement, obtenir le rsultat de la dernire instruction.
1. http://beanshell.org. 2. http://beanshell.org/developer.html.

230

Lart du dveloppement Android

Voici par exemple le chier de description XML du plus petit IDE BeanShell du monde :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:id="@+id/eval" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Go!" /> <EditText android:id="@+id/script" android:layout_width="fill_parent" android:layout_height="fill_parent" android:singleLine="false" android:gravity="top" /> </LinearLayout>

Voici limplmentation de lactivit :


package com.commonsware.android.andshell; import android.app.Activity; import android.app.AlertDialog; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import bsh.Interpreter; public class MainActivity extends Activity { private Interpreter i=new Interpreter(); @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); Button btn=(Button)findViewById(R.id.eval); final EditText script=(EditText)findViewById(R.id.script); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { String src=script.getText().toString(); try { i.set("context", MainActivity.this);

Chapitre 21

Tirer le meilleur parti des bibliothques Java

231

i.eval(src); } catch (bsh.EvalError e) { AlertDialog.Builder builder= new AlertDialog.Builder(MainActivity.this); builder .setTitle("Exception!") .setMessage(e.toString()) .setPositiveButton("OK", null) .show(); } } }); } }

Compilez ce projet (en incorporant le JAR de BeanShell comme on la mentionn plus haut), puis installez-le sur lmulateur. Lorsque vous le lancerez, vous obtiendrez un IDE trs simple, avec une grande zone de texte vous permettant de saisir votre script et un gros bouton Go! pour lexcuter (voir Figure 21.1).
import android.widget.Toast; Toast.makeText(context, "Hello, world!", 5000).show();

Figure 21.1 LIDE AndShell.

Notez lutilisation de context pour dsigner lactivit lors de la cration du toast. Cette variable a t congure globalement par lactivit pour se dsigner elle-mme.

232

Lart du dveloppement Android

Vous pourriez lappeler autrement : ce qui importe est que lappel set() et le code du script utilisent le mme nom. Lorsque vous cliquez sur le bouton Go!, vous obtenez le rsultat de la Figure 21.2.
Figure 21.2 LIDE AndShell excutant un script BeanShell.

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

Chapitre 21

Tirer le meilleur parti des bibliothques Java

233

Enn, mais ce nest pas le moins important, les JAR des interprteurs ont tendance tre... gros. Celui du BeanShell utilis ici fait 200 Ko, par exemple. Ce nest pas ridicule si lon considre ce quil est capable de faire, mais cela implique que les applications qui utilisent BeanShell seront bien plus longues tlcharger, quelles prendront plus de place sur le terminal, etc.

Tout fonctionne... enn, presque


Tous les codes Java ne fonctionneront pas avec Android et Dalvik. Vous devez plus prcisment tenir compte des paramtres suivants :

Si le code Java suppose quil sexcute avec Java SE, Java ME ou Java EE, 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 compilation de votre application avec un JAR compil ; ces problmes surviendront plutt lors de lexcution. Cest pour cette raison quil est prfrable dutiliser du code open-source avec Android chaque fois que cela est possible : vous pourrez ainsi construire vous-mme le code tiers en mme temps que le vtre et dtecter plus tt les difcults rsoudre.

Relecture des scripts


Ce chapitre tant consacr lcriture des scripts avec Android, vous apprcierez srement de savoir quil existe dautres possibilits que lintgration directe de Beanshell dans votre projet. Certains essais ont t raliss avec dautres langages reposant sur la JVM, notamment JRuby et Jython. Pour le moment, leur support dAndroid est incomplet, mais cette 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 sont pas vraiment redistribuables. De plus, ASE na pas t rellement

234

Lart du dveloppement Android

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.

22
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 Wi, 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 Wi peuvent accder Internet partir de leur tlphone Android. Il nest donc pas tonnant quAndroid offre aux dveloppeurs un large ventail de moyens leur permettant dexploiter cet accs. Ce dernier peut tre de haut niveau, comme le navigateur WebKit intgr que nous avons tudi au Chapitre 13. 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 spciques comme HTTP, XMPP, SMTP, etc. Ce livre sintresse plutt aux accs de haut niveau comme le composant WebKit et les API Internet car, dans la mesure du possible, les dveloppeurs devraient sefforcer de rutiliser des composants existants au lieu de crer leurs propres protocoles.

236

Lart du dveloppement Android

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 conguration (les valeurs des formulaires si lon effectue une commande POST via HttpPost, par exemple) puis lon passe la mthode au client pour quil effectue la requte HTTP en appelant execute(). Ce qui se passe ensuite peut tre trs simple ou trs compliqu. On peut obtenir un objet HttpResponse enveloppant un code de rponse (200 pour OK, par exemple), des en-ttes HTTP, etc. Mais on peut galement utiliser une variante dexecute() qui prend en paramtre un objet ResponseHandler<String> : cet appel renverra simplement une reprsentation String de la rponse. En pratique, cette approche est dconseille car il est prfrable de vrier les codes de rponses HTTP pour dtecter les erreurs. Cependant, pour les applications triviales comme les exemples de ce livre, la technique 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 afcher. Nous laissons en exercice au lecteur la rcriture de ce programme pour quil utilise un ListView. En outre, cet exemple tant relativement long, nous ne prsenterons
1. http://hc.apache.org/.

Chapitre 22

Communiquer via Internet

237

ici 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 Chapitre 33. Lorsquun emplacement a t trouv soit au lancement, soit parce que nous avons boug , nous rcuprons les donnes de Google Weather via la mthode updateForecast() :
private void updateForecast(Location loc) { String url = String.format(format, "" + (int) (loc.getLatitude() * 1000000), "" + (int) (loc.getLongitude() * 1000000)); HttpGet getMethod = new HttpGet(url); try { ResponseHandler<String> responseHandler = new BasicResponseHandler(); String responseBody = client.execute(getMethod, responseHandler); buildForecasts(responseBody); String page = generatePage(); browser.loadDataWithBaseURL(null, page, "text/html", "UTF-8", null); } catch (Throwable t) { Toast.makeText(this, "La requete a echouee: " + t.toString(), 4000).show(); } } }

La mthode updateForecast() prend un objet Location en paramtre, obtenu via le processus de mise jour de la localisation. Pour linstant, il suft de savoir que Location dispose des mthodes getLatitude() et getLongitude(), qui renvoient, respectivement, la latitude et la longitude. LURL Google Weather est stocke dans une ressource chane laquelle nous ajoutons en cours dexcution la latitude et la longitude. Nous construisons un objet HttpGet avec cette URL (lobjet HttpClient a t cr dans onCreate()) puis nous excutons cette mthode. partir de la rponse XML, nous construisons la page HTML des prvisions que nous transmettons au widget WebKit. Si lobjet HttpClient choue avec une exception, nous indiquons lerreur laide dun toast.
1. Reportez-vous la page ddie cet ouvrage sur le site www.pearson.fr.

238

Lart du dveloppement Android

Traitement des rponses


La rponse que lon obtient est dans un certain format HTML, XML, JSON, etc. et cest nous, bien sr, de choisir linformation qui nous intresse pour en tirer quelque chose dutile. Dans le cas de WeatherDemo, nous voulons extraire lheure de la prvision, la temprature et licne (qui reprsente les conditions mtorologiques) an de nous en servir pour produire une page HTML. Android fournit :

trois analyseurs XML, lanalyseur DOM classique du W3C (org.w3c.dom), un analyseur SAX (org.xml.sax) et lanalyseur pull prsent au Chapitre 19 ; un analyseur JSON (org.json).

Lorsque cela est possible, vous pouvez bien sr utiliser du code Java tiers pour prendre en charge dautres formats un analyseur RSS/Atom, par exemple. Lutilisation du code tiers a t dcrite au Chapitre 21. Pour WeatherDemo, nous utilisons lanalyseur DOM du W3C dans notre mthode buildForecasts() :
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); } }

Chapitre 22

Communiquer via Internet

239

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 Forecast en incluant la date, la temprature et lURL de licne qui sera afche en fonction du temps. La mthode generatePage() produit son tour un tableau HTML rudimentaire contenant les prvisions :
String generatePage() { StringBuffer bufResult = new StringBuffer("<html><body><table>"); bufResult.append("<tr><th width=\"50%\">Jour</th>" + "<th>Basse</th><th>Haute</th><th>Tendance</th></tr>"); for (Forecast forecast : forecasts) { bufResult.append("<tr><td align=\"center\">"); bufResult.append(forecast.getDay()); bufResult.append("</td><td align=\"center\">"); bufResult.append(forecast.getLowTemp()); bufResult.append("</td>"); bufResult.append("</td><td align=\"center\">"); bufResult.append(forecast.getHighTemp()); bufResult.append("</td><td><img src=\""); bufResult.append(forecast.getIcon()); bufResult.append("\"></td></tr>"); } bufResult.append("</table></body></html>"); return (bufResult.toString()); }

La Figure 22.1 montre le rsultat obtenu.


Figure 22.1 Lapplication WeatherDemo.

240

Lart du dveloppement Android

Autres points importants


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

Partie

IV

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

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

244

Lart du dveloppement Android

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 an de pouvoir crer des applications plus complexes tout en tant de "bons citoyens dAndroid".

Quelle est votre intention ?


Lorsque sir Tim Berners-Lee a conu le protocole de transfert hypertexte, HTTP, il a dni un ensemble de verbes et dadresses sous la forme dURL. Une adresse dsigne une ressource : une page web, une image ou un programme qui sexcute sur un serveur, par exemple. Un verbe prcise laction qui doit sappliquer cette adresse : GET pour la rcuprer, POST pour lui envoyer des donnes de formulaire an quelle les traite, etc. Les intentions sont similaires car elles reprsentent une action et un contexte. Bien quelles permettent de dnir plus dactions et de composants de contexte quil ny a de verbes et de ressources HTTP, le concept est le mme. Tout comme un navigateur web sait comment traiter une paire verbe + URL, Android sait comment trouver les activits ou les autres applications qui sauront grer une intention donne.

Composantes des intentions


Les deux parties les plus importantes dune intention sont laction et ce quAndroid appelle les "donnes". Elles sont quasiment analogues aux verbes et aux URL de HTTP laction est le verbe et les "donnes" sont une Uri comme content://contacts/people/1, reprsentant un contact dans la base de donnes des contacts. Les actions sont des constantes, comme ACTION_VIEW (pour afcher la ressource), ACTION_EDIT (pour lditer) ou ACTION_PICK (pour choisir un lment disponible dans une Uri reprsentant une collection, comme content://contacts/people). Si vous crez une intention combinant ACTION_VIEW avec lUri content://contacts/ people/1 et que vous la passiez Android, ce dernier saura comment trouver et ouvrir une activit capable dafcher cette ressource. Outre laction et lUri des "donnes", vous pouvez placer dautres critres dans une intention (qui est reprsente par un objet Intent) :

Une catgorie. Votre activit "principale" appartient la catgorie LAUNCHER, pour indiquer quelle apparat dans le menu du lanceur. Les autres activits appartiendront probablement aux catgories DEFAULT ou ALTERNATIVE. Un type MIME indiquant le type de ressource sur laquelle vous voulez travailler si vous ne connaissez pas une Uri collection.

Chapitre 23

Cration de ltres dintentions

245

Un composant, cest--dire la classe de lactivit suppose recevoir cette intention. Cette utilisation des composants vite davoir besoin des autres proprits de lintention, mais elle rend cette dernire plus fragile car elle suppose des implmentations spciques. Des "Extras", cest--dire un Bundle dautres informations que vous voulez passer au rcepteur en mme temps que lintention et dont ce dernier pourra tirer parti. Les 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 modis. Il est prfrable dutiliser les modles dUri et les types MIME pour identier les services auxquels vous souhaitez accder. Si vous ne prcisez pas de composant cible, Android devra trouver les activits (ou les autres rcepteurs dintentions) ligibles pour cette intention. Vous aurez remarqu que nous avons mis "activits" au pluriel car une activit peut trs bien se rsoudre en plusieurs activits. Cette approche du routage est prfrable au routage implicite. Essentiellement, trois conditions doivent tre vries pour quune activit soit ligible pour une intention donne : 1. Lactivit doit supporter laction indique. 2. Lactivit doit supporter le type MIME indiqu (sil a t fourni). 3. Lactivit doit supporter toutes les catgories nommes dans lintention. La conclusion est que vous avez intrt ce que vos intentions soient sufsamment spciques pour trouver le ou les bons rcepteurs, mais pas plus. Tout ceci deviendra plus clair mesure que nous tudierons quelques exemples.

Dclarer vos intentions


Tous les composants Android qui souhaitent tre prvenus par des intentions doivent dclarer des ltres dintention an quAndroid sache quelles intentions devraient aller vers quel composant. Pour ce faire, vous devez ajouter des lments intent-filter au chier AndroidManifest.xml.

246

Lart du dveloppement Android

Le script de cration des applications Android (activityCreator ou son quivalent IDE) fournit des ltres dintention tous les projets. Ces dclarations sont de la forme :
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.skeleton"> <application> <activity android:name=".Now" android:label="Now"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

Notez la prsence de llment intent-filter sous llment activity. Il annonceles choses suivantes :

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

Cette activit tant lactivit principale de lapplication, Android sait quelle est le composant quil doit lancer lorsquun utilisateur choisit cette application partir du menu principal. Vous pouvez indiquer plusieurs actions ou catgories dans vos ltres dintention an de prciser que le composant associ (lactivit) gre plusieurs sortes dintentions diffrentes. Il est fort probable que vous voudrez galement que vos activits secondaires (non MAIN) prcisent le type MIME des donnes quelles manipulent. Ainsi, si une intention est 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" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="vnd.android.cursor.item/vnd.commonsware.tour" /> </intent-filter> </activity>

Celle-ci sera alors lance par une intention demandant lafchage dune Uri reprsentant un contenu vnd.android.cursor.item/vnd.commonsware.tour. Cette intention pourrait provenir dune autre activit de la mme application (lactivit principale, par exemple) ou dune autre application qui connat une Uri que cette activit peut grer.

Chapitre 23

Cration de ltres dintentions

247

Rcepteurs dintention
Dans les exemples que nous venons de voir, les ltres dintention taient congurs sur des activits. Cependant, lier les intentions des activits nest parfois pas exactement ce dont on a besoin :

Certains vnements systme peuvent nous obliger dclencher une opration dans un service plutt quune activit. Certains vnements peuvent devoir lancer des activits diffrentes en fonction des circonstances, o le critre repose non pas uniquement sur lintention elle-mme, mais sur un autre tat (si lon obtient lintention X et que la base de donnes contienne Y, on lance lactivit M ; si la base ne contient pas Y, on lance lactivit N, par exemple).

Dans ces situations, Android offre un rcepteur dintention dni comme une classe qui implmente linterface BroadcastReceiver. Les rcepteurs dintention sont des objets conus pour recevoir des intentions notamment celles qui sont diffuses et pour 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 suft dajouter un lment receiver au chier AndroidManifest.xml :
<receiver android:name=".MaClasseReceptriceDIntention/>

Un rcepteur dintention ne vit que le temps de traiter onReceive() lorsque cette mthode se termine, linstance est susceptible dtre supprime par le ramasse-miettes et ne sera pas rutilise. Ceci signie donc que les fonctionnalits de ces rcepteurs sont un peu limites, essentiellement pour viter lappel de fonctions de rappel. Ils ne peuvent notamment pas tre lis un service ni ouvrir une bote de dialogue. La seule exception est lorsque le BroadcastReceiver est implment sur un composant qui a une dure de vie assez longue, comme une activit ou un service : dans ce cas, le rcepteur vivra aussi longtemps que son "hte" (jusqu ce que lactivit soit stoppe, par exemple). Cependant, dans cette situation, vous ne pouvez pas dclarer le rcepteur dans AndroidManifest.xml : il faut appeler registerReceiver() dans la mthode onResume() de lactivit pour annoncer son intrt pour une intention, puis appeler 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 : ceci ne fonctionne que lorsque le rcepteur est actif. Voici ce que prcise la documentation de BroadcastReceiver ce sujet :

248

Lart du dveloppement Android

Si vous enregistrez un rcepteur dans votre implmentation dActivity.onResume(), il faut le dsinscrire dans Activity.onPause() (vous ne recevrez pas dintention pendant la pause et cela vite une surcharge inutile du systme). Neffectuez pas cette dsinscription dans Activity.onSaveInstanceState(), car cette mthode nest pas appele lorsque lutilisateur revient dans son historique. Vous pouvez donc utiliser les intentions pour transmettre des messages aux condition suivantes :

Votre rcepteur ne se soucie pas de manquer des messages lorsquil est inactif. Vous fournissez un moyen pour que le rcepteur rcupre les messages quil a manqus pendant quil tait inactif.

Aux Chapitres 30 et 31, nous verrons un exemple de la premire condition, o le rcepteur (le client du service) utilise des messages reposant sur des intentions lorsquelles sont disponibles, mais pas quand le client nest pas actif.

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

250

Lart du dveloppement Android

Cette approche peut utiliser deux scnarios :

Vous connaissez lactivit lancer, probablement parce quelle fait partie de votre application. Vous disposez dune Uri vers quelque chose et vous voulez que vos utilisateurs puissent en faire quelque chose, bien que vous ne sachiez pas encore comment.

Ce chapitre prsente le premier scnario ; le suivant dtaillera le second.

Activits paires et sous-activits


Lorsque vous dcidez de lancer une activit, une question essentielle laquelle vous devez rpondre est : "Est-ce que mon activit a besoin de savoir quand se termine lactivit quelle a lance ?" Supposons par exemple que vous vouliez crer une activit pour collecter des informations dauthentication pour un service web auquel vous vous connectez vous devrez peuttre vous authentier avec OpenID1 pour utiliser un service OAuth2. En ce cas, votre activit principale devra savoir quand se termine lauthentication pour pouvoir commencer utiliser le service web. Imaginons maintenant une application de courrier lectronique Android. Lorsque lutilisateur dcide de visualiser un chier attach, ni vous ni lutilisateur ne sattend ce que lactivit principale sache quand cette visualisation se terminera. Dans le premier scnario, lactivit lance est clairement subordonne lactivit qui la lance. La premire sera donc srement lance comme une sous-activit, ce qui signie que la seconde sera prvenue de la n de son activit lle. Dans le second scnario, lactivit lance est plutt un "pair" de lactivit qui la lance. Elle sera donc plutt lance comme une activit classique. Votre activit ne sera pas informe de la n de sa "lle" mais, encore une fois, elle na pas vraiment besoin de le savoir.

Dmarrage
Pour dmarrer une activit, il faut une intention et choisir comment la lancer.

Cration dune intention


Comme on la expliqu au Chapitre 1, les intentions encapsulent une requte pour une activit ou une demande adresse un autre rcepteur dintention, an quil ralise une certaine tche.
1. http://openid.net/. 2. http://oauth.net/.

Chapitre 24

Lancement dactivits et de sous-activits

251

Si lactivit que vous comptez lancer vous appartient, il peut tre plus simple de crer une intention explicite, nommant le composant lancer. partir de votre activit, vous pourriez par exemple crer une intention de la faon suivante :
new Intent(this, HelpActivity.class);

Cette instruction indique que vous voulez lancer HelpActivity. Vous pourriez galement crer une intention pour une Uri donne, demandant une action particulire :
Uri uri=Uri.parse("geo:" + lat.toString() + "," + lon.toString()); Intent i=new Intent(Intent.ACTION_VIEW, uri);

Ici, partir de la latitude et de la longitude dune position (respectivement lat et lon), nous construisons une Uri de schma geo et nous crons une intention demandant de lafcher (ACTION_VIEW).

Faire appel
Lorsque lon dispose de lintention, il faut la passer Android et rcuprer lactivit lle lancer. Quatre choix sont alors possibles :

Le plus simple consiste appeler startActivity() en lui passant lintention Android recherchera lactivit qui correspond le mieux et lui passera lintention pour quelle la traite. Votre activit ne sera pas prvenue de la n de lactivit lle. Vous pouvez appeler startActivityForResult() en lui passant lintention et un identiant (unique pour lactivit appelante). Android recherchera lactivit qui correspond le mieux et lui passera lintention. Votre activit sera prvenue par la mthode de rappel onActivityResult() de la n de lactivit lle (voir plus loin). Vous pouvez appeler sendBroadcast(). Dans ce cas, Android passera lintention tous les BroadcastReceiver enregistrs qui pourraient vouloir cette intention, pas uniquement celui qui correspond le mieux. Vous pouvez appeler sendOrderedBroadcast(). Android passera alors lintention tous les BroadcastReceiver candidats, chacun leur tour si lun deux "consomme" lintention, les autres candidats ne sont pas prvenus.

La plupart du temps, vous utiliserez startActivity() ou startActivityForResult() les intentions diffuses sont plutt lances par le systme Android lui-mme. Comme on la indiqu, vous pouvez implmenter la mthode de rappel onActivityResult() lorsque vous utilisez startActivityForResult(), an dtre prvenu de la n de lactivit lle. Cette mthode reoit lidentiant unique fourni startActivityForResult() pour que vous puissiez savoir quelle est lactivit qui sest termine. Vous rcuprez galement :

252

Lart du dveloppement Android

Le code rsultat de lactivit lle qui a appel setResult(). Gnralement, ce code vaut RESULT_OK ou RESULT_CANCELLED, bien que vous puissiez crer vos propres codes (choisissez un entier partir de la valeur RESULT_FIRST_USER). Un objet String optionnel contenant des donnes du rsultat, comme une URL vers une ressource interne ou externe une intention ACTION_PICK se sert gnralement de cette chane pour renvoyer le contenu slectionn. Un objet Bundle optionnel contenant des informations supplmentaires autres que le code rsultat et la chane de donnes.

Pour mieux comprendre le lancement dune activit paire, examinons le projet Activities/Launch. Le chier de description XML est assez simple puisquil contient deux champs pour la latitude et la longitude, ainsi quun bouton :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TableLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:stretchColumns="1,2" > <TableRow> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="2dip" android:paddingRight="4dip" android:text="Situation :" /> <EditText android:id="@+id/lat" android:layout_width="fill_parent" android:layout_height="wrap_content" android:cursorVisible="true" android:editable="true" android:singleLine="true" android:layout_weight="1" /> <EditText android:id="@+id/lon" android:layout_width="fill_parent" android:layout_height="wrap_content" android:cursorVisible="true" android:editable="true" android:singleLine="true" android:layout_weight="1" />

Chapitre 24

Lancement dactivits et de sous-activits

253

</TableRow> </TableLayout> <Button android:id="@+id/map" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Montre moi !" /> </LinearLayout>

LOnClickListener du bouton prend la latitude et la longitude pour les intgrer dans une Uri de schma geo, puis lance lactivit.
package com.commonsware.android.activities; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; public class LaunchDemo extends Activity { private EditText lat; private EditText lon; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); Button btn=(Button)findViewById(R.id.map); lat=(EditText)findViewById(R.id.lat); lon=(EditText)findViewById(R.id.lon); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { String _lat=lat.getText().toString(); String _lon=lon.getText().toString(); Uri uri=Uri.parse("geo:" + _lat + "," +_lon); startActivity(new Intent(Intent.ACTION_VIEW, uri)); } }); } }

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

254

Lart du dveloppement Android

Figure 24.1 Lapplication LaunchDemo, dans laquelle on a saisi un emplacement.

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

Chapitre 24

Lancement dactivits et de sous-activits

255

Navigation avec onglets


La navigation par onglet est lune des principales fonctionnalits des navigateurs web actuels : grce elle, une mme fentre peut afcher plusieurs pages rparties dans une srie donglets. Sur un terminal mobile, cela a moins dintrt car on gaspillerait la prcieuse surface de lcran pour afcher les onglets eux-mmes. Toutefois, pour les besoins de la dmonstration, nous montrerons comment crer ce genre de navigateur, en utilisant TabActivity et les intentions. Au Chapitre 10, nous avons vu quun onglet pouvait contenir une vue ou une activit. Dans ce dernier cas, vous devez fournir une intention qui lancera lactivit souhaite ; le framework de gestion des onglets placera alors linterface utilisateur de cette activit dans longlet. Votre premier instinct pourrait tre dutiliser une Uri http: comme nous lavions fait avec une Uri geo: dans lexemple prcdent :
Intent i=new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse("http://commonsware.com"));

Vous pourriez ainsi utiliser le navigateur intgr et disposer de toutes ses fonctionnalits. Malheureusement, cela ne marche pas car, pour des raisons de scurit, vous ne pouvez pas hberger les activits dautres applications dans vos onglets uniquement vos propres activits. Nous allons donc dpoussirer nos dmonstrations de WebView du Chapitre 13 pour crer le projet Activities/IntentTab. Voici le code source de lactivit principale, celle qui hberge le TabView :
public class IntentTabDemo extends TabActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TabHost host=getTabHost(); host.addTab(host.newTabSpec("un") .setIndicator("CW") .setContent(new Intent(this, CWBrowser.class))); host.addTab(host.newTabSpec("deux") .setIndicator("Android") .setContent(new Intent(this, AndroidBrowser.class))); } }

Comme vous pouvez le constater, notre classe hrite de TabActivity : nous navons donc pas besoin de crer un chier de description XML TabActivity sen occupe pour nous.

256

Lart du dveloppement Android

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://commonsware.com"); } } public class AndroidBrowser extends Activity { WebView browser; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); browser=new WebView(this); setContentView(browser); browser.loadUrl("http://code.google.com/android"); } }

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

Chapitre 24

Lancement dactivits et de sous-activits

257

Figure 24.3 Lapplication IntentTabDemo montrant le premier onglet.

Figure 24.4 Lapplication IntentTabDemo montrant le second onglet.

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

260

Lart du dveloppement Android

Pourtant, on na absolument aucune ide de ce quil est possible de faire avec toutes les Uri. On peut srement afcher tous les contenus, mais peut-on les modier ? Peut-on les appeler au tlphone ? En outre, lutilisateur pouvant ajouter des applications avec des nouveaux types de contenus tout moment, on ne peut pas supposer connatre toutes les combinaisons possibles en consultant simplement les applications de base fournies avec tous les terminaux Android. Heureusement, les concepteurs dAndroid ont pens ce problme et ont mis notre disposition plusieurs moyens de prsenter nos utilisateurs un ensemble dactivits lancer pour une Uri donne mme si lon na aucune ide de ce que reprsente vraiment cette Uri. Ce chapitre explore quelques-uns de ces outils dintrospection.

Faites votre choix


Parfois, on sait quune Uri reprsente une collection dun certain type : on sait, par exemple, que content://contacts/people reprsente la liste des contacts dans le rpertoire initial des contacts. Dans ce cas, on laisse lutilisateur choisir un contact que notre activit pourra ensuite utiliser (pour le marquer ou lappeler, par exemple). Pour ce faire, on doit crer une intention ACTION_PICK sur lUri concerne, puis lancer une sous-activit (par un appel startActivityForResult()) an que lutilisateur puisse choisir un contenu du type indiqu. Si notre mthode de rappel onActivityResult() pour cette requte reoit le code rsultat RESULT_OK, la chane de donnes peut tre analyse an de produire une Uri reprsentant le contenu choisi. titre dexemple, examinons le projet Introspection/Pick : cette activit prsente lutilisateur un champ pouvant contenir une Uri dsignant une collection (prremplie ici avec content://contacts/people), plus un trs gros bouton :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <EditText android:id="@+id/type" android:layout_width="fill_parent" android:layout_height="wrap_content" android:cursorVisible="true" android:editable="true" android:singleLine="true" android:text="content://contacts/people" /> <Button android:id="@+id/pick" android:layout_width="fill_parent"

Chapitre 25

Trouver les actions possibles grce lintrospection

261

android:layout_height="fill_parent" android:text="Dismoi tout!" android:layout_weight="1" /> </LinearLayout>

Lorsquon clique dessus, le bouton cre une intention ACTION_PICK pour lUri collection qui a t saisie par lutilisateur ; puis la sous-activit est lance. Si cette dernire se termine par RESULT_OK, une intention ACTION_VIEW est invoque pour lUri rsultante.
public class PickDemo extends Activity { static final int PICK_REQUEST=1337; private EditText type; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); type=(EditText)findViewById(R.id.type); Button btn=(Button)findViewById(R.id.pick); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { Intent i=new Intent(Intent.ACTION_PICK, Uri.parse(type.getText().toString())); startActivityForResult(i, PICK_REQUEST); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode==PICK_REQUEST) { if (resultCode==RESULT_OK) { startActivity(new Intent(Intent.ACTION_VIEW, data.getData())); } } } }

Lutilisateur peut donc choisir une collection (voir Figure 25.1), slectionner un contenu (voir Figure 25.2) et lafcher (voir Figure 25.3).

262

Lart du dveloppement Android

Figure 25.1 Lapplication PickDemo lors de son dmarrage.

Figure 25.2 La mme application, aprs avoir cliqu sur le bouton : la liste des contacts safche.

Figure 25.3 Afchage dun contact lanc par PickDemo aprs que lutilisateur a choisi une personne de la liste.

Chapitre 25

Trouver les actions possibles grce lintrospection

263

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

Ici, monUri est une Uri dcrivant le contenu qui sera afch par lutilisateur dans cette activit. MonActivite est le nom de la classe de lactivit et menu, le menu modier. Dans notre cas, lintention que lon utilise pour choisir les actions exige que les rcepteurs dintention appropris reconnaissent la catgorie CATEGORY_ALTERNATIVE. Puis nous

264

Lart du dveloppement Android

ajoutons les options au menu avec la mthode addIntentOptions(), laquelle nous passons les paramtres suivants :

La position de tri pour cet ensemble de choix. Cette valeur est gnralement 0 (pour que lensemble apparaisse dans lordre o il est ajout au menu) ou ALTERNATIVE (pour quil apparaisse aprs les autres choix du menu). Un nombre unique pour cet ensemble de choix ou 0 si lon na pas besoin de ce nombre. Une instance de ComponentName reprsentant lactivit qui remplit son menu elle sert ltrer les propres actions de lactivit, an quelle puisse les traiter comme elle le souhaite. Un tableau dinstances dIntent, contenant les correspondances "spciques" toutes les actions correspondant ces Intent safcheront dans le menu avant les autres actions possibles. Lintention pour laquelle vous voulez les actions disponibles. Un ensemble dindicateurs. Le seul rellement pertinent est reprsent par MATCH_DEFAULT_ONLY, qui indique que les actions qui correspondent doivent galement implmenter la catgorie DEFAULT_CATEGORY. Si lon na pas besoin de cette information, il suft dutiliser la valeur 0 pour ces indicateurs. Un tableau de Menu.Items qui contiendra les lments de menu qui correspondent au tableau des instances Intent spciques fourni en quatrime paramtre, ou null si lon nutilise pas ces lments.

Demander lentourage
Les familles ActivityAdapter et addIntentOptions() utilisent toutes les deux la mthode queryIntentActivityOptions() pour rechercher les actions possibles. queryIntentActivityOptions() est implmente dans PackageManager : pour obtenir une instance de cette classe, servez-vous de la mthode getPackageManager(). La mthode queryIntentActivityOptions() prend certains des paramtres daddIntentOptions(), notamment le ComponentName de lappelant, le tableau des instances dIntent "spciques", lIntent gnrale reprsentant les actions que vous recherchez et lensemble des indicateurs. Elle renvoie une liste dinstances dIntent correspondant aux critres indiqus, les Intent spciques en premier. Pour offrir des actions alternatives aux utilisateurs par un autre moyen quaddIntentOptions(), vous pouvez appeler queryIntentActivityOptions(), obtenir les instances dIntent et les utiliser pour remplir une autre interface utilisateur (une barre doutils, par exemple).

26
Gestion de la rotation
Certains terminaux Android, comme le G1 de T-Mobile, disposent dun clavier " tiroir" qui, lorsquil est sorti, provoque le passage de lcran du mode portrait au mode paysage. Dautres, comme liPhone, utilisent des acclromtres pour dterminer lorientation de lcran. Android fournit plusieurs moyens de grer la rotation de lcran an que vos applications puissent elles-mmes traiter correctement les deux orientations. Ces outils vous aident simplement dtecter et grer le processus de rotation cest vous de vrier que vos interfaces utilisateurs apparatront correctement dans les deux orientations.

Philosophie de la destruction
Par dfaut, lorsquune modication dans la conguration du tlphone risque daffecter la slection des ressources, Android supprimera et recrera toutes les activits en cours dexcution ou en pause la prochaine fois quelles seront afches. Bien que ce phnomne puisse avoir lieu pour un grand nombre de modications de conguration (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 chiers de description, notamment).

266

Lart du dveloppement Android

Il faut bien comprendre quil sagit du comportement par dfaut : il peut tre le plus adapt lune ou lautre de vos activits, mais vous pouvez le contrler et adapter la rponse de vos activits aux changements dorientation ou de conguration.

Tout est pareil, juste diffrent


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

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"

Chapitre 26

Gestion de la rotation

267

android:layout_height="fill_parent" > <Button android:id="@+id/pick" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:text="Choisir" android:enabled="true" /> <Button android:id="@+id/view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:text="Voir" android:enabled="false" /> </LinearLayout>

Linterface utilisateur est essentiellement compose de deux boutons occupant, chacun, la moiti de lcran. En mode portrait, les boutons sont placs lun au-dessus de lautre ; en mode paysage, ils sont cte cte. Lapplication semble fonctionner correctement une rotation (Ctrl+F12 dans lmulateur) modie la disposition de linterface. Bien que les boutons naient pas dtat, vous constateriez, 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 drive de lexemple PickDemo du Chapitre 25 (voir Figures 25.1, 25.2 et 25.3), o cliquer sur un contact vous permettait de consulter sa che. Ici, on la divise en deux boutons, "Voir" ntant actif que lorsquun contact a t slectionn. Voyons comment tout ceci est gr par onSaveInstanceState() :
public class RotationOneDemo extends Activity { static final int PICK_REQUEST=1337; Button viewButton=null; Uri contact=null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btn=(Button)findViewById(R.id.pick); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { Intent i=new Intent(Intent.ACTION_PICK,

268

Lart du dveloppement Android

Uri.parse("content://contacts/people")); startActivityForResult(i, PICK_REQUEST); } }); viewButton=(Button)findViewById(R.id.view); viewButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { startActivity(new Intent(Intent.ACTION_VIEW, contact)); } }); restoreMe(savedInstanceState); viewButton.setEnabled(contact!=null); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode==PICK_REQUEST) { if (resultCode==RESULT_OK) { contact=data.getData(); viewButton.setEnabled(true); } } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (contact!=null) { outState.putString("contact", contact.toString()); } } private void restoreMe(Bundle state) { contact=null; if (state!=null) { String contactUri=state.getString("contact"); if (contactUri!=null) { contact=Uri.parse(contactUri); } } } }

Dans lensemble, ceci ressemble une activit normale... parce que cen est une. 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

Chapitre 26

Gestion de la rotation

269

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 che du contact slectionn. Le rsultat est prsent aux Figures 26.1 et 26.2. Lavantage de cette implmentation est quelle gre un certain nombre dvnements systme en plus de la rotation la fermeture de lapplication cause dun manque de mmoire, par exemple. Pour le plaisir, mettez en commentaire lappel de restoreMe() dans onCreate() et essayez de lancer lapplication : vous constaterez quelle "oublie" un contact slectionn dans lune des orientations lorsque vous faites tourner lmulateur ou le terminal.
Figure 26.1 Lapplication RotationOne en mode portrait.

Figure 26.2 Lapplication RotationOne en mode paysage.

270

Lart du dveloppement Android

Il ny a pas de petites conomies !


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

Chapitre 26

Gestion de la rotation

271

startActivityForResult(i, PICK_REQUEST); } }); viewButton=(Button)findViewById(R.id.view); viewButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { startActivity(new Intent(Intent.ACTION_VIEW, contact)); } }); restoreMe(); viewButton.setEnabled(contact!=null); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode==PICK_REQUEST) { if (resultCode==RESULT_OK) { contact=data.getData(); viewButton.setEnabled(true); } } } @Override public Object onRetainNonConfigurationInstance() { return(contact); } private void restoreMe() { contact=null; if (getLastNonConfigurationInstance()!=null) { contact=(Uri)getLastNonConfigurationInstance(); } } }

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

272

Lart du dveloppement Android

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

Chapitre 26

Gestion de la rotation

273

</activity> </application> </manifest>

Ici, nous indiquons que nous traiterons nous-mmes les modications de conguration keyboardHidden et orientation, ce qui nous protge de toutes les modications de "rotation" une ouverture dun clavier ou une rotation physique. Notez que cette conguration sapplique une activit, pas lapplication si vous avez plusieurs activits, vous devrez dcider pour chacune delles de la tactique employer. Voici le code Java de ce projet :
public class RotationThreeDemo extends Activity { static final int PICK_REQUEST=1337; Button viewButton=null; Uri contact=null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupViews(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode==PICK_REQUEST) { if (resultCode==RESULT_OK) { contact=data.getData(); viewButton.setEnabled(true); } } } public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); setupViews(); } private void setupViews() { setContentView(R.layout.main); Button btn=(Button)findViewById(R.id.pick); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { Intent i=new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts/people")); startActivityForResult(i, PICK_REQUEST); }

274

Lart du dveloppement Android

}); viewButton=(Button)findViewById(R.id.view); viewButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { startActivity(new Intent(Intent.ACTION_VIEW, contact)); } }); viewButton.setEnabled(contact!=null); } }

Limplmentation donCreate() dlgue lessentiel de son traitement la mthode setupViews(), qui charge le layout et congure les boutons. Cette partie a t place dans sa propre mthode car elle est galement appele partir donConfigurationChanged().

Forcer le destin
Dans les trois sections prcdentes, nous avons vu comment traiter les vnements de 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 suft dajouter android:screenOrientation = "portrait" (ou "landscape") au chier AndroidManifest.xml (voir le projet Rotation/RotationFour) :
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.rotation.four" android:versionCode="1" android:versionName="1.0.0"> <application android:label="@string/app_name"> <activity android:name=".RotationFourDemo" android:screenOrientation="portrait" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

Noubliez pas que, comme prcdemment, cette conguration est propre une activit.

Chapitre 26

Gestion de la rotation

275

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

Figure 26.4 Lapplication RotationFour en mode paysage.

276

Lart du dveloppement Android

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. Par dfaut, le G1 ne se comporte pas de cette faon sur ce terminal, lorientation de lcran ne dpend que de louverture ou de la fermeture du clavier. Cependant, il est trs facile de modier ce comportement : pour que lafchage pivote en fonction de la position du tlphone, il suft dajouter android:screenOrientation = "sensor" au chier AndroidManifest.xml (voir le projet Rotation/RotationFive) :
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.rotation.five" android:versionCode="1" android:versionName="1.0.0"> <application android:label="@string/app_name"> <activity android:name=".RotationFiveDemo" android:screenOrientation="sensor" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

La valeur "sensor" indique Android que vous souhaitez que ce soient les acclromtres qui contrlent lorientation de lcran, an quil pivote en mme temps que le tlphone. Au moins sur le G1, ceci semble ne fonctionner que lorsque lon passe de la position portrait classique la position paysage en faisant pivoter le tlphone de 90 degrs dans le sens inverse des aiguilles dune montre. Une rotation dans lautre sens ne modie pas lcran. Notez galement que cette conguration dsactive la rotation de lcran par louverture du clavier. Dans une activit "normale", avec le terminal en position portrait, lcran pivotera si lon ouvre le clavier ; avec une activit utilisant la conguration android:screenOrientation = "sensor", lcran ne pivotera pas.

Partie

Fournisseurs de contenus et services


CHAPITRE 27. CHAPITRE 28. CHAPITRE 29. CHAPITRE 30. CHAPITRE 31. CHAPITRE 32. 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

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

280

Lart du dveloppement Android

Android permet dutiliser des fournisseurs de contenu existants ou de crer les vtres. Ce chapitre est consacr leur utilisation ; le Chapitre 28 expliquera comment mettre disposition vos propres donnes laide du framework des fournisseurs de contenu.

Composantes dune Uri


Le modle simpli de construction dune Uri est constitu du schma, de lespace de noms des donnes et, ventuellement, de lidentiant de linstance. Ces diffrents 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 didentiant 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:// contacts/people, car le fournisseur de contenu des contacts peut fournir dautres donnes en utilisant dautres valeurs pour lUri de base. LUri de base reprsente une collection dinstances. Combine avec un identiant dinstance (5, par exemple), elle reprsente une instance unique. La plupart des API dAndroid sattendent ce que les URI soient des objets Uri, bien quil soit plus naturel de les considrer comme des chanes. La mthode statique Uri.parse() permet de crer une instance dUri partir de sa reprsentation textuelle.

Obtention dun descripteur


Do viennent ces instances dUri ? Le point de dpart le plus courant, lorsque lon connat le type de donnes avec lequel on souhaite travailler, consiste obtenir lUri de base du fournisseur de contenu lui-mme. CONTENT_URI, par exemple, est lUri de base des contacts reprsents par des personnes elle correspond content://contacts/people. Si vous avez simplement besoin de la collection, cette Uri fonctionne telle quelle ; si vous avez besoin dune instance dont vous connaissez lidentiant, vous pouvez utiliser la mthode addId() dUri pour la lui ajouter, an dobtenir une Uri pour cette instance prcise. Vous pourriez galement obtenir des instances dUri partir dautres sources vous pouvez rcuprer des descripteurs dUri pour les contacts via des sous-activits rpondant aux intentions ACTION_PICK, par exemple. Dans ce cas, lUri est vraiment un descripteur

Chapitre 27

Utilisation dun fournisseur de contenu (content provider)

281

opaque... jusqu ce que vous dcidiez de la dchiffrer laide des diffrentes mthodes daccs de la classe Uri. Vous pouvez galement utiliser des objets String cods en dur ("content:// contacts/ people", par exemple) et les convertir en instances Uri grce Uri.parse(). Ceci dit, ce nest pas une solution idale car les valeurs des Uri de base sont susceptibles dvoluer au cours du temps.

Cration des requtes


partir dune Uri de base, vous pouvez excuter une requte pour obtenir des donnes du fournisseur de contenu li cette Uri. Ce mcanisme ressemble beaucoup SQL : on prcise les "colonnes" qui nous intressent, les contraintes permettant de dterminer les lignes du rsultat, un ordre de tri, etc. La seule diffrence est que cette requte sadresse un fournisseur de contenu, pas directement un SGBDR comme SQLite. Le point nvralgique de ce traitement est la mthode managedQuery(), qui attend cinq paramtres : 1. LUri de base du fournisseur de contenu auquel sadresse la requte ou lUri dinstance de lobjet interrog. 2. Un tableau des proprits dinstances que vous voulez obtenir de ce fournisseur de contenu. 3. Une contrainte, qui fonctionne comme la clause WHERE de SQL. 4. Un ensemble ventuel de paramtres lier la contrainte, par remplacement des ventuels marqueurs demplacements quelle contient. 5. Une instruction de tri facultative, qui fonctionne comme la clause ORDER BY de SQL. Cette mthode renvoie un objet Cursor partir duquel vous pourrez ensuite rcuprer les donnes produites par la requte. Les "proprits" sont aux fournisseurs de contenu ce que sont les colonnes aux bases de donnes. En dautres termes, chaque instance (ligne) renvoye par une requte est forme dun ensemble de proprits (colonnes) reprsentant, chacune, un lment des donnes. Tout ceci deviendra plus clair avec un exemple : voici lappel de la mthode managedQuery() de la classe ConstantsBrowser du projet ContentProvider/Constants :
constantsCursor=managedQuery(Provider.Constants.CONTENT_URI, PROJECTION, null, null, null);

Les paramtres de cet appel sont :

282

Lart du dveloppement Android

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. La liste des proprits pour un fournisseur de contenu donn devrait tre prcise dans la documentation (ou le code source) de celui-ci. Ici, nous utilisons des valeurs logiques de la classe Provider qui reprsentent les diffrentes proprits qui nous intressent (lidentiant unique, le nom et la valeur de la constante).

Sadapter aux circonstances


Lorsque managedQuery() nous a fourni un Cursor, nous avons accs au rsultat de la requte et pouvons en faire ce que nous voulons. Nous pouvons, par exemple, extraire manuellement les donnes du Cursor pour remplir des widgets ou dautres objets. Cependant, si le but de la requte est dobtenir une liste dans laquelle lutilisateur pourra choisir un lment, il est prfrable dutiliser la classe SimpleCursorAdapter, qui tablit un pont entre un Cursor et un widget de slection comme une ListView ou un Spinner. Pour ce faire, il suft de copier le Cursor dans un SimpleCursorAdapter que lon passe ensuite au widget ce widget montrera alors les options disponibles. La mthode onCreate() de ConstantsBrowser, par exemple, prsente lutilisateur la liste des constantes physiques :
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); constantsCursor=managedQuery(Provider.Constants.CONTENT_URI, PROJECTION, null, null, null); ListAdapter adapter=new SimpleCursorAdapter(this, R.layout.row, constantsCursor, new String[] {Provider.Constants.TITLE, Provider.Constants.VALUE}, new int[] {R.id.title, R.id.value}); setListAdapter(adapter); registerForContextMenu(getListView()); }

Chapitre 27

Utilisation dun fournisseur de contenu (content provider)

283

Aprs lexcution de managedQuery() et lobtention du Cursor, ConstantsBrowser cre un SimpleCursorAdapter avec les paramtres suivants :

Lactivit (ou un autre Context) qui cre ladaptateur. Ici, il sagit de ConstantsBrowser elle-mme. Lidentiant du layout utilis pour afcher les lments de la liste (R.layout.row). Le curseur (constantsCursor). Les proprits extraire du curseur et utiliser pour congurer les instances View des lments de la liste (TITLE et VALUE). Les identiants correspondants des widgets TextView qui recevront ces proprits (R.id.title et R.id.value).

Puis on place ladaptateur dans la ListView et lon obtient le rsultat prsent la Figure 27.1.
Figure 27.1 ConstantsBrowser, afchage dune liste de constantes physiques.

Pour disposer de plus de contrle sur les vues, vous pouvez crer une sous-classe de SimpleCursorAdapter et rednir getView() an de crer vos propres widgets pour la liste, comme on la expliqu au Chapitre 9.

Gestion manuelle des curseurs


Vous pouvez, bien sr, extraire les donnes du curseur " la main". Linterface Cursor ressemble ce que proposent les API daccs aux bases de donnes lorsquelles fournis-

284

Lart du dveloppement Android

sent des curseurs sous forme dobjets bien que, comme toujours, la diffrence rside dans les dtails.

Position
Les instances de Cursor intgrent la notion de position, qui est semblable linterface Iterator de Java. Pour accder aux lignes dun curseur, vous disposez des mthodes suivantes :

moveToFirst() vous place sur la premire ligne de lensemble rsultat, tandis que moveToLast() vous place sur la dernire. moveToNext() vous place sur la ligne suivante et teste sil reste une ligne traiter (auquel cas elle renvoie true ; false sinon). moveToPrevious() vous place sur la ligne prcdente : cest la mthode inverse de moveToNext(). moveToPosition() vous place sur la ligne lindice indiqu ; move() vous place sur une ligne relativement la ligne courante (selon un dplacement qui peut tre positif ou ngatif). getPosition() renvoie lindice de la position courante. Vous disposez galement dun ensemble de prdicats, dont isFirst(), isLast(), isBeforeFirst() et isAfterLast().

Proprits
Lorsque le Cursor est positionn sur la ligne voulue, plusieurs mthodes supportant les diffrents types possibles vous permettent dobtenir les proprits de cette ligne (getString(), getInt(), getFloat(), etc.). Chacune delles prend en paramtre lindice de la proprit voulue (en partant de zro). Pour savoir si une proprit donne possde une valeur, vous pouvez utiliser la mthode isNull() pour savoir si cette proprit est null ou non.

Insertions et suppressions
Les fournisseurs de contenu seraient assez peu intressants si vous ne pouviez pas ajouter ou supprimer des donnes et que vous deviez vous contenter de modier celles qui sy trouvent. Pour insrer des donnes dans un fournisseur de contenu, linterface ContentProvider (que vous pouvez obtenir via un appel getContentProvider() dans votre activit) offre deux possibilits :

Chapitre 27

Utilisation dun fournisseur de contenu (content provider)

285

La mthode insert() prend en paramtre une Uri de collection et une structure ContentValues dcrivant lensemble de donnes placer dans la ligne. La mthode bulkInsert() prend en paramtre une Uri de collection et un tableau de structures ContentValues pour remplir plusieurs lignes la fois.

La mthode insert() renvoie une Uri que vous pourrez manipuler ensuite. La mthode bulkInsert() renvoie le nombre de lignes cres ; vous devrez effectuer une requte pour obtenir nouveau les donnes que vous venez dinsrer. Voici, par exemple, un extrait de ConstantsBrowser qui insre une nouvelle constante au fournisseur de contenu partir dun DialogWrapper fournissant le nom et la valeur de cette constante :
private void processAdd(DialogWrapper wrapper) { ContentValues values=new ContentValues(2); values.put(Provider.Constants.TITLE, wrapper.getTitle()); values.put(Provider.Constants.VALUE, wrapper.getValue()); getContentResolver().insert(Provider.Constants.CONTENT_URI, values); constantsCursor.requery(); }

Comme nous avons dj un Cursor en suspens pour le contenu de ce fournisseur, nous appelons requery() sur celui-ci pour mettre jour son contenu. Cet appel, son tour, mettra jour le SimpleCursorAdapter qui enveloppe ventuellement le Cursor et ces modications seront rpercutes sur les widgets de slection (ListView, par exemple) qui utilisent cet adaptateur. Pour supprimer une ou plusieurs lignes dun fournisseur de contenu, utilisez la mthode delete() de ContentResolver. Elle fonctionne comme linstruction DELETE de SQL et prend trois paramtres : 1. LUri reprsentant la collection (ou linstance) que vous voulez supprimer. 2. Une contrainte fonctionnant comme une clause WHERE, qui sert dterminer les lignes qui doivent tre supprimes. 3. Un ventuel ensemble de paramtres qui remplaceront les marqueurs demplacements apparaissant dans la contrainte.

Attention aux BLOB !


Les BLOB (Binary large objects) existent dans de nombreux SGBDR, dont SQLite. Le modle Android, cependant, prfre grer ces volumes de donnes via leurs propres Uri.

286

Lart du dveloppement Android

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 lafcher, ce que sait parfaitement faire le widget ImageView si on lui fournit une Uri vers un chier JPEG. En stockant la photo de sorte quelle possde sa propre Uri, vous navez pas besoin de copier des donnes du fournisseur de contenu vers une zone temporaire juste pour pouvoir lafcher : il suft dutiliser lUri. On suppose, en fait, que peu dapplications Android feront beaucoup plus que dposer des donnes binaires et utiliser des widgets ou des activits prdnies pour afcher ces donnes.

28
Construction dun fournisseur de contenu
La construction dun fournisseur de contenu est srement la partie la plus complique et la plus ennuyeuse du dveloppement avec Android car un fournisseur de contenu a de nombreuses exigences en termes dimplmentation de mthodes et dexposition de donnes publiques. Malheureusement, tant que vous naurez pas utilis votre 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, par exemple, vous dveloppez un lecteur RSS et que vous vouliez autoriser les autres programmes accder aux ux que vous avez tlchargs et mis en cache , vous aurez besoin dun fournisseur de contenu.

288

Lart du dveloppement Android

Dabord, une petite dissection


Comme on la expliqu au chapitre prcdent, lUri est le pilier de laccs aux donnes dun fournisseur de contenu : cest la seule information quil faut rellement connatre. partir de lUri de base du fournisseur, vous pouvez excuter des requtes ou construire une Uri vers une instance prcise dont vous connaissez lidentiant. Cependant, pour construire un fournisseur de contenu, vous devez en savoir un peu plus sur les dtails internes de lUri dun contenu. Celle-ci est compose de deux quatre parties en fonction de la situation :

Elle comprend toujours un schma (content://), indiquant quil sagit dune Uri de contenu, pas dune Uri vers une ressource web (http://), par exemple. Elle a toujours une autorit, qui est la premire partie du chemin plac aprs le schma. Lautorit est une chane unique identiant le fournisseur qui gre le contenu associ cette Uri. Elle contient ventuellement un chemin de types de donnes, form de la liste des segments de chemins situs entre lautorit et lidentiant dinstance (sil y en a un). Ce chemin peut tre vide si le fournisseur ne gre quun seul type de donnes. Il peut tre form dun seul segment (truc) ou de plusieurs (truc/machin/chouette) pour grer tous les scnarios daccs aux donnes requis par le fournisseur de contenu. Elle peut contenir un identiant dinstance, qui est un entier identiant une information particulire du contenu. Une Uri de contenu sans identiant dinstance dsigne lensemble du contenu reprsent par lautorit (et, sil est fourni, par le chemin des donnes).

Une Uri de contenu peut tre aussi simple que content://sekrits, qui dsigne 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 (identie par 17) de type card/pin gre par le fournisseur de contenu sekrits.

Puis un peu de saisie


Vous devez ensuite proposer des types MIME correspondant au contenu de votre fournisseur. Android utilise la fois lUri de contenu et le type MIME pour identier le contenu sur le terminal. Une Uri de collection ou, plus prcisment, la combinaison dune autorit et dun chemin de type de donnes doit correspondre deux types MIME, lun pour la collection, lautre pour une instance donne. Ces deux types correspondent aux motifs dUri que nous avons vus dans la section prcdente, respectivement pour les Uri avec et sans identiant. Comme on la vu aux Chapitres 24 et 25, on peut fournir un type MIME une intention pour quelle se dirige vers lactivit adquate (lintention ACTION_PICK

Chapitre 28

Construction dun fournisseur de contenu

289

sur un type MIME de collection pour appeler une activit de slection permettant de choisir une instance dans cette collection, par exemple). Le type MIME de la collection doit tre de la forme vnd.X.cursor.dir/Y, o X est le nom de votre socit, organisation ou projet et o Y est un nom de type dlimit par des points. Vous pourriez, par exemple, utiliser le type MIME vnd.tlagency.cursor.dir/ sekrits.card.pin pour votre collection de secrets. Le type MIME de l'instance doit tre de la forme vnd.X.cursor.item/Y o X et Y sont gnralement les mmes valeurs que celles utilises pour le type MIME de la collection (bien que ce ne soit pas obligatoire).

tape n 1 : crer une classe Provider


Un fournisseur de contenu est une classe Java, tout comme une activit et une intention. La principale tape de la cration dun fournisseur consiste donc produire sa classe Java, qui doit hriter de ContentProvider. Cette sous-classe doit implmenter six mthodes qui, ensemble, assurent le service quun fournisseur de contenu est cens offrir aux activits qui veulent crer, lire, modier ou supprimer du contenu.

onCreate()
Comme pour une activit, le point dentre principal dun fournisseur de contenu est sa mthode onCreate(), o lon ralise toutes les oprations dinitialisation que lon souhaite. Cest l, notamment, que lon initialise le stockage des donnes. Si, par exemple, on compte stocker les donnes dans tel ou tel rpertoire dune carte SD et utiliser un chier XML comme "table des matires", cest dans onCreate() que lon vriera que ce rpertoire et ce chier existent et, dans le cas contraire, quon les crera pour que le reste du fournisseur de contenu sache quils sont disponibles. De mme, si le fournisseur de contenu utilise SQLite pour son stockage, cest dans onCreate() que lon testera si les tables existent en interrogeant la table sqlite_master. Voici, par exemple, la mthode onCreate() de la classe Provider du projet ContentProvider/Constants :
@Override public boolean onCreate() { db=(new DatabaseHelper(getContext())).getWritableDatabase(); return (db == null) ? false : true; }

Toute la "magie" de ce code se trouve dans lobjet priv DatabaseHelper, qui a t dcrit au Chapitre 20.

290

Lart du dveloppement Android

query()
Comme lon pourrait sy attendre, la mthode query() contient le code grce auquel le fournisseur de contenu obtient des dtails sur une requte quune activit veut raliser. Cest vous de dcider deffectuer ou non cette requte. La mthode query() attend les paramtres suivants :

Une Uri reprsentant la collection ou linstance demande. Un String[] contenant la liste des proprits renvoyer. Un String reprsentant lquivalent dune clause WHERE de SQL et qui contraint le rsultat de la requte. Un String[] contenant les valeurs qui remplaceront les ventuels marqueurs 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. Pour les fournisseurs qui utilisent SQLite, toutefois, limplmentation de la mthode query() devrait tre triviale : il suft dutiliser un SQLiteQueryBuilder pour convertir les diffrents paramtres en une seule instruction SQL, puis dappeler la mthode query() de cet objet pour invoquer la requte et obtenir le Cursor qui sera ensuite renvoy par votre propre mthode query(). Voici, par exemple, limplmentation de la mthode query() de Provider :
@Override public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort) { SQLiteQueryBuilder qb=new SQLiteQueryBuilder(); qb.setTables(getTableName()); if (isCollectionUri(url)) { qb.setProjectionMap(getDefaultProjection()); } else { qb.appendWhere(getIdColumnName() + "=" + url.getPathSegments().get(1)); } String orderBy; if (TextUtils.isEmpty(sort)) { orderBy=getDefaultSortOrder(); } else {

Chapitre 28

Construction dun fournisseur de contenu

291

orderBy=sort; } Cursor c=qb.query(db, projection, selection, selectionArgs, null, null, orderBy); c.setNotificationUri(getContext().getContentResolver(), url); return c; }

Nous crons un SQLiteQueryBuilder et nous insrons les dtails de la requte dans cet objet. Vous remarquerez que la requte pourrait utiliser soit une Uri de collection, soit une Uri dinstance dans ce dernier cas, nous devons ajouter lidentiant dinstance la requte. Puis nous utilisons la mthode query() de lobjet builder pour obtenir un Cursor correspondant au rsultat.

insert()
Votre mthode insert() recevra une Uri reprsentant la collection et une structure ContentValues contenant les donnes initiales de la nouvelle instance. Cest vous de la crer, dy placer les donnes fournies et de renvoyer une Uri vers cette nouvelle instance. Une fois encore, cette implmentation sera triviale pour un fournisseur de contenu qui utilise SQLite : il suft de vrier que toutes les donnes requises ont t fournies par lactivit, de fusionner les valeurs par dfaut avec les donnes fournies et dappeler la mthode insert() de la base de donnes pour crer linstance. Voici, par exemple, limplmentation de la mthode insert() de Provider :
@Override public Uri insert(Uri url, ContentValues initialValues) { long rowID; ContentValues values; if (initialValues!=null) { values=new ContentValues(initialValues); } else { values=new ContentValues(); } if (!isCollectionUri(url)) { throw new IllegalArgumentException("URL inconnue" + url); } for (String colName : getRequiredColumns()) { if (values.containsKey(colName) == false) { throw new IllegalArgumentException("Colonne manquante : " + colName); } } populateDefaultValues(values); rowID=db.insert(getTableName(), getNullColumnHack(), values); if (rowID > 0) { Uri uri=ContentUris.withAppendedId(getContentUri(), rowID);

292

Lart du dveloppement Android

getContext().getContentResolver().notifyChange(uri, null); return uri; } throw new SQLException("Echec de linsertion dans " + url); }

La technique est la mme que prcdemment : pour raliser linsertion, on utilise les 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 vrions que les valeurs fournies leur correspondent. Cest au fournisseur de fournir les valeurs par dfaut (via populateDefaultValues()) pour les colonnes qui ne sont pas fournies lappel dinsert() et qui ne sont pas automatiquement gres par la dnition de la table SQLite.

update()
La mthode update() prend en paramtre lUri de linstance ou de la collection modier, 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 didentier linstance ou les instances modier (en utilisant lUri et la clause WHERE) puis de remplacer leurs valeurs actuelles par celles qui ont t fournies en paramtre. Si vous utilisez SQLite comme systme de stockage, il suft de transmettre tous les paramtres la mthode update() de la base, mme si cet appel variera lgrement selon que vous modiez une ou plusieurs instances. Voici, par exemple, limplmentation de la mthode update() de Provider :
@Override public int update(Uri url, ContentValues values, String where, String[] whereArgs) { int count; if (isCollectionUri(url)) { count=db.update(getTableName(), values, where, whereArgs); } else { String segment=url.getPathSegments().get(1); count=db .update(getTableName(), values, getIdColumnName() + "=" + segment

Chapitre 28

Construction dun fournisseur de contenu

293

+ (!TextUtils.isEmpty(where) ? " AND (" + where + ) : ""), whereArgs); } getContext().getContentResolver().notifyChange(url, null); return count; }

Ici, les modications pouvant sappliquer une instance prcise ou toute la collection, nous testons lUri avec isCollectionUri() : sil sagit dune modication de collection, on se contente de la faire. Sil sagit dune modication dune seule instance, il faut ajouter une contrainte la clause WHERE pour que la requte ne porte que sur la ligne concerne.

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

294

Lart du dveloppement Android

Ce code est quasiment identique celui de la mthode update() que nous avons dcrite prcdemment il supprime un sous-ensemble de la collection ou une instance unique (si elle vrie la clause WHERE passe en paramtre).

getType()
getType() est la dernire mthode quil faut implmenter. Elle prend une Uri en 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 n 2 : fournir une Uri


Vous devez galement ajouter un membre public statique contenant lUri de chaque collection supporte par votre fournisseur de contenu. Gnralement, on utilise une Uri constante, publique et statique dans la classe du fournisseur lui-mme :
public static final Uri CONTENT_URI= Uri.parse("content://com.commonsware.android.tourit.Provider/tours");

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

Chapitre 28

Construction dun fournisseur de contenu

295

tape n 3 : dclarer les proprits


Pour dnir les noms des proprits de votre fournisseur, vous devez crer une classe publique statique implmentant linterface BaseColumns :
public static final class Constants implements BaseColumns { public static final Uri CONTENT_URI =Uri.parse("content://com.commonsware.android.constants.Provider/ constants"); public static final String DEFAULT_SORT_ORDER="title"; public static final String TITLE="title"; public static final String VALUE="value"; }

Si vous utilisez SQLite pour stocker les donnes, les valeurs des constantes correspondant aux noms des proprits doivent tre les noms des colonnes respectives de la table, an de pouvoir simplement passer la projection (tableau de proprits) lors de lappel query() ou le ContentValues lors dun appel insert() ou update(). Vous remarquerez que les types des proprits ne sont pas prciss. Il peut sagir de 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 signie que vous devez documenter soigneusement vos proprits an que les utilisateurs de votre fournisseur de contenu sachent quoi sattendre.

tape n 4 : modier le manifeste


Ce qui lie limplmentation du fournisseur de contenu au reste de lapplication se trouve dans le chier AndroidManifest.xml. Il suft dajouter un lment <provider> comme ls de llment <application> :
<provider android:name=".Provider" android:authorities="com.commonsware.android.tourit.Provider" />

La proprit android:name est le nom de la classe du fournisseur de contenu, prx par un point pour indiquer quil se trouve dans lespace de noms des classes de cette application. La proprit android:authorities est une liste des autorits reconnues par le 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 identiant dinstance. Chaque autorit de chaque valeur CONTENT_URI devrait tre incluse dans la liste android:authorities.

296

Lart du dveloppement Android

Quand Android rencontre une Uri de contenu, il peut dsormais passer en revue les fournisseurs enregistrs via les manifestes an de trouver une autorit qui correspond. Il saura alors quelle application et quelle classe implmentent le fournisseur de contenu et pourra ainsi tablir le lien entre lactivit appelante et le fournisseur de contenu appel.

Avertissements en cas de modications


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

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

298

Lart du dveloppement Android

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.

Mre, puis-je ?
Demander dutiliser les donnes ou les services dautres applications exige dajouter llment uses-permission au chier AndroidManifest.xml. Votre manifeste peut ainsi contenir zro ou plusieurs de ces lments comme ls directs de llment racine manifest. Llment uses-permission na quun seul attribut, android:name, qui est le nom de la permission exige par votre application :
<uses-permission android:name="android.permission.ACCESS_LOCATION" />

Les permissions systme de base commencent toutes par android.permission et sont numres dans la documentation du SDK la rubrique Manifest.permission. Les applications tierces peuvent possder leurs propres permissions, qui seront sans doute galement documentes. Voici quelques-unes des permissions prdnies les plus 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 modication des donnes dans les fournisseurs de contenu intgrs.

Lutilisateur devra conrmer ces permissions lors de linstallation de lapplication. Cependant, cette conrmation nest pas disponible dans la version actuelle de lmulateur. Si vous ne possdez pas une permission donne et que vous tentiez deffectuer une 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.

Chapitre 29

Demander et exiger des permissions

299

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 "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 condentialit des informations ou lutilisation de services qui pourraient vous coter cher. Les permissions de base dAndroid sont conues pour cela pouvez-vous lire ou modier des contacts, envoyer des SMS, etc. Si votre application stocke des informations susceptibles dtre prives, la scurit est moins un problme. Mais, si elle stocke des donnes prives comme des informations mdicales, la scurit devient bien plus importante. La premire tape pour scuriser votre propre application laide de permissions consiste les dclarer dans le chier AndroidManifest.xml. Au lieu dutiliser uses-permission, vous ajoutez alors des lments permission. L encore, il peut y avoir zro ou plusieurs de ces lments, qui seront tous des ls directs de manifest. Dclarer une permission est un peu plus compliqu quutiliser une permission car vous devez donner trois informations : 1. Le nom symbolique de la permission. Pour viter les collisions de vos permissions avec celles des autres applications, utilisez lespace de noms Java de votre application comme prxe. 2. Un label pour la permission : un texte court et comprhensible par les utilisateurs. 3. Une description de la permission : un texte un peu plus long et comprhensible par les utilisateurs.
<permission android:name="vnd.tlagency.sekrits.SEE_SEKRITS" android:label="@string/see_sekrits_label" android:description="@string/see_sekrits_description" />

Cet lment nimpose pas la permission. Il indique seulement quil sagit dune permission possible ; votre application doit quand mme savoir dtecter les violations de scurit lorsquelles ont lieu.

300

Lart du dveloppement Android

Imposer les permissions via le manifeste


Une application a deux moyens dimposer des permissions, en prcisant o et dans quelles circonstances elles sont requises. Le plus simple consiste indiquer dans le manifeste les endroits o elles sont requises. Les activits, les services et les rcepteurs dintentions peuvent dclarer un attribut android:permission dont la valeur est le nom de la permission exige pour accder ces lments :
<activity android:name=".SekritApp" android:label="Top Sekrit" android:permission="vnd.tlagency.sekrits.SEE_SEKRITS"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>

Seules les applications qui ont demand la permission indique pourront accder au composant ainsi scuris. Ici, "accder" la signication suivante :

Les activits ne peuvent pas tre lances sans cette permission. Les services ne peuvent pas tre lancs, arrts ou lis une activit sans cette permission. Les rcepteurs dintention ignorent les messages envoys via sendBroadcast() si lexpditeur ne possde pas cette permission.

Les fournisseurs de contenu offrent deux attributs distincts, android:readPermission et android:writePermission :


<provider android:name=".SekritProvider" android:authorities="vnd.tla.sekrits.SekritProvider" android:readPermission="vnd.tla.sekrits.SEE_SEKRITS" android:writePermission="vnd.tla.sekrits.MOD_SEKRITS" />

Dans ce cas, readPermission contrle laccs linterrogation du fournisseur de contenu, tandis que writePermission contrle laccs aux insertions, aux modications et aux suppressions de donnes dans le fournisseur.

Chapitre 29

Demander et exiger des permissions

301

Imposer les permissions ailleurs


Il y a deux moyens supplmentaires dimposer les permissions dans votre code. Vos services peuvent vrier les permissions chaque appel, grce checkCallingPermission(), qui renvoie PERMISSION_GRANTED ou PERMISSION_DENIED selon que lappelant a ou non la permission indique. Si, par exemple, votre service implmente des mthodes de lecture et dcriture spares, vous pourriez obtenir le mme effet que readPermission et writePermission en vriant que ces mthodes ont les permissions requises. Vous pouvez galement inclure une permission dans lappel sendBroadcast(). Ceci signie que les rcepteurs possibles doivent possder cette permission : sans elle, ils ne pourront pas recevoir lintention. Le sous-systme dAndroid inclut probablement la permission RECEIVE_SMS lorsquil diffuse linformation quun SMS est arriv, par exemple ceci restreint les rcepteurs de cette intention aux seuls qui sont autoriss recevoir des messages SMS.

Vos papiers, sil vous plat !


Il ny a pas de dcouverte automatique des permissions lors de la compilation ; tous les checs lis aux permissions ont lieu lexcution. Il est donc important de documenter les permissions requises pour votre API publique, ce qui comprend les fournisseurs de contenu, les services et les activits conues pour tre lances partir dautres activits. Dans le cas contraire, les programmeurs voulant sinterfacer avec votre application devront batailler pour trouver les rgles de permissions que vous avez mises en place. En outre, vous devez vous attendre ce que les utilisateurs de votre application soient interrogs pour conrmer les permissions dont votre application a besoin. Vous devez donc leur indiquer quoi sattendre, sous peine quune question pose par le tlphone ne les dcide ne pas installer ou ne pas utiliser lapplication.

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

304

Lart du dveloppement Android

Service avec classe


Limplmentation dun service partage de nombreuses caractristiques avec la construction dune activit. On hrite dune classe de base fournie par Android, on rednit certaines mthodes du cycle de vie et on accroche le service au systme via le manifeste. La premire tape de cration dun service consiste tendre la classe Service en drivant une classe WeatherPlusService dans le cadre de notre exemple. Tout comme les activits peuvent rednir les mthodes onCreate(), onResume(), onPause() et apparentes, les implmentations de Service peuvent rednir trois mthodes du cycle de vie : 1. onCreate(), qui, comme pour les activits, sera appele lorsque le processus du service est cr. 2. onStart(), qui est appele lorsquun service est lanc manuellement par un autre processus, ce qui est diffrent dun lancement implicite par une requte IPC (voir Chapitre 31). 3. onDestroy(), qui est appele lorsque le service est teint. Voici, par exemple, la mthode onCreate() de WeatherPlusService :
@Override public void onCreate() { super.onCreate(); client=new DefaultHttpClient(); format=getString(R.string.url); myLocationManager= (LocationManager)getSystemService(Context.LOCATION_SERVICE); myLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 10000.0f, onLocationChange); singleton=this; }

On appelle dabord la mthode onCreate() de la superclasse an quAndroid puisse effectuer correctement son travail dinitialisation. Puis on cre notre HttpClient et la chane de format, comme nous lavions fait dans le projet Weather. On obtient ensuite linstance de LocationManager pour notre application et on lui demande de rcuprer les mises jour mesure que notre emplacement volue, via LocationManager.GPS_MANAGER, qui sera dtaill au Chapitre 33.

Chapitre 30

Cration dun service

305

La mthode onDestroy() est bien plus simple :


@Override public void onDestroy() { super.onDestroy(); singleton=null; myLocationManager.removeUpdates(onLocationChange); }

On se contente ici de stopper la surveillance des dplacements, aprs avoir appel la mthode onDestroy() de la superclasse pour quAndroid puisse effectuer les travaux de nettoyage ncessaires. Outre ces mthodes du cycle de vie, votre service doit galement implmenter la mthode onBind(), qui renvoie un IBinder, la composante fondamentale du mcanisme dIPC. Pour les services locaux ce qui nous intresse dans ce chapitre , il suft que cette mthode renvoie null.

Il ne peut en rester quun !


Par dfaut, les services sexcutent dans le mme processus que tous les autres 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 suft donc que lon expose le singleton lui-mme pour que les autres composants puissent accder lobjet. Dans le cas de WeatherPlusService, nous utilisons un membre statique public, singleton, pour stocker ce singleton (nous pourrions videmment rendre ce membre priv et fournir une mthode daccs). Dans onCreate(), nous initialisons singleton avec lobjet lui-mme, tandis que, dans onDestroy(), nous le rinitialisons null. Cette dernire tape est trs importante. Les donnes statiques sont dangereuses car elles peuvent provoquer des fuites mmoire : si nous oublions de remettre singleton null dans onDestroy(), lobjet WeatherPlusService restera indniment en mmoire, bien quil soit dconnect du reste dAndroid. Assurez-vous de toujours rinitialiser null les rfrences statiques de vos services ! Comme nous le verrons au chapitre suivant, les activits peuvent dsormais accder aux mthodes publiques de votre objet service grce ce singleton.

306

Lart du dveloppement Android

Destine du manifeste
Enn, vous devez ajouter le service votre chier AndroidManifest.xml pour quil soit reconnu comme un service utilisable. Il suft pour cela dajouter un lment service comme ls de llment application en utilisant lattribut android:name pour dsigner la classe du service. Voici, par exemple, le chier AndroidManifest.xml du projet WeatherPlus :
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.service"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <application android:label="@string/app_name"> <activity android:name=".WeatherPlus" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".WeatherPlusService" /> </application> </manifest>

La classe du service tant dans le mme espace de noms que tout ce qui se trouve dans cette application, vous pouvez utiliser la notation pointe simplie (".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 Chapitre 29 pour plus de dtails.

Sauter la clture
Parfois, le service doit prvenir de faon asynchrone une activit dun certain vnement. La thorie derrire limplmentation de WeatherPlusService, par exemple, est que le service est prvenu lorsque le terminal (ou lmulateur) change demplacement. Le service appelle alors le service web et produit une nouvelle page de prvisions. Puis il doit prvenir lactivit que ces nouvelles prvisions sont disponibles, an quelle puisse les charger et les afcher. Pour interagir de cette faon avec les composants, deux possibilits sont votre disposition : les mthodes de rappel et les intentions diffuses.

Chapitre 30

Cration dun service

307

Notez que, si votre service doit simplement alerter lutilisateur dun certain vnement, vous pouvez utiliser une notication, qui est le moyen le plus classique de grer ce type de scnario.

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

308

Lart du dveloppement Android

String url=String.format(format, loc.getLatitude(), loc.getLongitude()); HttpGet getMethod=new HttpGet(url); try { ResponseHandler<String> responseHandler=new BasicResponseHandler(); String responseBody=client.execute(getMethod, responseHandler); String page=generatePage(buildForecasts(responseBody)); synchronized(this) { forecast=page; } sendBroadcast(broadcast); } catch (Throwable t) { android.util.Log.e("WeatherPlus", "Exception dans updateForecast()", t); } return(null); } @Override protected void onProgressUpdate(Void... inutilis) { // Inutile ici } @Override protected void onPostExecute(Void inutilis) { // Inutile ici } }

Lessentiel de ce code ressemble au code du projet Weather original il effectue la requte HTTP, convertit sa rponse en un ensemble dobjets Forecast quil transforme ensuite en page web. La premire diffrence, outre lintroduction dAsyncTask, est que la page web est simplement mise en cache dans le service car celui-ci ne peut pas 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"; "com.commonsware.android.service.ForecastUpdateEvent";

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

310

Lart du dveloppement Android

Figure 31.1 Le client du service WeatherPlus.

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

Chapitre 31

Appel dun service

311

Une partie pineuse de ce code consiste sassurer que le singleton est bien l quand on a besoin de lui. Lappel startService() tant asynchrone, vous reprenez le contrle tout de suite, or le service dmarrera bientt, mais pas immdiatement. Dans le cas de WeatherPlus, on peut sen contenter car on nessaie pas dutiliser le singleton tant que le service ne nous a pas prvenus de la disponibilit dune prvision, via une intention de diffusion. Cependant, dans dautres situations, il faudra peut-tre appeler la mthode postDelayed() dun Handler an de reporter dune seconde ou deux lutilisation du service, en esprant que le singleton sera devenu disponible entre-temps.

Capture de lintention
Au chapitre prcdent, nous avons vu comment le service diffusait une intention pour signaler lactivit WeatherPlus quun mouvement du terminal avait provoqu une modication des prvisions. Nous pouvons maintenant tudier la faon dont lactivit reoit et utilise cette intention. Voici les implmentations donResume() et donPause() de WeatherPlus :
@Override public void onResume() { super.onResume(); registerReceiver(receiver, new IntentFilter(WeatherPlusService.BROADCAST_ACTION)); } @Override public void onPause() { super.onPause(); unregisterReceiver(receiver); }

Dans onResume(), nous enregistrons un BroadcastReceiver statique pour recevoir les intentions qui correspondent laction dclare par le service. Dans onPause(), nous dsactivons ce rcepteur car nous ne recevrons plus ces intentions pendant que nous sommes en pause. Le BroadcastReceiver, de son ct, met simplement les prvisions jour :
private BroadcastReceiver receiver=new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { updateForecast(); } };

32
Alerter les utilisateurs avec des notications
Les messages qui surgissent, les icnes et les "bulles" qui leur sont associes, les icnes qui bondissent dans la barre dtat, etc. sont utiliss par les programmes pour attirer votre attention, et parfois pour de bonnes raisons. Votre tlphone vous alerte aussi probablement pour dautres motifs que la rception dun appel : batterie faible, alarme programme, notications de rendez-vous, rception de SMS ou de courrier lectronique, etc. Il nest donc pas tonnant quAndroid dispose dun framework complet pour grer ce genre dvnements, dsigns sous le terme de notications.

Types davertissements
Un service qui sexcute en arrire-plan doit pouvoir attirer lattention des utilisateurs lorsquun vnement survient la rception dun courrier, par exemple. En outre, le service doit galement diriger lutilisateur vers une activit lui permettant dagir en rponse cet vnement lire le message reu, par exemple. Pour ce type daction,

314

Lart du dveloppement Android

Android fournit des icnes dans la barre dtat, des avertissements lumineux et dautres indicateurs que lon dsigne globalement par le terme de notications. Votre tlphone actuel peut possder ce type dicne pour indiquer le niveau de charge de la batterie, la force du signal, lactivation de Bluetooth, etc. Avec Android, les applications peuvent ajouter leurs propres icnes dans la barre dtat et faire en sorte quelles napparaissent que lorsque cela est ncessaire (lorsquun message est arriv, par exemple). Vous pouvez lancer des notications via le NotificationManager, qui est un service du systme. Pour lutiliser, vous devez obtenir lobjet service via un appel la mthode getSystemService (NOTIFICATION_SERVICE) de votre activit. Le NotificationManager vous offre trois mthodes : une pour avertir (notify()) et deux pour arrter davertir (cancel() et cancelAll()). La mthode notify() prend en paramtre une Notification, qui est une structure dcrivant la forme que doit prendre lavertissement. Les sections qui suivent dcrivent tous les champs publics mis votre disposition (mais souvenez-vous que tous les terminaux ne les supportent pas ncessairement tous).

Notications matrielles
Vous pouvez faire clignoter les LED du terminal en mettant lights true et en prcisant leur couleur (sous la forme dune valeur #ARGB dans ledARGB). Vous pouvez galement prciser le type de clignotement (en indiquant les dures dallumage et dextinction en millisecondes dans ledOnMS et ledOffMS). Vous pouvez faire retentir un son en indiquant une Uri vers un contenu situ, par exemple, dans un ContentManager (sound). Considrez ce son comme une sonnerie pour votre application. Enn, vous pouvez faire vibrer le terminal en indiquant les alternances de la vibration (vibrate) en millisecondes dans un tableau de valeurs long. Cette vibration peut tre le comportement par dfaut ou vous pouvez laisser le choix lutilisateur, au cas o il aurait besoin dun avertissement plus discret quune sonnerie.

Icnes
Alors que les lumires, les sons et les vibrations sont destins faire en sorte que lutilisateur regarde son terminal, les icnes constituent ltape suivante consistant signaler ce qui est si important. Pour congurer une icne pour une Notification, vous devez initialiser deux champs publics : icon, qui doit fournir lidentiant dune ressource Drawable reprsentant licne voulue, et contentIntent, qui indique le PendingIntent qui devra tre dclench lorsquon clique sur licne. Vous devez vous assurer que le PendingIntent sera captur

Chapitre 32

Alerter les utilisateurs avec des notications

315

ventuellement par le code de votre application pour prendre les mesures ncessaires an que lutilisateur puisse grer lvnement qui a dclench la notication. Vous pouvez galement fournir un texte qui apparatra lorsque licne est place sur la barre dtat (tickerText). Pour congurer ces trois composantes, lapproche la plus simple consiste appeler la mthode setLatestEventInfo(), qui enveloppe leurs initialisations dans un seul appel.

Les avertissements en action


Examinons le projet Notifications/Notify1, notamment sa classe NotifyDemo :
public class NotifyDemo extends Activity { private static final int NOTIFY_ME_ID=1337; private Timer timer=new Timer(); private int count=0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btn=(Button)findViewById(R.id.notify); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { TimerTask task=new TimerTask() { public void run() { notifyMe(); } }; timer.schedule(task, 5000); } }); btn=(Button)findViewById(R.id.cancel); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { NotificationManager mgr= (NotificationManager)getSystemService(NOTIFICATION_SERVICE); mgr.cancel(NOTIFY_ME_ID); }

316

Lart du dveloppement Android

}); } private void notifyMe() { final NotificationManager mgr= (NotificationManager)getSystemService(NOTIFICATION_SERVICE); Notification note=new Notification(R.drawable.red_ball, "Message detat!", System. currentTimeMillis()); PendingIntent i=PendingIntent.getActivity(this, 0, new Intent(this, NotifyMessage.class), 0); note.setLatestEventInfo(this, "Titre de la notification", "Message de notification", i); note.number=++count; mgr.notify(NOTIFY_ME_ID, note); } }

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

Chapitre 32

Alerter les utilisateurs avec des notications

317

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

318

Lart du dveloppement Android

Figure 32.3 Le tiroir des notications compltement ouvert, contenant notre notication.

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

Partie

VI

Autres fonctionnalits dAndroid


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

33
Accs aux services de localisation
Le GPS est une fonctionnalit trs apprcie des terminaux mobiles actuels car il permet de vous indiquer votre emplacement gographique tout moment. Bien que lutilisation la plus frquente du GPS soit la cartographie et lorientation, connatre votre emplacement vous ouvre de nombreux autres horizons. Vous pouvez, par exemple, mettre en place une application de chat dynamique o vos contacts sont classs selon leurs emplacements gographiques, an de choisir ceux qui sont les plus prs de vous. Vous pouvez galement "gotaguer" automatiquement les articles que vous postez sur Twitter ou dautres services similaires. Cependant, le GPS nest pas le seul moyen didentier un emplacement gographique :

Lquivalent europen de GPS, Galileo, est encore en cours de mise au point. La triangulation permet de dterminer votre position en fonction de la force du signal des antennes relais proches de vous. La proximit des "hotspots" Wi, dont les positions gographiques sont connues.

322

Lart du dveloppement Android

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 emplacement gographique. Certains ont une meilleure prcision que dautres ; certains sont gratuits, tandis que dautres sont payants ; certains peuvent vous donner des informations supplmentaires, comme votre altitude par rapport au niveau de la mer ou votre vitesse courante. Android a donc abstrait tout cela en un ensemble dobjets LocationProvider. Votre 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 LocationProvider pour savoir quel est le LocationProvider qui convient votre cas particulier. Votre application devra galement disposer de la permission ACCESS_LOCATION ; sinon les diffrentes API de localisation choueront cause dune violation de scurit. Selon les fournisseurs de localisation que vous voulez utiliser, vous pourrez galement avoir besoin dautres permissions, comme ACCESS_COARSE_LOCATION ou ACCESS_FINE_LOCATION.

Se trouver soi-mme
Lopration la plus vidente dun fournisseur de localisation consiste trouver votre emplacement actuel. Pour ce faire, vous avez besoin dun LocationManager, que vous obtiendrez par un appel getSystemService(LOCATION_SERVICE) partir de votre 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 33

Accs aux services de localisation

323

Si vous choisissez la premire approche, un appel la mthode getProviders() du LocationManager vous donnera une liste de fournisseurs que vous pouvez prsenter lutilisateur pour quil fasse son choix. Vous pouvez galement crer et initialiser un objet Criteria, en prcisant ce que vous attendez dun LocationProvider. Par exemple :

setAltitudeRequired() pour indiquer si vous avez besoin ou non de connatre votre altitude ; setAccuracy() pour xer un niveau de prcision minimal de la position, en mtres ; setCostAllowed() pour indiquer si le fournisseur doit tre gratuit ou non (cest--dire sil peut impliquer un paiement de la part de lutilisateur du terminal).

Lorsque lobjet Criteria a t rempli, appelez la mthode getBestProvider() de votre LocationManager et Android passera les critres en revue pour vous donner la meilleure rponse. Tous ces critres peuvent ne pas tre vris part celui concernant le prix, ils peuvent tous tre ignors si rien ne correspond. Pour effectuer des tests, vous pouvez galement indiquer directement dans votre code le nom dun LocationProvider (gps, par exemple). Lorsque vous connaissez le nom du LocationProvider, vous pouvez appeler 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. Ces mthodes renvoient un objet Location qui vous indiquera la latitude et la longitude du terminal en degrs des valeurs double de Java. Si le fournisseur donne dautres informations, 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 LocationProvider consiste senregistrer pour les modications de la position du terminal, comme expliqu dans la section suivante.

324

Lart du dveloppement Android

Se dplacer
Tous les fournisseurs de localisation ne rpondent pas immdiatement. GPS, par exemple, ncessite lactivation dun signal et la rception des satellites (cest ce que lon appelle un "x GPS") avant de pouvoir connatre sa position. Cest la raison pour laquelle Android ne fournit pas de mthode getMeMyCurrentLocationNow(). Ceci combin avec le fait que les utilisateurs puissent vouloir que leurs mouvements soient pris en compte dans lapplication, vous comprendrez pourquoi il est prfrable denregistrer les modications de la position et les utiliser pour connatre la position courante. Les applications Weather et WeatherPlus montrent comment enregistrer ces mises jour en appelant la mthode requestLocationUpdates() de lobjet LocationManager. Cette mthode prend quatre paramtres : 1. Le nom du fournisseur de localisation que vous souhaitez utiliser. 2. Le temps, en millisecondes, qui doit scouler avant que lon puisse obtenir une mise jour de la position. 3. Le dplacement minimal du terminal en mtres pour que lon puisse obtenir une mise jour de la position. 4. Un LocationListener qui sera prvenu des vnements lis la localisation, comme le montre le code suivant :
LocationListener onLocationChange=new LocationListener() { public void onLocationChanged(Location location) { updateForecast(location); } public void onProviderDisabled(String provider) { // Exige par linterface, mais inutilise } public void onProviderEnabled(String provider) { // Exige par linterface, mais inutilise } public void onStatusChanged(String provider, int status, Bundle extras) { // Exige par linterface, mais inutilise } };

Ici, nous appelons simplement updateForecast() en lui passant lobjet Location fourni lappel de la mthode de rappel onLocationChanged(). Comme on la vu au Chapitre 30, limplmentation dupdateForecast() construit une page web contenant les prvisions mtorologiques pour lemplacement courant et envoie un message de diffusion an que lactivit sache quune mise jour est disponible.

Chapitre 33

Accs aux services de localisation

325

Lorsque lon na plus besoin des mises jour, on appelle removeUpdates() avec le LocationListener que lon avait enregistr.

Est-on dj arriv ? Est-on dj arriv ? Est-on dj arriv ?


Parfois, on veut savoir non pas o lon se trouve ni mme o lon va, mais si lon est l o lon voulait aller. Il pourrait sagir dune destination nale ou dune tape dans un ensemble de directions pour pouvoir indiquer le virage suivant, par exemple. Dans ce but, LocationManager fournit la mthode addProximityAlert(), qui enregistre un PendingIntent qui sera dclench lorsque le terminal se trouvera une certaine distance dun emplacement donn. La mthode addProximityAlert() attend les paramtres suivants :

La latitude et la longitude de la position qui nous intresse. Un rayon prcisant la proximit avec la position pour que lintention soit leve. Une dure denregistrement en millisecondes passe cette priode, lenregistrement expirera automatiquement. Une valeur de 1 indique que lenregistrement sera 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" dnie par la position et le rayon.

Notez quil nest pas garanti que vous receviez une intention sil y a eu une interruption dans les services de localisation ou si le terminal nest pas dans la zone cible pendant le temps o lalerte de proximit est active. Si la position, par exemple, est trop proche du but et que le rayon est un peu trop rduit, le terminal peut ne faire que longer le bord de la zone cible ou y passer si rapidement que sa position ne sera pas enregistre pendant quil est dans la zone. Il vous appartient de faire en sorte quune activit ou un rcepteur dintention rponde lintention que vous avez enregistre pour lalerte de proximit. Cest galement vous de dterminer ce qui doit se passer lorsque cette intention arrive : congurer une notication (faire vibrer le terminal, par exemple), enregistrer linformation dans un fournisseur de contenu, poster un message sur un site web, etc. Notez que vous recevrez lintention chaque fois que la position est enregistre et que vous tes dans la zone cible pas simplement lorsque vous y entrez. Par consquent, vous la recevrez plusieurs fois le nombre doccurrences dpend de la taille de la zone et de la vitesse de dplacement du terminal.

326

Lart du dveloppement Android

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

34
Cartographie avec MapView et MapActivity
Google Maps est lun des services les plus connus de Google aprs le moteur de 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. Android intgre Google Maps : cette activit de cartographie est directement disponible partir du menu principal mais, surtout, les dveloppeurs ont leur disposition les classes MapView et MapActivity pour intgrer des cartes gographiques dans leurs applications. Grce elles, ils peuvent non seulement contrler le niveau du zoom, permettre aux utilisateurs de faire dler la carte, mais galement utiliser les services de localisation pour marquer lemplacement du terminal et indiquer son dplacement. Heureusement, cette intgration est assez simple et vous pouvez exploiter toute sa puissance si vous le souhaitez.

328

Lart du dveloppement Android

Termes dutilisation
Google Maps, notamment lorsquil est intgr dans des applications tierces, ncessite le respect dun assez grand nombre de termes juridiques. Parmi ceux-ci se trouvent des clauses que vous trouverez peut-tre insupportables. Si vous dcidez dutiliser Google Maps, prenez soin de bien lire tous ces termes an dtre sr que lutilisation que vous comptez en faire ne les viole pas. Nous vous conseillons fortement de demander lavis dun conseiller juridique en cas de doute. En outre, ne dlaissez pas les autres possibilits de cartographie qui reposent sur dautres sources de donnes gographiques, comme OpenStreetMap1.

Empilements
partir dAndroid 1.5, Google Maps ne fait plus partie du SDK proprement parler mais a t dplac dans les API supplmentaires de Google, qui sont des extensions au SDK de base. Ce systme dextension fournit des points dentre aux autres sous-systmes qui peuvent se trouver sur certains terminaux mais pas sur dautres. En ralit, Google Maps ne fait pas partie du projet open-source Android, et il existera ncessairement des terminaux qui nen disposeront pas cause des problmes de licence. Dans lensemble, le fait que Google Maps soit une extension naffectera pas votre dveloppement habituel condition de ne pas oublier les points suivants :

Vous devrez crer votre projet pour quil utilise la cible 3 (-t 3), an dtre sr que les API de Google Maps sont disponibles. Pour tester lintgration de Google Maps, vous aurez galement besoin dun AVD qui utilise la cible 3 (-t 3). Inversement, pour tester votre application dans un environnement Android 1.5 sans Google Maps, vous devrez crer un AVD qui utilise la cible 2 (-t 2).

Les composants essentiels


Pour insrer une carte gographique dans une application, le plus simple consiste crer une sous-classe de MapActivity. Comme ListActivity, qui enveloppe une partie des dtails cachs derrire une activit domine par une ListView, MapActivity gre une partie de la conguration dune activit domine par une MapView.

1. http://www.openstreetmap.org/.

Chapitre 34

Cartographie avec MapView et MapActivity

329

Dans le chier de layout de la sous-classe de MapActivity, vous devez ajouter un lment qui, actuellement, sappelle com.google.android.maps.MapView. Il sagit ici dun nom totalement dvelopp qui ajoute le nom de paquetage complet au nom de la classe (cette notation est ncessaire car MapView ne se trouve pas dans lespace de noms com.google.android.widget). Vous pouvez donner la valeur que vous souhaitez lattribut android:id du widget MapView et grer tous les dtails lui permettant de safcher correctement ct des autres widgets. Vous devez cependant prciser les attributs suivants :

android:apiKey. Dans une version de lapplication en production, cet attribut doit contenir une cl de lAPI Google Maps (voir plus loin). android:clickable = "true". Si vous voulez que les utilisateurs puissent cliquer sur la carte et la faire dler.

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


<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <com.google.android.maps.MapView android:id="@+id/map" android:layout_width="fill_parent" android:layout_height="fill_parent" android:apiKey="<VOTRE_CL_API>" android:clickable="true" /> <LinearLayout android:id="@+id/zoom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" /> </RelativeLayout>

Nous prsenterons ces mystrieux LinearLayout zoom et apiKey plus bas dans ce chapitre. Vous devez galement ajouter deux informations supplmentaires votre chier AndroidManifest.xml :

Les permissions INTERNET et ACCESS_COARSE_LOCATION. Dans llment <application>, ajoutez un lment <uses-library> avec lattribut android:name = "com.google.android.maps" pour indiquer que vous utilisez lune des API facultatives dAndroid.

Voici le chier AndroidManifest.xml du projet NooYawk:


<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.maps"> <uses-permission android:name="android.permission.INTERNET" />

330

Lart du dveloppement Android

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <application android:label="@string/app_name"> <uses-library android:name="com.google.android.maps" /> <activity android:name=".NooYawk" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

Avec la sous-classe de MapActivity, cest peu prs tout ce dont vous avez besoin pour dbuter. Si vous ne faites rien dautre, que vous compiliez ce projet et que vous linstalliez dans lmulateur, vous obtiendrez une belle carte du monde. Notez, cependant, que MapActivity est une classe abstraite et que vous devez donc implmenter la mthode isRouteDisplayed() pour prciser si vous fournissez ou non une gestion des itinraires. En thorie, lutilisateur doit pouvoir faire dler la carte en utilisant le pad directionnel. Cependant, ce nest pas trs pratique lorsque lon a le monde entier dans sa main... Une carte du monde ntant pas trs utile en elle-mme, nous devons lui ajouter quelques fonctionnalits.

Testez votre contrle


Pour trouver votre widget MapView, il suft, comme dhabitude, dappeler la mthode findViewById(). Le widget lui-mme fournit la mthode getMapController(). Entre le MapView et le MapController, vous disposez dun bon nombre de possibilits pour dterminer ce quafche la carte et la faon dont elle se comporte ; les sections suivantes prsentent le zoom et le centrage, qui sont srement celles que vous utiliserez le plus.

Zoom
La carte du monde avec laquelle vous dmarrez est plutt vaste. Sur un tlphone, on prfre gnralement consulter une carte ayant une tendue plus rduite quelques pts de maisons, par exemple. Vous pouvez contrler directement le niveau du zoom grce la mthode setZoom() de MapController. Celle-ci attend un paramtre entier reprsentant le niveau du zoom, o 1 reprsente la vue du monde entier et 21, le plus fort grossissement que vous pouvez 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

Chapitre 34

Cartographie avec MapView et MapActivity

331

pts de maisons dans chaque dimension et constitue gnralement un bon point de dpart pour vos essais. Si vous souhaitez que les utilisateurs aient le droit de changer le niveau du zoom, utilisez lappel setBuiltInZoomControls(true) : il pourra alors utiliser les contrles de zoom qui se trouvent en bas de la carte.

Centrage
Gnralement, quel que soit le niveau du zoom, vous voudrez contrler ce qui est afch sur la carte : la position courante de lutilisateur ou un emplacement sauvegard avec dautres donnes de votre activit, par exemple. Pour changer la position de la carte, appelez la mthode setCenter() de MapController. Cette mthode prend un objet GeoPoint en paramtre. Un GeoPoint reprsente un 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 afcher les images satellites avec Google Maps et Android. MapView offre la mthode toggleSatellite(), qui, comme son nom lindique, permet dactiver ou de dsactiver la vue satellite de la surface prsente sur la carte. Vous pouvez faire en sorte de laisser lutilisateur le soin de faire ce choix partir dun menu ou, comme dans NooYawk, via des touches :
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_S) { map.setSatellite(!map.isSatellite()); return(true); } else if (keyCode == KeyEvent.KEYCODE_Z) { map.displayZoomControls(true); return(true); } return(super.onKeyDown(keyCode, event)); }

332

Lart du dveloppement Android

Couches sur couches


Si vous avez dj utilis la version complte de Google Maps, vous avez srement dj vu que lon pouvait dposer des choses sur la carte elle-mme : les "repres", par exemple, qui indiquent les emplacements des points dintrt proches de la position que vous avez demande. En termes de carte et galement pour la plupart des diteurs graphiques srieux , ces repres sont placs sur une couche distincte de celle de la carte elle-mme et ce que vous voyez au nal est la superposition de ces deux couches. Android permet de crer de telles couches, an de marquer les cartes en fonction des choix de lutilisateur et des besoins de votre application. NooYawk, par exemple, utilise une couche pour montrer les emplacements des immeubles slectionns dans Manhattan.

Classes Overlay
Toute couche ajoute votre carte doit tre implmente comme une sous-classe dOverlay. Si vous voulez simplement ajouter des repres, vous pouvez utiliser la sous-classe ItemizedOverlay, qui vous simpliera la tche. Pour attacher une couche votre carte, il suft dappeler la mthode getOverlays() de votre objet MapView et dajouter votre instance dOverlay avec add() :
marker.setBounds(0, 0, marker.getIntrinsicWidth(), marker.getIntrinsicHeight()); map.getOverlays().add(new SitesOverlay(marker));

Nous expliquerons un peu plus loin le rle de marker.

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

Drivez votre sous-classe (SitesOverlay, dans notre exemple) dItemizedOverlay<OverlayItem>. Dans le constructeur, mettez en place la liste des instances OverlayItem et appelez populate() lorsquelles sont prtes tre utilises par la couche. Implmentez size() pour quelle renvoie le nombre dlments qui devront tre grs par la couche. Rednissez createItem() pour quelle renvoie linstance OverlayItem correspondant lindice qui lui est pass en paramtre.

Chapitre 34

Cartographie avec MapView et MapActivity

333

Lors de linstanciation de la sous-classe dItemizedOverlay, fournissez-lui un objet Drawable reprsentant licne par dfaut de chaque lment (une pinglette, par exemple).

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

334

Lart du dveloppement Android

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 an que la couche ajuste ce quelle afche. 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. La mthode onTap() dItemizedOverlay prend en paramtre lindice de lobjet OverlayItem sur lequel on a cliqu. Cest ensuite vous de traiter cet vnement. Dans le cas de la classe SitesOverlay que nous venons de prsenter, le code donTap() est le suivant :
@Override protected boolean onTap(int i) { Toast.makeText(NooYawk.this, items.get(i).getSnippet(), Toast.LENGTH_SHORT).show(); return(true); }

Ici, on lve simplement un Toast contenant le texte associ lOverlayItem et lon renvoie true pour indiquer que lon a gr le toucher de cet objet.

Moi et MyLocationOverlay
Android dispose dune couche intgre permettant de grer deux scnarios classiques :

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

Il vous suft pour cela de crer une instance de MyLocationOverlay, de lajouter la liste des couches de votre MapView et dactiver et de dsactiver ces fonctionnalits aux moments opportuns.

Chapitre 34

Cartographie avec MapView et MapActivity

335

La notion de "moments opportuns" est lie lconomie de la batterie. Comme il ny a aucune raison de mettre jour des emplacements ou des directions lorsque lactivit est en pause, il est conseill dactiver ces fonctionnalits dans onResume() et de les dsactiver dans onPause(). Pour que NooYawk afche une boussole dans MyLocationOverlay, par exemple, nous devons dabord crer la couche et lajouter la liste des couches :
me=new MyLocationOverlay(this, map); map.getOverlays().add(me);

Puis nous activons et dsactivons cette boussole lorsque cela est ncessaire :
@Override public void onResume() { super.onResume(); me.enableCompass(); } @Override public void onPause() { super.onPause(); me.disableCompass(); }

La cl de tout
Si vous compilez le projet NooYawk et que vous linstalliez dans votre mulateur, vous verrez srement un cran montrant une grille et deux pinglettes, mais pas de carte. La raison en est que la cl de lAPI dans le code source nest pas valide pour votre machine de dveloppement. Vous devez donc produire votre propre cl pour lutiliser avec votre application. Le site web 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 certicat utilis pour signer vos applications en mode debug (voir ci-aprs).
1. http://code.google.com/android/toolbox/apis/mapkey.html.

336

Lart du dveloppement Android

4. Sur la page dinscription pour la cl de lAPI, collez cette signature MD5 et envoyez le formulaire. 5. Sur la page de rponse, copiez la cl de lAPI et collez-la dans la valeur de lattribut android:apiKey du layout de votre MapView. La partie la plus complique consiste trouver la signature MD5 du certicat utilis pour signer vos applications en mode debug... et une bonne partie de cette complexit consiste comprendre le concept. Toutes les applications Android sont signes laide dune signature numrique produite partir dun certicat. Vous recevez automatiquement un certicat de dbogage lorsque vous installez le SDK et il faut suivre un autre processus pour crer un certicat autosign utilisable avec vos applications en production. Ce processus ncessite dutiliser les outils keytool et jarsigner de Java. Pour obtenir votre cl dAPI, vous navez besoin que de keytool. Si vous utilisez OS X ou Linux, faites la commande suivante pour obtenir la signature MD5 de votre certicat de dbogage :
keytool -list -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android -keypass android

Sur les autres plates-formes de dveloppement, vous devrez remplacer la valeur de -keystore par lemplacement sur votre machine et votre compte utilisateur :

Windows XP : C:\Documents et Settings\<utilisateur>\Local Settings\ApplicationData\Android\debug.keystore. Windows Vista : C:\Users\<utilisateur>\AppData\Local\Android\debug.keystore (o <utilisateur> est le nom de votre compte).

La seconde ligne du rsultat qui safche contient votre signature MD5, qui est une suite de paires de chiffres hexadcimaux spares par des caractres deux-points.

35
Gestion des appels tlphoniques
La plupart des terminaux Android, si ce nest tous, sont des tlphones. Leurs utilisateurs sattendent donc pouvoir tlphoner et recevoir des appels et, si vous le souhaitez, vous pouvez les y aider :

Vous pourriez crer une interface pour une application de gestion des ventes ( la Salesforce.com) en offrant la possibilit dappeler les vendeurs dun simple clic, sans que lutilisateur soit oblig de mmoriser ces contacts la fois dans lapplication et dans son rpertoire tlphonique. Vous pourriez dvelopper une application de rseau social avec une liste de numros de tlphone qui volue constamment : au lieu de "synchroniser" ces contacts avec ceux du tlphone, lutilisateur pourrait les appeler directement partir de cette application. Vous pourriez crer une interface personnalise pour le systme de contacts existants, ventuellement pour que les utilisateurs mobilit rduite (telles les personnes ges) puissent disposer de gros boutons pour faciliter la composition des appels.

Quoi quil en soit, Android vous permet de manipuler le tlphone comme nimporte quelle autre composante du systme.

338

Lart du dveloppement Android

Le Manager
Pour tirer le meilleur parti de lAPI de tlphonie, utilisez la classe TelephonyManager, qui permet notamment de :

dterminer si le tlphone est en cours dutilisation, via sa mthode getCallState(), qui renvoie les valeurs CALL_STATE_IDLE (tlphone non utilis), CALL_STATE_RINGING (appel en cours de connexion) et CALL_STATE_OFFHOOK (appel en cours) ; trouver lidentiant de la carte SIM avec getSubscriberId(); connatre le type du tlphone (GSM, par exemple) avec getPhoneType() ou celui de la connexion (comme GPRS, EDGE) avec getNetworkType().

Appeler
Pour effectuer un appel partir d'une application, en utilisant par exemple un numro que vous avez obtenu par votre propre service web, crez une intention ACTION_DIAL avec une Uri de la forme tel:NNNNN (o NNNNN est le numro de tlphone appeler) et utilisez cette intention avec startActivity(). Cela ne lancera pas l'appel, mais activera l'activit du combin, partir duquel l'utilisateur pourra alors appuyer sur un bouton pour effectuer l'appel. Voici, par exemple, un chier de disposition simple mais efcace, extrait du projet Phone/ Dialer :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Numero : " /> <EditText android:id="@+id/number" android:layout_width="fill_parent" android:layout_height="wrap_content"

Chapitre 35

Gestion des appels tlphoniques

339

android:cursorVisible="true" android:editable="true" android:singleLine="true" /> </LinearLayout> <Button android:id="@+id/dial" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1" android:text="Appeler !" /> </LinearLayout>

Nous utilisons simplement un champ de saisie pour entrer un numro de tlphone et un bouton pour appeler ce numro. Le code Java se contente de lancer le combin en utilisant le numro saisi dans le champ :
package com.commonsware.android.dialer; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; public class DialerDemo extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); final EditText number=(EditText)findViewById(R.id.number); Button dial=(Button)findViewById(R.id.dial); dial.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { String toDial="tel:" + number.getText().toString(); startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(toDial))); } }); } }

340

Lart du dveloppement Android

Comme le montre la Figure 35.1, linterface de cette activit nest pas trs impressionnante.
Figure 35.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 Figure 35.2.
Figure 35.2 Lactivit Dialer dAndroid lance partir de DialerDemo.

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

Cette fonctionnalit tant assez rcente dans Android, les API risquent dtre modies : surveillez les mises jour.

342

Lart du dveloppement Android

La chasse est ouverte


Android dispose de deux types de recherches : locales et globales. Les premires effectuent la recherche dans lapplication en cours tandis que les secondes utilisent le moteur de Google pour faire une recherche sur le Web. Chacune delles peut tre lance de diffrentes faons :

Vous pouvez appeler onSearchRequested() partir dun bouton ou dun choix de menu an de lancer une recherche locale (sauf si vous avez redni cette mthode dans votre activit). Vous pouvez appeler directement startSearch() pour lancer une recherche locale ou globale en fournissant ventuellement une chane de caractres comme point de dpart. Vous pouvez faire en sorte quune saisie au clavier dclenche une recherche locale avec setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL) ou globale avec setDefaultKeyMode(DEFAULT_KEYS_SEARCH_GLOBAL).

Dans tous les cas, la recherche apparat comme un ensemble de composants graphiques disposs en haut de lcran, votre activit apparaissant en ou derrire eux (voir Figures 36.1 et 36.2).
Figure 36.1 Les composants de la recherche locale dAndroid.

Chapitre 36

Recherches avec SearchManager

343

Figure 36.2 Les composants de la recherche globale dAndroid avec une liste droulante montrant les recherches prcdentes.

Recherches personnelles
terme, il existera deux variantes de recherches disponibles :

les recherches de type requte, o la chane recherche par lutilisateur est passe une activit qui est responsable de la recherche et de lafchage des rsultats ; les recherches de type ltre, o la chane recherche par lutilisateur est passe une activit chaque pression de touche et o lactivit est charge de mettre jour une liste des correspondances.

Cette dernire approche tant encore en cours de dveloppement, intressons-nous la premire.

Cration de lactivit de recherche


Pour quune application puisse proposer des recherches de type requte, la premire chose faire consiste crer une activit de recherche. Bien quil soit possible quune mme activit puisse tre ouverte partir du lanceur et partir dune recherche, il savre que cela trouble un peu les utilisateurs. En outre, utiliser une activit spare est plus propre dun point de vue technique. Lactivit de recherche peut avoir laspect que vous souhaitez. En fait, part examiner les requtes, elle ressemble, se comporte et rpond comme toutes les autres activits du systme. La seule diffrence est quune activit de recherche doit vrier les intentions

344

Lart du dveloppement Android

fournies onCreate() (via getIntent()) et onNewIntent() pour savoir si lune delles est une recherche, auquel cas elle effectue la recherche et afche le rsultat. Lapplication Search/Lorem, par exemple, commence comme un clone de lapplication du Chapitre 8, qui afchait une liste des mots pour dmontrer lutilisation du conteneur ListView. Ici, nous la modions pour pouvoir rechercher les mots qui contiennent une chane donne. Lactivit principale et lactivit de recherche partagent un layout form dune ListView et dun TextView montrant lentre slectionne :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:drawSelectorOnTop="false" /> </LinearLayout>

Lessentiel du code des activits se trouve dans une classe abstraite LoremBase :
abstract public class LoremBase extends ListActivity { abstract ListAdapter makeMeAnAdapter(Intent intent); private static final int LOCAL_SEARCH_ID = Menu.FIRST+1; private static final int GLOBAL_SEARCH_ID = Menu.FIRST+2; private static final int CLOSE_ID = Menu.FIRST+3; TextView selection; ArrayList<String> items=new ArrayList<String>(); @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); selection=(TextView)findViewById(R.id.selection); try { XmlPullParser xpp=getResources().getXml(R.xml.words); while (xpp.getEventType()!=XmlPullParser.END_DOCUMENT) {

Chapitre 36

Recherches avec SearchManager

345

if (xpp.getEventType()==XmlPullParser.START_TAG) { if (xpp.getName().equals("word")) { items.add(xpp.getAttributeValue(0)); } } xpp.next(); } } catch (Throwable t) { Toast .makeText(this, "Echec de la requete : " + t.toString(), 4000) .show(); } setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); onNewIntent(getIntent()); } @Override public void onNewIntent(Intent intent) { ListAdapter adapter=makeMeAnAdapter(intent); if (adapter==null) { finish(); } else { setListAdapter(adapter); } } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items.get(position).toString()); } @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, LOCAL_SEARCH_ID, Menu.NONE, "Recherche locale") .setIcon(android.R.drawable.ic_search_category_default); menu.add(Menu.NONE, GLOBAL_SEARCH_ID, Menu.NONE, "Recherche globale") .setIcon(R.drawable.search) .setAlphabeticShortcut(SearchManager.MENU_KEY); menu.add(Menu.NONE, CLOSE_ID, Menu.NONE, "Fermeture") .setIcon(R.drawable.eject) .setAlphabeticShortcut(f); return(super.onCreateOptionsMenu(menu)); }

346

Lart du dveloppement Android

@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case LOCAL_SEARCH_ID: onSearchRequested(); return(true); case GLOBAL_SEARCH_ID: startSearch(null, false, null, true); return(true); case CLOSE_ID: finish(); return(true); } return(super.onOptionsItemSelected(item)); } }

Cette activit prend en charge tout ce qui est li lafchage dune liste de mots, y compris lextraction des mots partir du chier XML. En revanche, elle ne fournit pas le ListAdapter placer dans la ListView cette tche est dlgue aux sous-classes. Lactivit principale LoremDemo utilise simplement un ListAdapter pour la liste de mots :
package com.commonsware.android.search; import android.content.Intent; import android.widget.ArrayAdapter; import android.widget.ListAdapter; public class LoremDemo extends LoremBase { @Override ListAdapter makeMeAnAdapter(Intent intent) { return(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items)); } }

Lactivit de recherche, cependant, fonctionne un peu diffremment. Elle commence par inspecter lintention fournie la mthode makeMeAnAdapter() ; cette intention provient soit donCreate(), soit donNewIntent(). Sil sagit dACTION_SEARCH, on sait que cest une recherche : on peut donc rcuprer la requte et, dans le cas de notre exemple stupide, drouler la liste des mots chargs pour ne conserver que ceux qui contiennent la chane recherche. La liste ainsi obtenue est ensuite enveloppe dans un ListAdapter que lon renvoie pour quil soit afch :
package com.commonsware.android.search; import android.app.SearchManager; import android.content.Intent;

Chapitre 36

Recherches avec SearchManager

347

import android.widget.ArrayAdapter; import android.widget.ListAdapter; import java.util.ArrayList; import java.util.List; public class LoremSearch extends LoremBase { @Override ListAdapter makeMeAnAdapter(Intent intent) { ListAdapter adapter=null; if (intent.getAction().equals(Intent.ACTION_SEARCH)) { String query=intent.getStringExtra(SearchManager.QUERY); List<String> results=searchItems(query); adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, results); setTitle("LoremSearch de : " + query); } return(adapter); } private List<String> searchItems(String query) { List<String> results=new ArrayList<String>(); for (String item : items) { if (item.indexOf(query)>-1) { results.add(item); } } return(results); } }

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

348

Lart du dveloppement Android

<meta-data android:name="android.app.default_searchable" android:value=".LoremSearch" /> </activity> <activity android:name=".LoremSearch" android:label="LoremSearch" android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.SEARCH" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" /> </activity> </application> </manifest>

Les modications ncessaires sont les suivantes : 1. Lactivit LoremDemo reoit un lment meta-data avec un attribut android:name valant android.app.default_searchable et un attribut android:value contenant la classe qui implmente la recherche (.LoremSearch). 2. Lactivit LoremSearch reoit un ltre dintention pour android.intent.action.SEARCH, an que les intentions de recherche puissent tre slectionnes. 3. Lactivit LoremSearch reoit lattribut android:launchMode = "singleTop", ce qui signie quune seule instance de cette activit sera ouverte un instant donn, an dviter que tout un lot de petites activits de recherche encombre la pile des activits. 4. Lactivit LoremSearch reoit un lment meta-data dot dun attribut android:name valant android.app.searchable et dun attribut android:value pointant vers une ressource XML contenant plus dinformations sur la fonctionnalit de recherche offerte par lactivit (@xml/searchable).
<searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/searchLabel" android:hint="@string/searchHint" />

Actuellement, cette ressource XML fournit deux informations :

le nom qui doit apparatre dans le bouton du domaine de recherche droite du champ de saisie, an dindiquer lutilisateur lendroit o il recherche (android:label) ; le texte qui doit apparatre dans le champ de saisie, an de donner lutilisateur un indice sur ce quil doit taper (android:hint).

Chapitre 36

Recherches avec SearchManager

349

Effectuer une recherche


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

Vous pouvez obtenir le mme effet en commenant taper dans lactivit principale car elle est congure pour dclencher une recherche locale.

37
Outils de dveloppement
Le SDK Android nest pas quune bibliothque de classes Java et dAPI, il contient galement un certain nombre doutils permettant de faciliter le dveloppement des applications. Nous avons surtout voqu le plug-in Eclipse, qui intgre le processus de dveloppement Android dans cet IDE et nous avons galement cit les plug-in quivalents des autres environnements, ainsi que les outils en ligne de commande, comme adb, qui permet de communiquer avec lmulateur. Dans ce chapitre, nous nous intresserons aux autres outils.

Gestion hirarchique
Android est fourni avec un outil permettant de visualiser une hirarchie, conu pour vous aider consulter vos layouts tels quils sont vus par une activit en cours dexcution dans un mulateur. Vous pouvez ainsi savoir lespace quoccupe un widget ou trouver un widget particulier. Pour utiliser cet outil, vous devez dabord lancer lmulateur, installer votre application, lancer lactivit et naviguer vers lendroit que vous souhaitez examiner. Comme le montre

352

Lart du dveloppement Android

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

Figure 37.2 Fentre principale du visualisateur hirarchique.

Chapitre 37

Outils de dveloppement

353

La liste gauche montre les diffrents mulateurs que vous avez chargs. Le nombre aprs le tiret devrait correspondre au nombre entre parenthses situ dans la barre de titre de lmulateur. Si vous cliquez sur un mulateur, la liste des fentres accessibles apparat droite, comme le montre la Figure 37.3.

Figure 37.3 Hirarchie des fentres disponibles.

Vous remarquerez quoutre lactivit ouverte apparaissent de nombreuses autres fentres, dont celle du lanceur (lcran daccueil), celle du "Keyguard" (lcran noir "Appuyez sur Menu pour dverrouiller le tlphone" qui apparat lorsque vous ouvrez lmulateur pour la premire fois), etc. Votre activit est identie par le nom du paquetage et de la classe de lapplication (com.commonsware.android.files/..., ici). Les choses commencent devenir intressantes lorsque vous slectionnez lune de ces fentres et que vous cliquez sur le bouton "Load View Hierarchy". Aprs quelques secondes, les dtails apparaissent dans une fentre appele "Layout View" (voir Figure 37.4). La zone principale de cette "Layout View" est occupe par une arborescence des 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 Figure 37.5.

354

Lart du dveloppement Android

Figure 37.4 Layout View de lapplication ReadWrite FileDemo.

Figure 37.5 Afchage des proprits dune vue.

Chapitre 37

Outils de dveloppement

355

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

Figure 37.6 Visualisateur hirarchique en mode "Pixel Perfect View".

La partie gauche contient une reprsentation arborescente des widgets et des autres vues de votre activit. Au milieu se trouve votre activit ("Normal View") et, sur la droite, vous pouvez voir une version zoome ("Loupe View") de celle-ci.

356

Lart du dveloppement Android

Il faut bien comprendre que cette visualisation est en direct : lactivit est interroge selon la frquence choisie par le curseur "Refresh Rate". Tout ce que vous faites avec cette activit se retera donc dans les vues "Normal" et "Loupe" de la fentre "Pixel Perfect View". Les lignes nes de couleur cyan places au-dessus de lactivit montrent la position sur laquelle le zoom sapplique il suft de cliquer sur une nouvelle zone pour changer lendroit inspect par la "Loupe View". Un autre curseur permet de rgler la puissance du grossissement.

DDMS (Dalvik Debug Monitor Service)


Lautre outil de larsenal du dveloppeur Android sappelle DDMS (Dalvik Debug Monitor Service). Cest une sorte de "couteau suisse" qui vous permet de parcourir les chiers journaux, de modier la position GPS fournie par lmulateur, de simuler la rception dappels et de SMS et de parcourir le contenu de lmulateur pour y placer ou en extraire des chiers. Nous ne prsenterons ici que les fonctionnalits les plus utiles. Pour utiliser DDMS, lancez le programme ddms qui se trouve dans le rpertoire tools/ de votre installation du SDK. Au dpart, vous ne verrez dans la partie gauche quune arborescence des mulateurs avec les programmes quils excutent (voir Figure 37.7).
Figure 37.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 Figure 37.8).

Chapitre 37

Outils de dveloppement

357

Figure 37.8 mulateur slectionn dans DDMS.

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

358

Lart du dveloppement Android

Stockage et extraction de chiers


Bien que vous puissiez utiliser adb pull et adb push pour, respectivement, extraire ou stocker des chiers sur un mulateur ou un terminal, DDMS permet de le faire de faon plus visuelle. Il suft, pour cela, de slectionner lmulateur ou le terminal concern, puis de choisir loption Device > File Explorer... partir du menu principal : une fentre de dialogue typique comme celle de la Figure 37.10 permet alors de parcourir larborescence des chiers.
Figure 37.10 Explorateur de chiers de DDMS.

Slectionnez simplement le chier concern et cliquez sur le bouton dextraction ( gauche) ou de stockage (au milieu) de la barre doutils pour le transfrer vers ou partir de votre machine de dveloppement. Le bouton de suppression ( droite) permet de supprimer le chier slectionn.
Info

Cet outil ne permet pas de crer de rpertoire : pour cela, vous devez soit utiliser la commande adb shell, soit les crer partir de votre application. Bien que vous puissiez parcourir la plupart des chiers dun mulateur, les restrictions de scurit dAndroid limitent beaucoup laccs en dehors de larborescence /sdcard sur un vrai terminal.

Copies dcran
Pour faire une copie dcran de lmulateur ou dun terminal Android, faites simplement Ctrl+S ou choisissez Device > Screen capture... dans le menu principal. Ceci ouvrira une bote de dialogue contenant une image de lcran courant, comme la Figure 37.11. partir de l, vous pouvez cliquer sur "Save" pour sauvegarder limage au format PNG sur votre machine de dveloppement, rafrachir limage partir de ltat courant de lmulateur ou du terminal, ou cliquer sur "Done" pour fermer la bote de dialogue.

Chapitre 37

Outils de dveloppement

359

Figure 37.11 Capture dcran avec DDMS.

Mise jour de la position


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

360

Lart du dveloppement Android

Lutilisation de longlet "Manual" est assez vidente : on fournit une latitude et une longitude et lon clique sur le bouton "Send" pour envoyer cet emplacement lmulateur. Ce dernier, son tour, prviendra les couteurs de localisation de la nouvelle position. La prsentation des formats GPX et KML sort du cadre de ce livre.

Appels tlphoniques et SMS


DDMS sait galement simuler la rception dappels tlphoniques et de SMS via le groupe "Telephony Actions" de longlet "Emulator Control" (voir Figure 37.13).

Figure 37.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 Figure 37.14, lmulateur afchera lappel entrant et vous demandera si vous lacceptez (avec le bouton vert du tlphone) ou si vous le rejetez (avec le bouton rouge). Pour simuler la rception dun SMS, saisissez un numro tlphonique, cochez le bouton radio "SMS", saisissez un message dans la zone de texte et cliquez sur "Send". Le message apparatra sous la forme dune notication, comme la Figure 37.15. En cliquant sur la notication, vous pourrez voir le contenu intgral du message, comme le montre la Figure 37.16.

Chapitre 37

Outils de dveloppement

361

Figure 37.14 Simulation de la rception dun appel.

Figure 37.15 Simulation de la rception dun SMS.

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

362

Lart du dveloppement Android

Gestion des cartes amovibles


Le G1 dispose dun emplacement pour carte microSD et de nombreux autres terminaux Android disposent dune forme similaire de stockage amovible, dsign de faon gnrique par le terme "Carte SD". Les cartes SD servent stocker les gros chiers, comme les images, les clips vido, les chiers musicaux, etc. La mmoire interne du G1, notamment, est relativement peu importante et il est prfrable de stocker un maximum de donnes sur une carte SD. Bien que le G1 ait une carte SD par dfaut, le problme, videmment, est que lmulateur nen a pas. Pour que ce dernier se comporte comme le G1, vous devez donc crer et "insrer" une carte SD dans lmulateur.

Cration dune image de carte


Au lieu dexiger que les mulateurs aient accs un vrai lecteur de carte SD pour utiliser de vraies cartes, Android est congur pour utiliser des images de cartes. Une image est simplement un chier que lmulateur traitera comme sil sagissait dun volume de carte SD : il sagit en fait du mme concept que celui utilis par les outils de virtualisation (comme VirtualBox) Android utilise une image disque pour reprsenter le contenu dune carte SD. Pour crer cette image, utilisez le programme mksdcard qui se trouve dans le rpertoire tools/ de votre installation du SDK. Ce programme attend au moins deux paramtres : 1. La taille de limage et donc de la "carte". Si vous fournissez un nombre, celui-ci sera interprt comme un nombre doctets. Vous pouvez galement le faire suivre de K ou M pour prciser que cette taille est, respectivement, exprime en kilo-octets ou en mgaoctets. 2. Le nom du chier dans lequel stocker limage. Pour, par exemple, crer limage dune carte SD de 1 Go an de simuler celle du GI dans lmulateur, faites :
mksdcard 1024M sdcard.img

Insertion de la carte
Pour que lmulateur utilise cette image de carte, lancez-le avec loption -sdcard suivie du chemin complet vers le chier image cr avec mksdcard. Bien que cette option nait pas deffet visible aucune icne dAndroid ne montrera quune carte est monte , le rpertoire /sdcard sera dsormais accessible en lecture et en criture. Pour placer et lire des chiers dans /sdcard, utilisez lexplorateur de chiers de DDMS ou les commandes adb push et adb pull partir de la console.

38
Pour aller plus loin
Ce livre ne couvre videmment pas tous les sujets possibles et, bien que la ressource numro un (en dehors de cet ouvrage) soit la documentation du SDK Android, vous aurez srement besoin dinformations qui se trouvent ailleurs. Une recherche web sur le mot "android" et un nom de classe est un bon moyen de trouver des didacticiels pour une classe donne. Cependant, noubliez pas que les documents crits avant n 2008 concernent probablement le SDK M5 et ncessiteront donc des modications trs importantes pour fonctionner correctement avec les SDK actuels. Ce chapitre vous donnera donc quelques pistes explorer.

Questions avec, parfois, des rponses


Les groupes Google consacrs Android sont les endroits ofciels pour obtenir de laide. Trois groupes sont consacrs au SDK :

Android Beginners1 est le meilleur endroit pour poster des questions de dbutant.

1. http://groups.google.com/group/android-beginners.

364

Lart du dveloppement Android

Android Developers1 est consacr aux questions plus compliques ou celles qui relvent de parties plus exotiques du SDK. Android Discuss2 est rserv aux discussions btons rompus sur tout ce qui est li Android, pas ncessairement aux problmes de programmation.

Vous pouvez galement consulter :


les didacticiels et les forums consacrs Android sur le site anddev.org 3 ; le canal IRC #android sur freenode.

Aller la source
Le code source dAndroid est dsormais disponible, essentiellement pour ceux qui souhaitent amliorer le systme ou jouer avec ses dtails internes. Toutefois, vous pouvez aussi y trouver les rponses que vous recherchez, notamment si vous voulez savoir comment fonctionne un composant particulier. Le code source et les ressources qui y sont lies se trouvent sur le site web du projet Android4. partir de ce site, vous pouvez :

tlcharger5 ou parcourir6 le code source ; signaler des bogues7 du systme lui-mme ; proposer des patchs8 et apprendre comment ces patchs sont valus et approuvs ; rejoindre un groupe Google particulier9 pour participer au dveloppement de la plateforme Android.

Citons galement quelques ressources francophones :

les sites www.frandroid.com et www.pointgphone.com, qui proposent des articles et des forums de discussion. groups.google.com/group/android-fr, qui est un groupe Google francophone consacr Android.
http://groups.google.com/group/android-developers. http://groups.google.com/group/android-discuss. http://anddev.org/. http://source.android.com. http://source.android.com/download. http://git.source.android.com/. http://source.android.com/report-bugs. http://source.android.com/submit-patches. http://source.android.com/discuss.

1. 2. 3. 4. 5. 6. 7. 8. 9.

Chapitre 38

Pour aller plus loin

365

Lire les journaux


Ed Burnette, qui a crit son propre livre sur Android, est galement le gestionnaire de Planet Android1, un agrgateur de ux pour un certain nombre de blogs consacrs Android. En vous abonnant ce ux, vous pourrez ainsi surveiller un grand nombre darticles, pas ncessairement consacrs la programmation. Pour surveiller plus prcisment les articles lis la programmation dAndroid, faites une recherche sur le mot-cl "android" sur Dzone ; vous pouvez galement vous abonner un ux2 qui rsume cette recherche.

1. http://www.planetandroid.com/. 2. http://www.dzone.com/links/feed/search/android/rss.xml.

Index

A
aapt, outil 30 AbsoluteLayout, conteneur 128 Acclromtres 265 Activity, classe 25, 165, 272 activity, lment 272 activity, lment du manifeste 15 ActivityAdapter, adaptateur 67 ActivityAdapter, classe 264 activityCreator, script 203 ActivityIconAdapter, adaptateur 67 ActivityManager, classe 161 Adaptateur 65 adb, programme 224, 351, 357, 358, 362 add(), mthode 130, 332 addId(), mthode 280 addIntentOptions(), mthode 131, 263, 264 addMenu(), mthode 131 addProximityAlert(), mthode 325 addSubMenu(), mthode 131 addTab(), mthode 116 AlertDialog, classe 156 AnalogClock, widget 111 android alphabeticShortcut, attribut 140 apiKey, attribut 329, 336

authorities, attribut 295 autoText, attribut de widget 38 background, attribut 44 capitalize, attribut de widget 38 clickable, attribut 329 collapseColumns, proprit 59 columnWidth, proprit 74 completionThreshold, proprit 77 configChanges, attribut 272 content, attribut 127 digits, attribut de widget 38 drawSelectorOnTop, proprit 71, 81 ellipsize, attribut 145 enabled, attribut 139 handle, attribut 127 hint, attribut 348 horizontalSpacing, proprit 74 icon, attribut 139 id, attribut 114, 138, 329 id, attribut de main.xml 31 inputMethod, attribut de widget 38 label, attribut 348 launchMode, attribut 348 layout_above, proprit 54 layout_alignBaseline, proprit 54 layout_alignBottom, proprit 54

368

Lart du dveloppement Android

android (suite) layout_alignLeft, proprit 54 layout_alignParentBottom, proprit 53 layout_alignParentLeft, proprit 53 layout_alignParentRight, proprit 53 layout_alignParentTop, proprit 53, 56 layout_alignRight, proprit 54 layout_alignTop, proprit 54 layout_below, proprit 54 layout_centerHorizontal, proprit 53 layout_centerInParent, proprit 53 layout_centerVertical, proprit 53 layout_column, proprit 58 layout_gravity, proprit 47 layout_height, attribut 114 layout_span, proprit 58 layout_toLeftOf, proprit 54 layout_toRightOf, proprit 54 layout_weight, proprit 47 layout_width, attribut de main.xml 31 layout_width, proprit 46, 55 menuCategory, attribut 139 name, attribut 295, 298, 306, 348 name, attribut du manifeste 15 nextFocusDown, attribut 43 nextFocusLeft, attribut 43 nextFocusRight, attribut 43 nextFocusUp, attribut 43 numColumns, proprit 74 numeric, attribut de widget 38 numericShortcut, attribut 140 orderInCategory, attribut 139 orientation, proprit 46 padding, proprit 47 paddingLeft, proprit 48 paddingTop, proprit 114 password, attribut de widget 38 permission, attribut 300, 306 phoneNumber, attribut de widget 38 readPermission, attribut 300 screenOrientation, attribut 274 shrinkColumns, proprit 59 singleLine, attribut de widget 38

spacing, proprit 81 spinnerSelector, proprit 81 src, attribut de widget 37 stretchColumns, proprit 59 stretchMode, proprit 74 text, attribut de main.xml 31 text, attribut de widget 36 typeface, attribut 143 value, attribut 348 versionCode, attribut du manifeste 17 verticalSpacing, proprit 74 visibility, attribut 44 visible, attribut 139 writePermission, attribut 300 Android Scripting Environment (ASE) 233 android, paquetage 25 android, script 22, 23 AndroidManifest 182 AndroidManifest.xml, fichier 9, 13, 148, 295, 298, 306, 347 animateClose(), mthode 128 animateOpen(), mthode 128 animateToggle(), mthode 128 Animations 124 apk, fichier 11 appendWhere(), mthode 223 application, lment 295, 306 application, lment du manifeste 14 Arborescence de rpertoires 9 ArrayAdapter, adaptateur 66, 69, 85, 96 ArrayAdapter, classe 169, 194 ArrayList, classe 194 AssetManager, classe 143 AsyncTask, classe 166, 307 AutoCompleteTextView, widget 39, 77 Auto-compltion 77 AVD (Android Virtual Device) 22

B
BaseColumns, interface 295 BeanShell, programme 229 beforeTextChanged(), mthode 79

Index

369

bindView(), mthode 105 BLOB (Binary Large Object) 286 BroadcastReceiver, classe 251, 311 BroadcastReceiver, interface 247 build.xml, fichier 10 Builder, classe 156 buildQuery(), mthode 223 bulkInsert(), mthode 285 Bundle, classe 176, 252, 266, 271 Bundle, objet 174 Button, widget 36

convertView, paramtre de getView() 89 create(), mthode 157 createDatabase(), mthode 225 createFromAsset(), mthode 143 createItem(), mthode 332 createTabContent(), mthode 118 Criteria, classe 323 Cursor, classe 221 Cursor, interface 281, 290 Cursor, widget 67 CursorAdapter, adaptateur 67, 105 CursorFactory, classe 224

C
Calendar, classe 110 cancel(), mthode 314 cancelAll(), mthode 314 canGoBack(), mthode 151 canGoBackOrForward(), mthode 151 canGoForward(), mthode 151 Catgories d'activits 244 check(), mthode 42 CheckBox, widget 40 CheckBoxPreference, lment 181 checkCallingPermission(), mthode 301 clear(), mthode 180 clearCache(), mthode 151 clearCheck(), mthode 42 clearHistory(), mthode 151 close(), mthode 128, 195, 219, 224 color, lment 211 commit(), mthode 180 ComponentName, classe 264 CompoundButton, widget 42 ContentManager, classe 314 ContentObserver, classe 296 ContentProvider, classe 289 ContentProvider, interface 285 ContentResolver, classe 285, 296 ContentValues, classe 220, 285, 291, 292 Context, classe 179, 195 ContextMenuInfo, classe 132

D
DatabaseHelper, classe 289 DateFormat, classe 110 DatePicker, widget 108 DatePickerDialog, widget 108 DDMS (Dalvik Debug Monitor Service) 326 ddms, programme 356 default.properties, fichier 10 DefaultHttpClient, classe 236 delete(), mthode 220, 285, 293 dex, programme 229 DialogWrapper, classe 285 DigitalClock, widget 111 dimen, lment 211 doInBackground(), mthode 167 draw(), mthode 333 Drawable, classe 333 Drawable, interface 205

E
edit(), mthode 180 EditText, widget 38 EditTextPreference, lment 188 Espace de noms 14 execSQL(), mthode 219 execute(), mthode 166, 171, 236 ExpandableListView, classe 128

370

Lart du dveloppement Android

F
fill_parent, valeur de remplissage 46 findViewById (), mthode 32 findViewById() 91 findViewById(), mthode 44, 89, 116, 191, 330 finish(), mthode 175, 197 Forecast, classe 239, 308 format(), mthode 201 FrameLayout, conteneur 114, 121 fromHtml(), mthode 202

G
Gallery, widget 81 GeoPoint, classe 331 getAltitude(), mthode 323 getAsInteger(), mthode 220 getAssets(), mthode 143 getAsString(), mthode 220 getAttributeCount(), mthode 209 getAttributeName(), mthode 209 getBearing(), mthode 323 getBestProvider(), mthode 323 getBoolean(), mthode 180 getCallState(), mthode 338 getCheckedItemPositions(), mthode 71 getCheckedRadioButtonId(), mthode 42 getColor(), mthode 211 getColumnIndex(), mthode 224 getColumnNames(), mthode 224 getContentProvider(), mthode 285 getContentResolver(), mthode 296 getCount(), mthode 223 getDefaultSharedPreferences(), mthode 180 getDimen(), mthode 211 getFloat(), mthode 284 getInputStream(), mthode 286 getInt(), mthode 224, 284 getIntent(), mthode 344

getItemId(), mthode 131 getLastKnownPosition(), mthode 323 getLastNonConfigurationInstance(), mthode 270 getLatitude(), mthode 237 getListView(), mthode 69 getLongitude(), mthode 237 getMapController(), mthode 330 getMenuInfo(), mthode 132 getNetworkType(), mthode 338 getOutputStream(), mthode 286 getOverlays(), mthode 332 getPackageManager(), mthode 264 getParent(), mthode 44 getPhoneType(), mthode 338 getPosition(), mthode 284 getPreferences(), mthode 179 getProgress(), mthode 113 getProviders(), mthode 323 getReadableDatabase(), mthode 219 getRequiredColumns(), mthode 292 getResources(), mthode 191, 194, 208 getRootView(), mthode 44 getSettings(), mthode 149, 153 getSharedPreferences(), mthode 179 getSpeed(), mthode 323 getString(), mthode 201, 224, 284 getStringArray(), mthode 212 getSubscriberId(), mthode 338 getSystemService(), mthode 314, 322 getTableName(), mthode 223 getTag(), mthode 91, 97 getType(), mthode 294 getView(), mthode 66, 86, 89, 105, 283 getWriteableDatabase(), mthode 219 getXml(), mthode 207 goBack(), mthode 151 goBackOrForward(), mthode 151 goForward(), mthode 151 GPS (Global Positioning System) 321 group, lment 138

Index

371

H
handleMessage(), mthode 162 Handler, classe 162, 311 hasAltitude(), mthode 323 hasBearing(), mthode 323 hasSpeed(), mthode 323 hierarchyviewer, programme 352 htmlEncode(), mthode 204 HttpClient, classe 304 HttpClient, interface 236 HttpComponents, bibliothque 236 HttpGet, classe 236 HttpPost, classe 236 HttpRequest, interface 236 HttpResponse, classe 236

ItemizedOverlay, classe 332 Items, classe 264 Iterator, interface 284

J
JavaScript, et WebView 149 JRuby, langage de script 233 Jython, langage de script 233

K
keytool, utilitaire 336

L
LayoutInflater, classe 87, 103 LinearLayout, conteneur 46, 84, 97, 103 ListActivity, classe 67, 168 ListAdapter, adaptateur 100 ListPreference, lment 188 ListView, widget 67, 83 loadData(), mthode 150 loadUrl(), mthode 148 Localisation (L10N) 200, 212 Location, classe 237, 323 LocationListener, classe 324 LocationManager, classe 304, 322 LocationProvider, classe 322 lock(), mthode 128

I
IBinder, classe 305 ImageView, classe 286 ImageView, widget 37 incrementProgressBy(), mthode 113 indiquant 182 InputMethod, interface 38 InputStream, classe 191, 195, 239 InputStreamReader, classe 195 insert(), mthode 220, 285, 291 instrumentation, lment du manifeste 14 Intent, classe 244 intent-filter, lment 245 Internationalisation (I18N) 200, 212 Interpreter, classe de BeanShell 229 isAfterLast(), mthode 223, 284 isBeforeFirst(), mthode 284 isChecked(), mthode 40 isCollectionUri(), mthode 292, 293 isEnabled(), mthode 44 isFirst(), mthode 284 isLast(), mthode 284 isNull(), mthode 284 isRouteDisplayed(), mthode 330 item, lment 138, 212

M
makeText(), mthode 156 managedQuery(), mthode 281 manifest, lment 298 manifest, lment racine du manifeste 14 MapActivity, classe 327, 328 MapView, classe 327, 328 Menu, classe 130, 140, 263 menu, lment 138 MenuInflater, classe 140 MenuItem, classe 130 Message, classe 162

372

Lart du dveloppement Android

meta-data, lment 348 mthode 91 MIME, types 288 mksdcard, programme 362 move(), mthode 284 moveToFirst(), mthode 223, 284 moveToLast(), mthode 284 moveToNext(), mthode 223, 284 moveToPosition(), mthode 284 moveToPrevious(), mthode 284 MyLocationOverlay, classe 334

N
name, attribut 200, 211, 212 newCursor(), mthode 224 newTabSpec(), mthode 115 newView(), mthode 105 next(), mthode 208 Notification, classe 172 NotificationManager, classe 314 notify(), mthode 314 notifyChange(), mthode 296

O
obtainMessage(), mthode 162 onActivityResult(), mthode 251, 260 onBind(), mthode 305 OnCheckedChangeListener, interface 50 onClick(), mthode 26 OnClickListener(), mthode 158 OnClickListener, classe 110, 253 OnClickListener, interface 26 onConfigurationChanged(), mthode 272 onContextItemSelected(), mthode 132, 134 onCreate(), mthode 26, 134, 174, 176, 184, 194, 219, 266, 282, 289, 304, 344, 346 onCreateContextMenu(), mthode 132, 134 onCreateOptionsMenu(), menu 134 onCreateOptionsMenu(), mthode 130, 131

onCreatePanelMenu(), mthode 131 OnDateChangedListener, classe 108 OnDateSetListener, classe 108 onDestroy(), mthode 174, 304 OnItemSelectedListener, interface 72 onListItemClick(), mthode 69, 97 onLocationChanged(), mthode 324 onNewIntent(), mthode 344, 346 onOptionsItemSelected(), mthode 130, 131, 134 onPageStarted(), mthode 151 onPause(), mthode 175, 197, 247, 304, 311, 335 onPostExecute(), mthode 167 onPreExecute(), mthode 167 onPrepareOptionsMenu(), mthode 130 onProgressUpdate(), mthode 168 onRatingChanged(), mthode 97 onReceive(), mthode 247 onReceivedHttpAuthRequest(), mthode 151 onRestart(), mthode 175 onRestoreInstanceState(), mthode 176, 266 onResume(), mthode 175, 184, 197, 247, 304, 311, 335 onRetainNonConfigurationInstance(), mthode 270 onSaveInstanceState(), mthode 174, 176, 248, 266, 267 onSearchRequested(), mthode 342, 349 onStart(), mthode 164, 175, 304 onStop(), mthode 175 onTap(), mthode 334 onTextChanged(), mthode 79 OnTimeChangedListener, classe 108 OnTimeSetListener, classe 108 onTooManyRedirects(), mthode 151 onUpgrade(), mthode 219 open(), mthode 128 openFileInput(), mthode 195, 197 openFileOutput(), mthode 195, 197 openRawResource(), mthode 191, 194 OpenStreetMap, cartographie 328 OutputStream, classe 195

Index

373

OutputStreamWriter, classe 195 Overlay, classe 332 OverlayItem, classe 332

P
package, attribut du manifeste 14 PackageManager, classe 264 parse(), mthode 280, 281 PendingIntent, classe 314, 325 permission, lment 299 permission, lment du manifeste 14 populate(), mthode 332 populateDefaultValues(), mthode 292 post(), mthode 165 postDelayed(), mthode 165, 311 PreferenceCategory, lment 185 PreferenceScreen, lment 181, 185 PreferencesManager, classe 180 ProgressBar, widget 113, 163 provider, lment 295 provider, lment du manifeste 16 publishProgress(), mthode 168

RelativeLayout, conteneur 52 reload(), mthode 151 remove(), mthode 180 removeProximityAlert(), mthode 325 removeUpdates(), mthode 325 requery(), mthode 224, 285 requestFocus(), mthode 44 requestLocationUpdates(), mthode 324 Resources, classe 191, 207 resources, lment 210 ResponseHandler, classe 236 ressources, lment 200 RingtonePreference, lment 181 RowModel, classe 96 Runnable, classe 165 runOnUiThread(), mthode 165

S
ScrollView, conteneur 61 SecurityException, exception 298 sendBroadcast(), mthode 251, 301, 308 sendMessage(), mthode 162 sendMessageAtFrontOfQueue(), mthode 162 sendMessageAtTime(), mthode 162 sendMessageDelayed(), mthode 162 sendOrderedBroadcast(), mthode 251 Service, classe 304 service, lment 306 service, lment du manifeste 16 setAccuracy(), mthode 323 setAdapter(), mthode 67, 71 setAlphabeticShortcut(), mthode 131 setAltitudeRequired(), mthode 323 setBuiltInZoomControls(), mthode 331 setCenter(), mthode 331 setCheckable(), mthode 131 setChecked(), mthode 40, 43 setChoiceMode(), mthode 69 setColumnCollapsed(), mthode 59 setColumnStretchable(), mthode 59 setContent(), mthode 115, 118 setContentView(), mthode 32

Q
query(), mthode 221, 290 queryIntentActivityOptions(), mthode 264 queryWithFactory(), mthode 224

R
R.java, fichier 10 RadioButton, widget 42 RadioGroup, widget 42 RatingBar, widget 94 rawQuery(), mthode 221 rawQueryWithFactory(), mthode 224 receiver, lment 247 receiver, lment du manifeste 16 registerContentObserver(), mthode 296 registerForContextMenu(), mthode 131 registerReceiver(), mthode 247

374

Lart du dveloppement Android

setCostAllowed(), mthode 323 setCurrentTab(), mthode 116 setDefaultFontSize(), mthode 153 setDefaultKeyMode(), mthode 342 setDropDownViewResource(), mthode 71, 72 setDuration(), mthode 156 setEnabled(), mthode 139 setFantasyFontFamily(), mthode 153 setFlipInterval(), mthode 125 setGravity(), mthode 47 setGroupCheckable(), mthode 130, 131 setGroupEnabled(), mthode 139 setGroupVisible(), mthode 139 setIcon(), mthode 157 setImageURI(), mthode 37 setIndeterminate(), mthode 113 setIndicator(), mthode 115 setJavaScriptCanOpenWindowsAutomatically(), mthode 153 setJavaScriptEnabled(), mthode 149, 153 setLatestEventInfo(), mthode 315 setListAdapter(), mthode 69 setMax(), mthode 113, 164 setMessage(), mthode 156 setNegativeButton(), mthode 157 setNeutralButton(), mthode 157 setNumericShortcut(), mthode 131 setOnCheckedChanged(), mthode 41 setOnCheckedChangeListener(), mthode 41 setOnClickListener(), mthode 26, 197 setOnItemSelectedListener(), mthode 67, 71 setOrientation(), mthode 46 setProgress(), mthode 113 setProjectionMap(), mthode 223 setQwertyMode(), mthode 131 setResult(), mthode 252 setTables(), mthode 223 setTag(), mthode 91, 97 setText(), mthode 27 setTextSize(), mthode 153 setTitle(), mthode 157 setTypeface(), mthode 143

setup(), mthode 116 setUserAgent(), mthode 154 setView(), mthode 156 setVisible(), mthode 139 setWebViewClient(), mthode 151 setZoom(), mthode 330 SharedPreferences, classe 180, 188 shouldOverrideUrlLoading(), mthode 151 show(), mthode 156 showNext(), mthode 122 SimpleAdapter, adaptateur 67 SimpleCursorAdapter, classe 282 Singleton, patron de conception 305 size(), mthode 332 SlidingDrawer, widget 126 SOAP, protocole 236 SoftReference, classe 307 Spanned, interface 201 Spinner, widget 71 SQLite Manager, extension Firefox 225 sqlite3, programme 224 SQLiteDatabase, classe 219 SQLiteOpenHelper, classe 219 SQLiteQueryBuilder, classe 221, 290 SSL et HttpClient 240 startActivity(), mthode 251, 338 startActivityForResult(), mthode 251, 260 startFlipping(), mthode 125 startSearch(), mthode 342, 349 startService(), mthode 310 stopService(), mthode 310 string, lment 200 string-array, lment 212 SystemClock, classe 162

T
TabActivity, classe 114, 255 TabContentFactory(), mthode 118 TabHost, classe 256 TabHost, conteneur 113 TableLayout, conteneur 57, 184 TableRow, conteneur 57

Index

375

TabSpec, classe 115 TabView, conteneur 255 TabWidget, widget 114 TelephonyManager, classe 338 TextView, widget 35, 67, 76, 85, 143 TextWatcher, interface 77 TimePicker, widget 108 TimePickerDialog, widget 108 Toast, classe 156 toggle(), mthode 40, 128 toggleSatellite(), mthode 331 TrueType, polices 143 Typeface, classe 143

uses-permission, lment 298 uses-sdk, lment du manifeste 14, 16

V
Versions du SDK 16 View, classe 26 View, widget 59, 86, 87 ViewFlipper, conteneur 120 Virus 297

W
WeakReference, classe 307 WebKit, widget 201, 235 WebSettings, classe 153 WebView, widget 147 WebViewClient, classe 151 wrap_content, valeur de remplissage 46

U
unlock(), mthode 128 unregisterContentObserver(), mthode 296 unregisterReceiver(), mthode 247 update(), mthode 220, 292 uptimeMillis(), mthode 162 Uri, classe 244, 260, 279, 338 uses-library, lment du manifeste 14

X
XmlPullParser, classe 208 XML-RPC, protocole 236

Lart du dveloppement

Android
laide dexemples simples et faciles excuter, apprenez dvelopper des applications pour terminaux Android. Smartphones, PDA et autres terminaux mobiles connaissent aujourdhui une vritable explosion. Dans ce contexte, Android, le systme dexploitation mobile cr par Google, prsente le double avantage dtre gratuit et open-source. Libre donc tout un chacun den exploiter lnorme potentiel ! Dans cet ouvrage, Mark Murphy, dveloppeur et membre actif de la communaut Android, vous explique tout ce que vous avez besoin de savoir pour programmer des applications de la cration des interfaces graphiques lutilisation de GPS, en passant par laccs aux services web et bien dautres choses encore ! Vous y trouverez une mine dastuces et de conseils pour raliser vos premires applications Android mais aussi pour accder facilement aux squences de code qui vous intressent. travers des dizaines dexemples de projets, vous assimilerez les points techniques les plus dlicats et apprendrez crer rapidement des applications convaincantes. Les codes sources du livre sont disponibles sur www.pearson.fr.
propos de lauteur
Mark Murphy programme depuis plus de 25 ans et a travaill sur des platesformes 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 Structure dun projet Contenu du manifeste Cration dun squelette dapplication Utilisation des layouts XML Utilisation des widgets de base Conteneurs Widgets de slection Samuser avec les listes Utiliser de jolis widgets et de beaux conteneurs Utilisation des menus Polices de caractres Intgrer le navigateur de WebKit Afchage de messages surgissant Utilisation des threads Gestion des vnements du cycle de vie dune activit Utilisation des prfrences Accs aux chiers Utilisation des ressources Accs et gestion des bases de donnes locales Tirer le meilleur parti des bibliothques Java Communiquer via Internet Cration de ltres dintentions Lancement dactivits et de sous-activits Trouver les actions possibles grce lintrospection Gestion de la rotation Utilisation dun fournisseur de contenu (content provider) Construction dun fournisseur de contenu Demander et exiger des permissions Cration dun service Appel dun service Alerter les utilisateurs avec des notications Accs aux services de localisation Cartographie avec MapView et MapActivity Gestion des appels tlphoniques Recherches avec SearchManager Outils de dveloppement Pour aller plus loin

Niveau : Intermdiaire / Avanc Catgorie : Dveloppement mobile

ISBN : 978-2-7440-4094-8

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
customer 27921 at Fri Mar 11 19:19:45 +0100 2011 Proprit de Albiri Sigue <tag.tog@gmail.com>