Android 2 Tworzenie aplikacji
Sayed Hashimi Satya Komatineni Dave MacLean
Tytuł oryginału: Pro Android 2 Tłumaczenie: Krzysztof Sawka ISBN: 978-83-246-2754-7 Original edition copyright© 2010 by Sayed Y. Hashimi, Satya Komatineni, and Dave MacLean All rights reserved Polish edition copyright© 2010 by Helion S.A. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. \\"ykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. \\'szrstkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w
tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej
odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Pliki z przykładami omawianymi w książce można znależć pod adresem:
ftp://ftp.helion.pllprzyklady!andro2.zip Wydawnictwo HELION ul. Kościuszki le, 44-100 GLIWICE
cel. 32 2312219, 32 230 98 63 e-mail:
[email protected]
-nnr: http://helion.pl (księgarnia internetowa, katalog książek)
=
CZ\-;elniku!
_:e-:z
o.:enić tę książkę, zajrzyj pod adres
;:-: :.serlopinie?andro2 :z=
"7-:<.a.:: swoje uwagi, spostrzeżenia, recenzję.
Mojemu synowi Sayed-Adiebowi - Sayed Y. Hashimi Mojej pięknej żonie AnnMarie za jej hart ducha; Ashley - za jej n iezachwianq nadzieję, Niko/asowi - za jego dobroć; Kavicie - za jej zaradność, dowcip oraz całokształt; Narayanowi - za jego urok osobisty; oraz całej mojej rodzinie w Indiach oraz w Stanach Zjednoczonych zajej miłość. - Satya Komatineni Mojej żonie Rosie oraz synowi lviike 'owi, za ich wsparcie; nie udałoby mi się bez nich. A także Maksowi za to, że przez tak wiele czasu towarzyszył mi u moich stóp - Dave MacLean .
Spis treści
O autorach
„„.„ ..„„.„.„„„.„„„„.............„
15
..„„.. „..„ .....„.„„ ....„.„.. „„.„„„.„.„ .•„..„.„„„.„...„„....„„
16
„„ ..„.„„.„.„ .. „„... „ ..„„.„ ...„ ..„.„.....„ ..„.„.„..„. .„.„...„„.„„„
17
Informacje o recenzencie technicznym Podziękowania Przedmowa Rozdział 1
13
..„„„.„.„.„„„ ..„„ ....„ ....„..... „...„„„.„.„ .•„„„ .• „„ ..„„ ....„ ....„.
Wprowadzenie do platformy obliczeniowej Android Nowa platforma dla nowego typu komputera Historia Androida
osobistego
„„„„„„„„..„.„. . . . . ..... . .. .
........„ ....„.
... ....... „. . . . . . . „. . . . „ . . . . . . . . . . . . . . . . .„ . ...... . . ..„.... „•.••.„ ••......... „..•........
Zapoznanie ze środowiskiem Dalvik VM
.„„„„„„.„„„„.„„„„„„„„„„„„„„„„„
Porównanie platform Android oraz Java ME Stos programowy Androida
„ „ . „ . „ .„„„„„.„„„„.„.„„„.„„„„„
„.„.„„„„.„„.„„ .. „„„. . . . „ ... „„„„„.„„„„„„„„„.„„„„.
19
20
21 23 25
27
Projektowanie aplikacji dla użytkownika ostatecznego „„. „„„„„ „„ „.„ „. „„„.„„„.„„„„„ „„„„„ „„„
29
. . „ . „ . „ . „ . „ . „ . „ . . .„ „ . „ . . . . „„ ... „ . „ . „ .... „„ . . . . „ ... „ .. „ . „ . . „ . „ . „ .
29
za pomocą zestawu Android SDK Emulator Androida
Interfejs użytkownika na platformie Android Podstawowe składniki Androida
.„„„„„.„„„. „„„.„„„„„.„„„.
„ „ „ „ . „ . „ . „ „ . . . . „„„„„„„„.„.„„„.„„„„„„.„.
Zaawansowane koncepcje interfejsu użytkownika Składniki usług w Androidzie
„„„„„„ „ . „ . „ „ „ . „ „ . . . „ ..
„„„.„.„„„„ „„„.„„ . „„ „„„ „.„„ „„„„„„„„„„„„
Składniki multimediów oraz telefonii w Androidzie Pakiety Java w Androidzie
Wykorzystanie zalet kodu źródłowego Androida Podsumowanie Rozdział
2
„ „ . „ . „ . „ . „ „ „ . „ . „ . „ „. „ . „ „ . „ „
.„. . . . . . „„ ..„. . . . . „ . „ . . . . . . . „ . . . . „ ..„ . . . . „„.„ . . . „ . „. . „„.„„„.„. . • . „ .. „.„ .. „.„.
Pierwsze koty za płoty
. ... . . . . . . „ . . . . . . . . . „„ . . ....
Pobieranie zestawu JDK 6
„ . . . . .„. . . . . ..... „ . . .. . . . . . .„ . . . . . . . . . . . . . . .
„.„„„„„.„ .........„„„.„....„ . „.................„ . . . . . ...... „..
Pobieranie środowiska Eclipse
3.5
Pobieranie zestawu Android SDK Instalowanie narzędzi ADT
.. . .. „. . . . . . .„.„ .......„. . . . .„..„ „ ..„.„.. „.„„ .„..„.. .
.„. . „.„.„„.„.„„.„ .. „„„.•. „ .. „„„„.„ ..
. . . . . . . . . . . „ ... ........„ .......... „„.„.„ ... „.„.„„.... „„ ..... „
Przedstawienie podstawowych składników
.„„.„„„„„.. „„„.„ . .„ „. . . . . . . . . . . . . . . . . .„.
............................ . . . . . . . . . . . „......................• .......•.......•..•..................•.....••.•
Aktywność Intencja
.. ... . . . ... ... ........ . . ...... ..........„....„...........„...........„ •............ „„..• „ ..„...
.... „............ „.......•................................•................„ •.............. „.„
Dostawca treści
31 32 34 34 36
39 41
43 43 44 ...... 44 45 46 48 49 49 ......... 49 49
„„„„„„„„„„.„„„„„„„„„„.„„.„„.„.. „„.„„„„„„„
Konfigurowanie środowiska
Widok
„„„„. . „„„.„.„„„„„„
. „ „ „ „ „ „ . „„„„„„.„„„„„ „„„„„„„„„„.„.„„„.„„„„
30
......... „ „ „ . „ „ .. „„„„„„.„„„„„.„.„ ...„„„.„„„.„„.„„„.„„„„ ..... „
6
Spis treści
Usługa
. . .
. . . ............
Urządzenia AVD Witaj Świecie!
...
.. ............. ... ............. ..... ... „ ... „. .. ............ .
..... . . „ .......... „ .... „ . .„„„. . . . . .„. . „ .....„.......... „.... ...... ...
Wirtualne urządzenia AVD Analiza aplikacji Notepad
„. . . .„ . . .„ „..............„ ...„ ........„ ......„ ..... „.„.„
.
... . ... ... . .. ... . ........
Rozłożenie kodu na czynniki pierwsze Badanie cyklu życia aplikacji
.
...... ...... .. .....„„„.............
.
...... . .. „ ....... „ ....„„..... ..... . .........
......
„„.„ .. „.. „„ .. „. . ...... . .. ............. ... ...„ ...... . ...............
Usuwanie błędów w aplikacji Podsumowanie
.... ... „. .. ..... . .. ...............
.
.... ...... ..................... ... . ......... ....... ... .„.... „ .... .. .. .... .„„. .
Wczytanie oraz uruchomienie aplikacji Notepad
...
. . .... . . . ............... . . .. .. . .... . ... .. . .. ... ... .....„ ........„ ....... „. .
. . .. ... . ..... . ......... . ..... . ... . .. . ..... . ..... ..... ........ ........ . ................ .
Korzystanie z zasobów, dostawców treści i intencji
Zasoby
... .... „ . ..
. . ....... . . . .... ............... . ... . .. . ..... ............ ..... .... .. . .. .. .. . . ...... ... . .. ....... ..... . ..
Poznanie struktury aplikacji Androida
Rozdział 3
49 50 . 50 . 50 ............. 54 57 59 59 . 61 . 68 71 . . . 72
............................ ..................................................................................
AndroidManifest.xml .
.....
... . .
..
... „ ••• „ ••• „„••• „.„„
73 74
.... .„....... . .. . ... ...... ...... . . .. ... . .„ .... „ . . . . „. . . . . . . .„ .. „.„ . . . . „.„ .. „ . „ ....... „ ...„ „ . „.... „..
Zasoby typu String Zasoby typu Layout
75 „.„ 76 78
. .. „... „. .„„. . „.„. . . . . . . . . . . . „ . „. . . . . . . „ . . . . .„ ... „. . . . . „ . . . . . „... „„ ...... .
..„ . .„ . „ ....„ ....„ . „ . . . . . . . . . . . . . . „ . . . . „ ..... „ . . . . . . . . . . . . . . . . . „..„ . „..
Składnia odniesienia do zasobu
„ . . . .„ ....„ . ... „. . . . . . .. . .. . „ .... . „ . . . „. . . .„„.„. . . .„ . „ „
Definiowanie własnych identyfikatorów zasobów do późniejszego użytku
. .. . . ....... . ... . ......... .. ....... ..... ... . .. „.... „. . . . . . . . . • .„ . . . . . . . . . „.
Skompilowane oraz nieskompilowane zasoby Androida
. . ... . . . . .. . ..... . . . . . .. .
Rodzaje głównych zasobów w Androidzie
. ... . ..... . .. . . ... ... „. . ..... . . . . . ... . .. . .. .... ..
Praca na własnych plikach zasobów XML
.. .. . ... . . .. .. . . .. . .. ... . .„. . . . . . . . .„ . . . ........
Praca na nieskompresowanych zasobach Praca z dodatkowymi plikami
.... ..
.„. . . . . . . . . . . . . . . . . . . . . . . . . . . . „.„. .. „.... „. . . .„ . „ .... „„. . „. .
..... . ... . ..... . .. . . .. . .. . .. . ... ..
.. .. ...... .. . .... ......„. . . . . . . . . . . . .. . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . . • . . . . . . . • . . • . . . . . . . . . . . . . .
Analiza wbudowanych dostawców Androida
. . .. ... . „. . . . . . . . .. . . . . ....... „. . . ..... „.
Analiza baz danych na emulatorze oraz dostępnych urządzeniach Architektura dostawców treści
........
.. . .. . .. . . .. . . .. . . .... . . . ... .. . ... ... . .. ... . .. . .. .. . . . . ... .. . . ..... ..
lmplementowanie dostawców treści Intencje
89 90 91 91 92 93 93 98 111 122 123 125 126 127
. .. . .. . . ... .. . ..... . . .......... . „ „ . „... .. .
Przegląd struktury katalogów mieszczących zasoby Dostawcy treści
79 80 81
.. ... .... ...... „........ . . .. ..... .. . .... ........... ......
. .. ..... .. . ... ..„ .............. . . . . . . . „ .. „. . . . . „ . . . „. . .. . . . . . . .„ . . . . . . . . . „ . . . . . . . . . . . . . . . . . „. . .. . ... . ....
Intencje dostępne w Androidzie
. . ..... . .. . . . . ... . . .. . .. ... . .. . . ............... ....... . . .. . . ... ..
Intencje a identyfikatory danych URI Działania ogólne
. ......... . . .... . . ..... . ... .. . ....„. . „...............
...... . .. . ..... . .... ... .... . .. . .. . . .„.„. . „„. . . . „. . „. . . . . . . . . . . „ .....„... . ... . .. ... .
Korzystanie z dodatkovvych informacji
.......... . ...... ...„ . . . „. . . . . . . . . . . ...... „.. . . . . .
Stosowanie składników do bezpośredniego przywoływania aktywności
..... ...... ...... ..... . . . .... . .. . .. . .. . ... .............. . .. „ . . . . . . . . . „ . . . . . . . . . „„„„. . . .„ ....
Najlepsze rozwiązanie dla projektantów składników Kategorie intencji
. ... ... ... . . .... ..... . . .... „ . . . . . . . . „ . . „ . . . . „ . . . „„ ... . . ... . . . .... . ... . ... . .. . .. . .. .
Reguły przydzielania intencji do ich składników Działanie ACTION_PICK
Podsumowanie
. . .. . .. . . . . .. . ... ... . . . . . ...... ......
„. . . . „. . .... „ .. . . ..... „.. . ..... . ......... . . ...... . ................. . ..
Działanie ACTION_GET_CONTENT Przydatne odnośniki
.
..... . .. ....... . .... . ... „. . .
.. . ........... .. . .. .. .. „. . . „.„.„„„. .„ „. . „„.
... . .. . ..... . .......... . „ .... . ......... . ..... „ . . . . . . . • . . • . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. .. . .. . . ... . „ ...... . . . . . . . . . . . . . . . . . „ . .„ . . . . . . . . . . . . . . „„. . „. . . . . „ . . . . „. . . . . . . . . . „ ... .. . „..
129 130 130 132 133 13 5 J 36 137
7
Spis treści Rozdział 4
Budowanie interfejsów użytkownika oraz używanie kontrolek
Projektowanie interfejsów Ul w Androidzie Standardowe kontrolki Androida Kontrolki tekstu
139
.„.„„„„„.„„.„„„„„„„„„„.„.„.„„„.„.„„„„.„
.„„„„.„ .. „ . „ . „ . „ . „ .. „ .... „.... „ „ . „ „. . „ „ „ „ „ „ . „ „ „ „ .. „ . „ „ „ „. . „ .
Kontrolki przycisków Kontrolki listy
.....
. . . .„„ .. „ „ .. „ „ ....„„. . „„ . .„ „ . . . . . . . . . .
.„.„.„ .. „ . .„ „ . . .„ „ . „ . „ . „ . . . „ „ . „ . „ . „ . „„.„„„„„„„„„.„„„.
„.„.„ .. „ . „. . „„„„„„„„„.„.„ .. „ . „ .. „ . „ . „. . „ . „ „ „ . „ „ . „ . „.. „ . „.. „„„
Kontrolki siatki
.„„„„ . . „ . „ . „. . „„.„„.„.„„„„ .. „ . „ . „ . „ . „ . „ . „ „ „ . „ .... „. . „ . „ .. „ . „ ..
Kontrolki daty i czasu
„.„„„„„„„.„„„„.„.„„„„„„„„„„„.„„„„„„„„„„„„„.„
Inne interesujące kontrolki w Androidzie Kontrolka MapView Kontrolka Gallery
„ „ „ „ „ „ „ „ „„„„.„„„.„.„„„„„„„„.„
. „ „ . „ „ .. „ . „ . „ . . . „ . „ „ „ „ „ „ „ . „. . „ . „ . „ . „ „ „ . „ . „ . „ „ „ . „ „ „ . „ . .
„ . „ . . „ . . „ „. . „ „ „ „ . „ „ „ . „.. „ „. . . „ . „ . „„ „ „ . „ „ „ . „ . . „ . „ „ . „ „ . „ „ „
Kontrolka Spinner
„ . „ . „ „ „ . .„ „ . . „ „ „ . „ . „ „ . „ . „ .. „ . „ . „ . „ „ „ „ . „ „ „ „ „ ..„ . „ . „ . „ . „. .
Menedżery układu graficznego
.... „ . „ . „ . „ . „ .. „ „ „ „ . „ . „ . „ „ „
Menedżer układu graficznego LinearLayout Menedżer układu graficznego TableLayout
„„„„.„.„.„.„„„.„.„„„„„„.„.
„ „ „ „ „ . „ „ „ „ „ „ „.„„„„„„„„.„
Menedżer układu graficznego RelativeLayout Menedżer układu graficznego FrameLayout
.. „ . „ . „ . „. „ . „ . „ „ „ . „ ...
„ „ „ . „ „ „ . „ „ „ „ „ „ . „.„„„„.„
„ „ „ „ „ . „„„.„„„„„„.„„„„„„.
Dostosowanie układu graficznego do konfiguracji różnych urządzeó Działanie adapterów
„„„.
.. „ . . .„ „ . „ . „ .. „ . „ . „ . „ „ „ . „ . „ „ . „ „ . „ . „ . „ .. „„„ . . .„ .. „ . „ . „„ „ . „ . „ ..
Zapoznanie z klasą SimpleCursorAdapter Zapoznanie z klasą ArrayAdapter
„.„„„.„.„„„„„„.„.„„„„„.„„„„
„„.„.„„.„„„„„.„.„„„„„„.„.„„„„„„„„.„
Tworzenie niestandardowych adapterów
. „ „ „ . „.„„„„.„„„.„.„„„„.„„„„„
139 145 145 l49 155 159 160 162 162 163 163 164 164 167 171 173 175 177 178 178 179
Usuwanie błędów i optymalizacja układów graficznych za pomocą narzędzia Hierarchy Viewer Podsumowanie Rozdział
5
.„„„„„„.„„„„„„„„„.„.„„„„„.„.„.„
„ „ „„ ... „ . . „. „ . . „ „„ „ „„ „ . . „. „. „. „„„ . . . . . „ . „ „ .. „ „.„ „ „. . „„ „ „. „ „ „. „ ..
Praca z menu i oknami dia logowymi
Menu w Androidzie Twor7.enie n1enu
.................................................
.. „ .. „.„ .. „ . „ . „ . „ .. „ .. „ . „ „. . „ . „ „ „ . „ . „ „ „ „ „ . „ „ . „ . „ .. „ . „ „ „ . „ „ „
Praca z grupami menu
„.„„„.„„„„„„.„„„„„„.„„„„„„„„„.„„„„„„.„„„„„„.
„„„„„„.„„„„„„.„„„„„.„„.„„„„„
Utworzenie środowiska testowego do sprawdzania menu Praca z innymi rodzajami menu Rozszerzone menu
.„„„„.„„„„„.
.„.„„.„.„.„„„„„.„„„„.„.„„„.„.„„„„„„.„„„„„
„ . „ .. „ . „ . „ . „ . „ ... „„„„„.. „ . „ „ „ . . „ „ . „ . „ .. „ .. „ . „ . „ . „ . „ . „ „ „ . „
Praca z menu w postaci ikon Praca z podmenu
183
„„„„.„. . „„„„.„.„„„. . . . . „.„„„.„ . „ . „ . „ . „. „ „ „ . „. . .„.„„„„ . . . „. .
Odpowiedź na wybór elementów menu
„„„„.„„„„„.„.„.„„„„.„„„.„„„„„„„„„„„„„„.
„„„.„„„„„„.„.„.„„„.„„„„„„.„.„.„„„„.„„„„.„„„„„„„„„„
Zabezpieczanie menu systemovvych Praca z menu kontekstowymi Praca z menu alternatywnymi
„.„„„.„„.„„.„„„„„„„„„.„.„.„„.„„„„
„ „ „ „ „ „ . „ „ „ „ „ „ . „ „ „ „„„„„.„.„.„.„„„.„„„„„
„„„„„.„„„„.„„„.„.„.„„„.„„„„„„.„„„.„„„„
Praca z menu w odpowiedzi na zmianę danych W czytywanie menu poprzez pliki XML
.„„„„„.„.„„„.„„„„„„„.„
„„„„„.„„„„„„„„„.„„„.„„„„„„„„.„„
Tworzenie odpowiedzi dla elementów menu opartych na pliku XML
180 182
„.
183 185 186 187 189 195 195 195 196 197 197 200 204 204 206
Krótkie wprowadzenie do dodatkowych znaczników menu w pliku XML
„.„.„.„ . . . . . „ „ „ . „ . „ . „ „ „ . „ . . „„ .. „ . . „ . „ „ „ „ . „ . . „ . „ .. „„ .. „ . .„ . „ . .„ „ .
207
8
Spis treści
Korzystanie z okien dialogowych w Androidzie Projektowanie alertów
. . . . . . . . „ ..
208 209 211 215 ..... 216 217 217
„ . . . . . . . „ .... „ ..... „„ . . . . . .
... „ . . . . . . . . . . „„ ... „ . . . . „.„„ . . . . . . . . . . „ . . . .„. . „ . „ „. .„ . . . . .....„ . „ .
Projektowanie okna dialogowego zachęty
.. „„„ ..„ ...„„„ .. „ „ „ „ „ „ „ „ „.....„
Natura okien dialogowych w Androidzie
....... „ .......... „ . „ .... „ . . . . . . . . „. .„ .....
Przeprojektowanie okna dialogowego zachęty Praca z zarządzanymi oknami dialogowymi
. . . . . . . . . „.„ .. „ „ „ „ „ . „ „ „ . „
„„„. „. . „ . . „ „ . . . . . . . . . . . . . . . „.....„ . . . . .„ .
Protokół zarządzanych okien dialogowych
. . „ .. „ . .„ . „ „ „ . „ „..„ „ „ . „ „.... „ „ .
Przekształcenie niezarządzanego okna dialogowego w zarządzane okno dialogowe Podsun1owanie Rozdział 6
218 ........ 2 1 9 227
.„ ....„ ....„ ...„„„„ . . . . . . .„ .. „ „ „ „ „ „ „ .„. . . „ . . . . .„
Uproszczenie protokołu zarządzanych okien dialogov.')'ch
„.„„„„
. . . „ . . . . . . . . .„ „ . . . . „ . . . . . . . . . . . . . . . . . . „ . . . . „ ..... „ . . . . . ......................................
Prezentacja animacji dwuwymiarowej
Animacja poklatkowa
.....„ ••. „••„„•••• „•••• „••„ ••....„ •.••
Zaplanowanie animacji poklatkowej Utworzenie aktywności
. . .„ „ . . „ „. . . . .„„.. „ . „.. „ . . . . . . . . . . . . „ . „ . . . . .„
. „ .... „ . „ ......... . . .
Dodawanie animacji do aktywności Animacja układu graficznego
„ . . . . . .. . „ . „ . „. . „ „ „ . „ . „ . „ „. . „„ . . .„ . . „ . „
. . . . .„ . . . . . „ . . . . . „. . .„ „ „ . „ „ „. . „ . „. . „„„„„. .
„ „ „ „. . „ „. . . . „ „. . „ „. . . . . . . „ . „ . . „„ .....„ . . .„ „ .... „ .... „ . „
Podstawowe typy animacji przejść
„ „ . „. . . . . ....„ . . . .„ . . . . . „ . . .„ „ .... „. . .„ „ .... „„„
Zaplanowanie środowiska testowego animacji układu graficznego Utworzenie aktywności oraz widoku ListView Animowanie widoku ListView Stosowanie interpolatorów Animacja widoku
229
. . .„ „ .. „ . . . . . .„.....„. . . . . ............................................„ ... „ „ „..
.„.„
„ . . . „ . . . „ . „ . „ „ ... „ . . . . „ . . . „„. .
. . .„.„.„.„„„„„„„„„„„„„„......„ . .„ ......„. . . .„.
. . „ .. „ .............„. . . . . . „ . . . . „ ....... . . . . .„ . . . .......... „ . . . .„ .
„„ .. „.„ . . . „ . „ „ „ „ . . „ . „ .. „„.„.„ .. „„ . . . „ . . . . „ . „. . . „ ... „ . „. . . . . . . . . „ . . . . . . ..
Animacja widoku
„ . . . . .. „ . . . . . „ . . . „„ .... „ . . . . . . . . . . . . . . . . . . . . . . „. . . . . . „ ....... „„.„ .. „. . . . . „ . .
Dodawanie animacji
„„ ..„ ... „„ . . . „ . „ . „ ... „ ... „ „ „ „..„ ... „ ..... „„ . . „ ..... „ ..............
230 230 23 1 233 236 236 237 238 240 244 245 246 248
Zastosowanie klasy Camera do wprowadzenia postrzegania przestrzeni w obrazie dwuv.')'miarowym Analiza interfejsu AnimationListener
Kilka uwag na temat macierzy transformacji Podsumowanie Rozdział 7
„ . . . . . „ .. „.„„ ...„ „
„ . . „ „ . „ . . . . . „. . . . .„ . . . .„ . „ . „ . „ ..„ . „ „ . . „ „
„.„„„.„ .. „ „ „ „ „ . „.. „ ... „ „ „. . .
„ ............. „ ............ „ . . . „ ....................... „.......................................
Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
„•••••••••••••„••••• „••••••••••••„•••••.• „••.„„.„....•
Model zabezpieczeń w Androidzie
„ „ .. „.„.„.„.. „„ . . . . . „ . . . . . . . . . . . . . . . „ . . . . „ „ . „ . . . . „.„
Przegląd pojęć dotyczących zabezpieczeń Podpisywanie wdrażanych aplikacji
„„ .....„ . . . . . „„ . . . „„ .... „ ......„„„•. „
. . . . .„ „ „ „ „ „ . „ „ „ „ „ „ „ . „ „ „ „ „ „ „ „ „ „ „ „ .
Przeprowadzanie testów zabezpieczeń środowiska wykonawczego Zabezpieczenia na krawędziach procesu
Deklarowanie oraz stosowanie uprawnień Stosowanie uprawnień niestandardowych
„. . . . „.„„..„ „ „ „ . „ . . . . . „ „ „ . „ „ „ „ .
„ . „ „ „ „ „ „ „ „ „ „ „ „ „ „ .. „ „ „ „..„ „ .
Stosowanie uprawnień identyfikatorów URI
„„„.„„. .„„„.„„„„. . . „ . „ . . . . „ .
Praca z usługami opartymi na położeniu geograficznym Pakiet mapowania
. . . . . „ . . . . . . . „ ... „ „ . . . . . .
..... „ „. . . . . . . . „ . . . „ ... „ „ „ „ „ .. „ „ .... „ . . . . . . „ . . . . . . . . . . „ ......... „ „ „ . „ .
Pakiet położenia geograficznego Podsumowanie
„ . .„ . . . . „
...... „ „ . . „ „. . . . . „ „ „ „ „ „ . „ .... „ . . . . „„.
...„ „ „. . „„„„„„„„„„„„„. .„ . . „ „ „ „ „ . „ „ „ „ ..
. „ . . . . „ . . . . .„. . . . . . . . . . . . „. . . . . . . . . „ . . . . . . • . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . .
251 252 253 254
255 255 256 256 261 261 262 264 270 270
271 282 299
Spis treści Rozdział 8
Tworzenie i użytkowanie usług
9
301
... „•••••„••••••..•.•.••••...•••• „.•.••....•... „••• „„.
Użytkowanie usług HTTP ................................................................................. 301 Wykorzystanie modułu HttpClient do żądań wywołania GET
.....„„.. „.....
Wykorzystanie modułu HttpClient do żądań wywołania POST Zajmowanie się wyjątkami
.„„ . . . . . „ ..„„ . . . . „ .... „ ..... „ .....„ .........
Kwestia problemów z wielowątkowością
....„ ..........„ ...... „ .
Nawiązywanie komunikacji międzyprocesowej Utworzenie prostej usługi Usługi w Androidzie
.„„ ... „„ ...
.
.......
302
. 303
„„.„ ...... 307
.
..„ . .. . . . „ . . .„ .....
309
.. „„ .. „„ ....
313
„. . . . . . . . . . „.„ . . . . . • . . „„
. . . „„ .. „ . . . . . . . . „.„„..................„. . . . . . • . . . . . . . . . . . „.„„
.... 313
............. .. ... ... . ... ..„ . . • . „ .................„„ . . . . . . . . . . . „...„ .•...„ ...
314
Usługi lokalne ............................................................................................... 316 Usługi AIDL
. . .... .. ................ . . . .. ... ..... . .. . „ . . . . . . . . . . . . . . . . . . . . . . . . . . . „.„ ..... „. . . . . . . . . . • .....
319
Definiowanie interfejsu usługi w języku AIDL ........................................ 320 Implementowanie interfejsu AIDL ............................................................ 323 Wyw·oływanie usługi z poziomu aplikacji klienckiej
.................. . „ . • . . • . . . . .
324
. . . . .. .. . „........... „. ..... .. ..
328
. . . . . . . . . . . . . .............................. ................................. ........................
338
Przekaz)'\vanie złożonych typów danych usługom Podsumowanie Rozdział 9
Używanie szkieletu multimedialnego i inteńejsów API telefonii
Stosowanie interfejsów API multimediów
. .. .. .„ . . . . . .......... „ . . . . . . . „ ... „ .... .
Odtwarzanie źródeł dźwiękowych
. . . . . . . . . . . . . „ . . • . . . . . . . . . . . „ ............ „ .. „„. . „.„
Metoda setDataSource
.
.
„ ..„.„ .. „. . . . „ .. „.„„ .. „............ „.„................ „ .. „..
Osobliwości klasy MediaPlayer
„. . . . . . . . . . . . . . . „ „ . . . . . . . . . . . . . . . „„ . . . . . . . . . . . . „ .. „„ .. „ ..
Analiza procesu rejestracji dźwięku Analiza procesu rejestracji wideo Analiza klasy MediaStore
.
..... „ • . „.„ ......... . „ .. „ ............... „.. . „ . . . ...
.... „...„„ ... „ . ......
.
.
.. .. . . . . .... . .. ..... „.„... „ . . . „.
„.„„.„„„„„„„„„ .. „„„„„„.„„„.„„„„.. „.„„„„„„.„
Dodawanie plików do magazynu multimediów Stosowanie interfejsów API telefonii Praca z wiadomościami SMS
„.„.„„ .. „ . „ . . . . . „. . . „ . „
347 348 350 351 355 360
...... 364
..„ . „ „ „ „ „ „ . „ . . . . . . „ „ „ „ „ „ „ . . . . . . .„ „ „ „ „ „ „ ..
„„„„.„„.... „„„.„„„. . . . . . „„„.„„ .. „„ ..„ „ . „ „ „ „ „
Praca z menedżerem telefonii .
. „ . . . . . . . . . . . . . „ .. „.„ .. „ . . . . . . „.„„„„„.„.„.„„„„. . ..
Rozdział 1 O
339
... 343
........... .... . .... .... . ......... .. .. . . . ..... ............. ........„..„ ......
Odtwarzanie plików wideo
Podsun1owanie
339
„„.
„....„„
366 367
. 373
„.. „„.. „ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . „„ ......... „ .... „„ . . .„„ ........„„.„ .... „„. .
375
Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
„„„„„„„.„„„„„„„„„„„„„„„ .......„.„„.
Historia i podstawy biblioteki OpenGL OpenGL ES
.. „. .
„„ ..... „ .... „ .... „„„„ . . . . . . . . . . . . „„„„„ .•...... „ . . . . . . . .............„ . „ . „
Środowisko OpenGL ES a Java ME ....
Kamera i współrzędne
. . . . . . . . „„„...
„„„.. ......„ . . . . . . . „.„. . . . . ............. „. . . .„ ... „„.„.„
Podstawy rysowania za pomocą biblioteki OpenGL
378
..... 379
.„.. „ ..„„..„.„.„.„„„ ............ „„„„„„„„ ..
M3G: inny standard grafiki trójwymiarowej środowiska Java Podstawy struktury OpenGL
377
... „ . . . . • . „ .. . ..... „„ .. „„„ .. „ . . . . . . . . . . „„„„„ ..
380 381
..... 381
.„„„„„ .• „„„„„„ ......
.
..... „. „„„ „. . . . . „. . . . . .„„. „„ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . „ ... .. ...
382 387
Tworzenie interfejsu pomiędzy standardem OpenGL ES a Androidem .... 391 Stosowanie klasy GLSurfaceView i klas pokrewnych Proste środowisko testowe rysujące trójkąt Zmiana ustawień kamery
„„.„...... 392
„ ... „. . .. ......
„„ ..................„ ............„ .. „ .. „
„„.„„„ . . . „ .......... „„„.„„„. . . . . . . „„.„„„„„. . . . . „„„.„.
394 398
1O
Spis treści
Wykorzystanie indeksów do dodania kolejnego trójkąta Animowanie prostego trójkąta w bibliotece OpenGL Stawianie czoła bibliotece OpenGL: kształty i tekstury
.„„
.................. 399
„„„„„„ ..„„„„ ....„.
400
. .. ... . . ...„ „ ... „„„„ .... „ .
404
Prosta sztuczka z menu, przydatna dla aplikacji demonstracyjnych Rysowanie prostokąta Praca z kształtami
....
404
... „ .... „ . „... „ „. . „„„„.„. . „„„.„„. . . . . . . . . . . . „ . „ „ „....„. . „ „ ..
409
. .„ „ „ .. „ „. . . . . . „ . „ „. . . . . „„„.„„„„„„.. „ ....... „.„„ . . „ „ . „ „ . „
Renderowanie kwadratu za pomocą obiektu RegularPolygon Praca z teksturami
Zasoby środowiska OpenGL
Rozdział 11
.„. . „ „.. „„ ..„ ..... „„.. „„„„„„.„ . .
Klasa ListPreference
Widok EditTextPreference Widok RingtonePreference Organizowanie preferencji
446 447
.......... . „ . . . . . „ . . . . . . . . . . . „. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . „.„. . .. . .. . . .. . .. . ...... . .. . .. . ....
W jaki sposób użytkownik odbiera aktywne foldery
Rozdział
„ . „ . „ . „ . „. . „.„ . . „ . . . . „ . „ „ „ „. . „ .
13 Widgety ekranu początkowego
.. 453
„.„.„.„„.„.„„ . . „. . . . „
• „... „„.„ . .. . .. . . ..
„.„„„„ . . „ . . . . „.„.„„.„„„„ . „ . „ „. • . „ . „ . „ . „ .. „ „ „ „ . „ . „ . „.
• „ . „ „ „ . • „.„„.
Czym są widgety ekranu początkowego?
454 459 470
471
.......................................................
Architektura widgetów ekranu początkowego
449 452
453
.............................................................
„.„„„.„„„.„ . . . .„.„.. „„. . „.„. .„„ ....„„ . . . „ ... „„.. „ . „
Tworzenie aktyv.rnego folderu
444
„„„„.„. . „ „ .. „ . . . . . „ . „ . . „ . „ . .
.„„„„„„„ . . „ . „ . „ .. „.„„„ .. „ „ . „ . „..... „ . „ . . . . . . „ . „ „ „ „ „ „ .
Badanie aktywnych folderów
Podsumowanie
.... 443
„„ ..„„•.. „ „.. „ „...
. . . „. . „.„„ . . „ • . „„.„„„„„„„.„„„ ..
Badanie aktywnych folderów
435 436
„„„.„.„.„„„„„
„„„.„„„ . . . .„ „ . „ „ „ .... „„„.„„ ... „ „ . „ „....„„„„.„.
„ . . . . • „.„.„......„„• . . . „..
432
435
.... „ ••••• ••••„.„„.„ .•• „.•.• „
„ „ „ . „ „.. „„.„„ ..„ . „ „ „ „ „ . „ ...„„.„ . . „ „ „ „ ...... „ . „ „ „ . „ „ „ . „
Widok CheckBoxPreference
423
.. 433
.„ ....„ „ „. . „ „ „ „ „ „ . „ „ .. „„„„.„ .. „.„.„.„.„„„„„ . . . . „
Manipulowanie preferencjami w sposób programowy
419
... 429
„ . „ . „ ... „„ .. „„„„„„ . . . . „ .. „„ .. „ . „ . . „„„„„ . . . „ . . . . „„ .. „
Badanie struktury preferencji
Rozdział 12
„
.„ . . . . „ ..... „ . „ .. „ . . . . . . . „.. „ . „ . „ ..... „.„ .. „„ .. „ . „ . „ . . . . . . • . „ . . „ . „ . . „ . „ . „ . „
Zarządzan ie preferencjami i ich organizacja
Podsu111owanie
„„„.„„„.
.....„ „. . „ „ „ „ „ „ . „ „ „ „ „ „ „ „ „ „ „. .„„„.„„„„ „„.„„„.„ . . „„ .. „.
Rysowanie wielu figur geometrycznych Podsumowanie
...... 410
„„ .. „ .. „ „ . „ „ ..„„„„„„„„„„„....
„ „ „ „ „ „ „ „ „ „ „ „ . „ „ „„„„„„„ . .„ „ .
472 472
Wrażenia użytkownika podczas korzystania z widgetów ekranu początkowego Cykl życia widgetu Przykładowy widget
„„„„„.„ . . . „.„„ ... „ „ „ . „ .... „.. „„.„„„ . . „„„„ ..• „ .. „„„.„
473
.
475
„„„„„„„„„.„.„„„„„„„„.„„„ „ . „ . „ . „ „ „ . „ . „ . „ „ „ . „ „ „ . „ . „ .
„ „. „„ „ „„„„ „ . „ . „„„ „ „ „ „.„ „„„„„„„.„„„„„„. „„„. „„„ „„„ „
Definiowanie dostawcy widgetu Definiowanie rozmiaru widgetu
„ „ „ „ „ . „ . „ . „ „ . „ „ „ „ „ „ . „.... „.„„„„„„„„.„„
„„„„„„„„„„„„.„ • . „„„.„„ . .„ „ • . . „.„„„„„„.
Pliki związane z ukladem graficznym widgetu Implementacja dostawcy widgetu Implementacja modeli widgetów
„„.„„„ . . „ .. „.„ •. „„„.„„„„„
„„„„„„„„„.. „„„„„„„„„„„„„„„„.„„ .. „.
„„„„„„„„„„„„„„„„„„.„„„„„„„„.„„„.„
Implementacja aktywności konfiguracji widgetu Ograniczenia i rozszerzenia widgetów Zasoby
„„„„„„„„„ .•• „„„„„„„.
„ „ „ „ „ „.„„„„„.„..„.„.„„ .. „ „ . „ . „ „ „ „ „ „
............................................................... . . . . . .................... ........................„
Podsumowanie
........ . ...... . ................... . .. . .. . .. ... . . ....... ..... . . ..... . .. „. . .. . . . . .. . .......... . .....
481 482 484 484 486 489 496 500 501 501
Spis treści
Rozdział 14
Wyszukiwanie w Androidzie
11
503
...„ ..•... „ •.........•.................•.....................
Wrażenia z wyszukiwania w Androidzie
504 504
.. . .......„„ . . . „. . . . . . „„......... „ . . . „.„ .. „...•.
Badanie procesu przeszukiwania globalnego w Androidzie
. . . . ... „...... ...
Włączanie dostawców propozycji do procesu wyszukiwania globalnego
. 508 511 ......................................... 5 1 3
........ . .. . . ...... .. . .. .. .. .... .. . . .. . .. . .... . . . . .
Interakcja pomiędzy polem QSB a dostawcą propozycji Interakcja aktywności z klawiszem wyszukiwania
..
. ... „ „ . . . . . . „ ....... .
Zachowanie klawisza wyszukiwania w obecności standardowej aktywności
514
. . . . . . ... . .. . .. ....... ... . ... . . . . ..... .... . ... . . . . . . . ...... ...... ........ .
Zachowanie aktywności v.ryłączającej v.ryszukiwanie Wywoływanie \'l}'Szukiwania za pomocą menu Wyszukiwanie lokalne i pokrewne aktywności Uruchomienie funkcji type-to-search
... ...... . .....
521
..... ..... .
... .. . . . ... . .. .
.
524
529
..„ ..........
530 531 531 532
. . . . . . „. . . .. ......... . . . ..... . . . . ..
. . . . . . . . . . . . . . ... . . ............ . .. . ... . . . . . .. . .
Pliki implementacji prostego dostawcy propozycji Implementacja klasy SimpleSuggestionProvider
. .... .. .... .. ... . . . „ ...... „... .
.. ....... .............. . .„..... . . . ..
Aktywność wyszukiwania dostępna w prostym dostawcy propozycji Aktywność wywołania wyszukiwania
520
. ........ . .
.. ... ... ... .. . ........... . .. . . . . . . . . ...
.
Planowanie prostego dostawcy propozycji
..
...„„........• „„.„„ ... „ .. . . „ .. „
... . .. . ....... ... . ... . . .
Implementacja prostego dostawcy propozycji
. .
. ....
..... .. . .. . ..... . ... . . . ...... . .. ............. ... . .... . . .
535
539
Wrażenia użytkownika podczas korzystania z prostego dostawcy propozycji
. . .. ...... . . ..... „ . . . . ........•• „ • . . . . . . • . • „ ••• „•.••.......
Implementacja niestandardowego dostawcy propozycji Planowanie niestandardowego dostawcy propozycji
... . .. . ..... .... . ... . .. . .. . . . . ... . . . . . .
. . . . ..
..
..
. . .. . . . . ...
Pliki wymagane do implementacji projektu SuggestURLProvidcr Implementacja klasy SuggestUr!Provider
„. . . . . . . . . ....... „. . . . . ..
.. . . . . .
„.„ . . . . . „ .. .. ... „.
540 544 545 545 546
Implementacja aktywności wyszukiwania dla niestandardowego dostawcy propozycji
554 .. 560
„„„„„„.„„ . . „.„ . . „„ ..„„„.„„
Plik manifest niestandardowego dostawcy propozycji
. ... „.•.„ . . . . . • „. „ . „
Wrażenia użytkownika podczas korzystania z niestandardowych propozycji
.
..
. . . . . . .. . .. .
„ ••.•......... . . . • • • • • • • • . • • • • • • • • • „ .. . .. . ... .
561
Zastosowanie klawiszy działania i danych wyszukiwania specyficznych dla aplikacji
. . ...... . . ....... . ........ . . . . . ... . . . ..
.
..... ... . .
. . . . ..... . „ . . . . . . • . . • . . . . . .
Wykorzystanie klawiszy działania w procesie 'vyszukiwania
.. . . ... . . .. . . ...
Praca ze specyficznym dla aplikacji kontekstem wyszukiwania Zasoby
.... .
. . . ..
„.„. . . . . . . . „.„ . . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . „ . . . . . . . . .. .... .
Podsumowanie Rozdział 1 5
„.
.
565 565
. . 568 570 570 ..
„ „ . ... . .. ...... ... .„. . ....... .. . „
... ................................................................... ..............................
Analiza interfejsów przetwarzania tekstu na mowę oraz tłumaczenia
.................................................................
Podstawy technologii przetwarzania tekstu na mowę w Androidzie Używ·anie 'vyrażei'1 do śledzenia toku wypowiedzi
„„ .. . . . . „ • •••• „
..
.. . „ . .. . . . ....... .
Zastosowanie plików dź·więkowych do przetwarzania tekstu na mowę Zaawansowane funkcje silnika TTS Konfiguracja strumieni audio
573 573 578
....... . . .
„.„
.... .. . „......................•..••••. „ ..........••••••........
. . „. . . . . . . . . . . . . . „ ..„ . . . . . . . . . . . . . . . . . . . .. . . . . . . . • . . . . . . . . . . . . . . . .
Stosowanie ikon akustycznych
.. ... . „ . „. . . . „ . . . . . . . . . . ........... . . . „ .. . ....... . . . .... .. „ . . .
579
586 586 587
12
Spis treści
Odtwarzanie ciszy
„ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...•..... „. . . . . . . . . .„.„„ ..„„ .. „......„„ . . „„. .
Używanie metod językowych
. . . . . .„„ .. „ . . . . . „ . . . . „„ .. „ .. „.„ .. „.„ . . . . „ . . . . . . „ . . . . . . . . .
Tłumaczenie tekstu na inny język Podsumowanie Rozdział
16
.„„ . . . . „„. . . . . . . . . . . . . • . . . . . „ . . „ . . . . . . . . . . „ . . . . . .„ .. . ... ... .
. . . . . . . . . . . . . . . . . „ . . . . „ . . . . „..... „ . . . . „ . . . . . . . . . ...................... „ . . . . . . • . . . . . . . . . . . „. . .
Ekrany dotykowe
. . . . „ . . . . . . . . . . . . . . „.... „. . . . . . . .„ . . . . . . . .„ . „ . . . . . . . . .„„..„.„.„ . . . . .„. . . . .„ „ .
Stosowanie klasy VelocityTracker Analiza funkcji przeciągania Wielodotykowość
.„„ .....„ .......................„..................................„ . . . „ ...........„ . . . .
17
. . . . . . . „ . . . • . „.„.„ . . . . .„ „....„.. „ . . . . . . . „.„ . . . . . „ . . . . .„ ...
. . .. . .. .. . . . . . . .. . ... . . .... „ . . . . . . . . . . . . . . . . . .„.................... „ .............•.....„ •........ „ . . . . . . . . . . . . . .
Podsumowanie Rozdział
....„„....„.„„. . . . .„„„.„„„„. .„„„„. . .„. .......
.„„ . . . ...„ . „ • . . . . „ . . . . . . . . . . . „ . . . . „„.„„„„„„. . .„. . . . . . . „
Obsługa map z a pomocą dotyku Gesty
588 589 599
601 601 613 615 618 625 628 635
......„.„ ••• „.„.....„••„•..„ •.•...•..„„ .•...•.•..•••.••.•...••••....•.•.
Klasa MotionEvents
587
. . . . . . . . . . ..„„. . . . . . . „. . . . . . . . . . . . . . . . . . . „ ...... . . . . • „ . . . . . . . „ . . . . . . . „„ ...„. . . . . „ . . . . . „ ..
Korzystanie ze sklepu Android Market
Jak zostać wydawcą
637 638 638
..............................................
. ... . „. . . . .„...„ ..... „ . . . . „ ... „ . . „.„„„„„ . . . „ . „. . „ . „ „. . . . . . „ . . . . ... . . . . . . . . .
Postępowanie zgodnie z zasadami Konsola programisty
. „ . . . „ „..„ .. „„„„„.„ ..„ .....„ .......... „. . . . . . . . .
.. „ „. . . . . . . . .„„ . . . . . . „ . „ . . . . . . . . „ . . . . . . . „ „. . . . . . . . . . „ . . . . „ . „ . „
Przygotowanie aplikacji do sprzedaży
......... 640 641 641 642
. . . . . . . . . . . . . „ . . „„ ...... „„„ . . .„„.... „„.„.„„„.„
Testowanie kompatybilności wobec różnych urządzeń Obsługa różnych rozmiarów ekranu
. . . . .. . . . . . .„ . „. . . . . . . „
... „. . . . . . . . . .„ . . . . . ...............„. . . . . . . . . „ . . . . . . .
Przygotowanie pliku AndroidManifest.xml do umieszczenia w sklepie Android Market Lokalizacja aplikacji
.„„ .. „„„„„ .„„„„„„„. . .„„. . . . „„„„. . . „„„.„„„„„.
. . . .. . „ . . . . . . . . . . . . . . . • . . . . ..............„„ . . . „„................„„. . . „ .... „.
Przygotowanie ikony aplikacji
. . . . „.„„„„.„.„„ . . „„„.„„„.. „ . . . . . . . . . . •. . . . . . „ .. „ ...
Problemy z płatnymi aplikacjami
.. . „ . . . . „. . . . . „„ ..„. . . . . „. . . . . . . . . . „ . . . . . . . . . . . „. . . . . ..
Kierowanie użytkowników z powrotem do sklepu Przygotowanie pliku .apk do wysłania Wysyłanie aplikacji
.„ .. „„... „ .... „„. . . . . . „ . . . .
. . . . . . . . „ . . . . . . . .„„. . . . . . . . . . .„„. . . . . . .„ ........
„. . . . . .„ „ .... „„. . . . „ ... „. . . . . . . . . . . . . . . . „.„ . . . . . . . . . . „ . . . . „ . „ . . . . . . . . „ .. „ . . . . . .
Wrażenia użytkownika korzystającego ze sklepu Android Market Podsumowanie Rozdział
. .. . . . . .. „ .
.... „ ..... „ .... „. . . . . . . ....„.......... „ . . . . . . . . . . . . „ ..„„ ..... „ . . . . . . . . . . „„. . . . „.„„..„
18 Perspektywy i zasoby Obecny stan Androida
......................................................„ ......... „.......
Sklepy z aplikacjami na system Android
.„„.
.„ . ... . ... . .. .................................. „„„. . . . . . . . . ........................
Porównanie Androida z innymi systemami operacyjnymi Obsługa technologii HTML 5 i co z niej wynika Zasoby związane z systemem Android
„„„„ . . . „ ....
„ ......... „„„.„
.„.„ .... „..... „ .... „ . . • . . . . . . . . „
.....„ ........... „„„ ... „ ..................„ ... . . . . . . . .
Podstawowe zasoby dotyczące systemu Android
.„ .. „ . . . . . . . . . „ . . . . . „. . . „„. . . .
Zasoby związane z aktualnościami ze świata Androida
Skorowidz
651 651 652 653 655 655 657 658 659 659 660 661
. . . . . . . . . . „ . . . . „ . . . . „ . . . . . . . .... „ . . . . .. . . . . ... .
Krótkie podsumowanie mobilnych systemów operacyjnych
Podsumowanie
645 645 648 650
.. . . . . .„ ............. „ . „ . „ . „ .. „„.„.„ ...„„. . . . . . . . „ „ . . . . .„ . . „ „ . . . . . . . . „
Producenci urządzeń mobilnych bazujących na systemie Android Perspektywy Androida
642 643 644 644 645
...... „„„„ . . . . . „„ ...
. .......... „.„„„.. „. . „ . . „ . „ . „ ....„ .. „.„.„.„.. „ ....... „ ........ „„.....•. „ . . . „ . . . .
.............................................................................................
663
O autorach Sayed Y. Hashimi urodził się w Afganistanie, obecnie zaś przebywa w Jacksonville na Florydzie. Ma bogate doświad czenie w dziedzinie ochrony zdrowia, finansów, logistyki oraz architektury zorientowanej na usługi. W swojej karierze zawodowej Sayed projektuje wielkoskalowe aplikacje rozpro szone przy wykorzystaniu różnych języków oraz platform, w tym CIC++, MFC, J2EE oraz .NET. Op ubli kował artykuły w największych czasopismach poświęconych oprogramowa niu oraz napisał kilka innych, popularnych książek dla wy dawnictwa Apress. Sayed posiada tytuł magistra inżynie rii uzyskany na Uniwersytecie Floryda. Można się z nim skon taktować na stronie www.sayedhashimi.com. Satya Komatineni (www.satyakomatineni.com) ma ponad 20-letnie doświadczenie w programowaniu w dużych oraz mniejszych przedsiębiorstwach. Satya opublikował ponad 30 artykułów dotyczących projektowania stron WWW przy użyciu technologii Java, .NET oraz baz danych. Jest częstym prelegentem na konferencjach przemysłowych dotyczących innowacyjnych technologii oraz regularnie umieszcza wpisy na blogach w serwisie java.net. Jest także twórcą AspireWeb (www.activeintellect.com/aspire), czyli uproszczonego narzę dzia posiadającego jawny kod źródłowy, służącego do projek towania stron WWW w języku Java, oraz Aspire Knowledge Central (www.knowledgefolders. com) - siec iowego systemu operacyjnego o jawnym kodzie źródło"qm, nastawionego na efektywność oraz możliwość publikowania przez poje dyncze osoby. Satya jest również członkiem wielu programów SBIR (ang. Small Business Innovation Research Program Program rozwoju innowacji w m ałych przedsiębiorstwach). Uzyskał stopiei'l licencjata inżynierii elektrycznej na Uni wersytecie Andhra w Visakhapatnamie oraz tytuł magistra inżynierii elektrycznej w Indyjskim Instytucie Technologicz nym w Nowym Delhi.
14
Android 2. Tworzenie aplikacji
Dave MacLean jest inżynierem oprogramowania oraz ar chitektem obecnie mieszkającym i pracującym w 'acksom-ille na Florydzie. Od 1980 roku zajmuje się programowaniem w wielu językach oraz projektowaniem systemow, od au tomatyzacji robotów do przechowywania danych, automa tycznie obsługiwanych aplikacji sieciowych oraz procesorów transakcji EDI. Dave pracował dla takich firm, jak Sun Microsystems, IBM, Trimble Navigation, General Yl:otors oraz kilku mniejszych przedsiębiorstw. Ukończył studia na Uniwersytecie Waterloo w Kanadzie, uzyskując tytuł z in żynierii projektowania systemów. Zapraszamy na naszą stronę
http://androidbook. com.
I nformacje o recenzencie tech nicznym Vikram Goyal jest projektantem op rogramowania mieszka jącym w Brisbane w Australii, poświęcającym część swojego czasu na wychowanie dzieci. Można się z nim skontaktować e-mailowo
[email protected] . -
Podziękowania Napisanie tej książki wymagało włożenia wysiłku nie tylko ze strony autorów, lecz również od części bardzo utalentowanego zespołu wydawnictwa Apress, a także ze strony recenzenta technicznego. Chcieliśmy zatem podziękować Steve'owi Anglin, Douglasowi Pundick, Fran Pameli, Elizabeth Berry oraz Brigid Duffy z wydawnictwa Apress. Chcieliśmy także wyrazić nasze uznanie dla recenzenta technicznego, Vikrama Goya!, za pracę, jaką włożył w tę książkę. Jego komentarze oraz poprawki były bezcenne. W końcu autorzy są głęboko wdzięczni swoim rodzinom za wyrozumiałość podczas dłużącego się pisania książki.
Przedmowa Pomyśl. Zakoduj. Napisz. Poprawiaj i powtarzaj w nieskończoność. Oto mantra pisarza technicznego. Technologia zmienia się tak szybko, że zanim autor skończy pisać ostatnie zdanie, należy je zmodyfikować. Czytelnicy tekstów technicznych są prawdopodobnie świa domi tego faktu, a jednak niektórzy poświęcają swój czas, żeby zakupić tę książkę i ją prze czytać. Ponadto część z tych osób decyduje się, aby przeczytać przedmowę. Oznacza to, że nie są one wyłącznie programistami siedzącymi po nocach, lecz pragną również poznać technologię stojącą za technologią. Bardzo dobrze, i gratulujemy udanej inwestycji. Pozwolę sobie wyjaśnić, dlaczego zakup tej książki okazał się właściwy. Jest to najlepszy podręcznik na rynku dotyczący nauki systemu Android. Posiada on tak wiele rozdziałów, naszpikowanych informacjami o Androidzie, że niejedna osoba będzie sobie często gratulowała zakupu tej książki. Jestem jej recenzentem technicznym i, szczerze powiedziawszy, żałuję, że nie mogłem edytować większej ilości treści - autorzy wykonali tak dobrą pracę, że właściwie nie miałem czego poprawiać (kilkakrotnie jednak zdarzyło mi się ich wyzywać za ilość informacji, które zmieścili w jednej książce, przez co nieraz miałem zwiększone obciążenie robocze aż do ostatniej minuty). Ale moja strata jest dla Czytelnika zyskiem: w książce tej znajdują się prawdopodobnie wszystkie informacje potrzebne do zro zumienia Androida. Wystarczy spojrzeć na spis treści. Tradycja wymaga, żebym napisał coś na temat samego Androida, tematu niniejszej książki. Oczywiście większość Czytelników ma już pewne pojęcie o tym środowisku - że jest sys temem operacyjnym firmy Google, który ma rywalizo;vać z iPhonem o dominację na rynku - dlatego większość Czytelników kupiła ten podręcznik. Android, jako technologia, wyrósł już z etapu potajemnego dźgania w ciemności i teraz, po zapowiedzi smartfonu Nexus One, telefonu projektowanego przez firmę Google, stanowi potęgę zdolną do rywalizacji. Rok 2010 będzie rokiem walki pomiędzy firmami Apple i Google o dominację na rynku przeno
śnych telefonów. Istnieje wystarczająco dużo miejsca dla obydv.ru technologii, jednak dzięki olbrzymiej przewadze firmy Google w internecie środek ciężkości przesunie się na nieko rzyść dla firmy Apple. Czytelnicy, biorący pod uwagę olbrzymi rynek otwierający się dla Androida, wykonali wła śnie pierwsze dwa kroki: a) Postanowili poświęcić się projektowaniu aplikacji na ten system, oraz b) Wybrali najlepszy podręcznik na rynku, poświęcony opanowaniu Androida. Pozo stał jeszcze ostatni krok do wykonania: przewrócenie strony i zdobycie wiedzy o możliwo ściach Androida. Vikram Goya!
[email protected] www.craftbits.com
Styczeń 2010 Brisbane, Australia
ROZDZIAŁ
1 Wprowadzenie do platformy obliczeniowej Android
Obliczenia stają się coraz bardziej „osobiste'', coraz częściej dostępne w dowol nym miejscu oraz w dowolnej chwili. Rozwój w tym kierunku jest najbardziej odczuwalny w przypadku urządzeń przenośnych, przekształcających się w plat formy obliczeniowe. Telefony komórkowe nie służą już wyłącznie do rozmawiania - od pewnego czasu posiadają możliwość przenoszenia danych oraz multime diów. Znamienny jest fakt, że urządzenia przenośne zyskują olbrzymie możli wości obliczeniowe, uprawniające je do uzyskania statusu komputera osobiste go (ang. personal computer - PC). Przewidujemy także, że wielu tradycyjnych producentów, takich jak ASUS, HP czy Dell, zacznie produkować różnorodne urządzenia oparte na systemie Android. Front wojenny pomiędzy systemami operacyjnymi, platformami obliczeniowymi, językami programowania oraz ramowymi modelami projektowania jest przenoszony oraz wdrażany w rejony urządzeń mobilnych. Spodziewamy się także zwiększenia ilości programów tworzonych na urządze nia przenośne w przemyśle informatycznym, gdyż coraz więcej aplikacji biu rowych będzie posiadało odpowiedniki na rynku mobilnym. Osobom pragnącym przyczynić się do rozwoju tego trendu zademonstrujemy sposoby zastosowania języka Tava przy pisaniu programów na urządzenia obsługujące platformę An droid firmy Google (http://developer.android.com/index.html). Jest to środowi sko o jawnym kodzie źródłowym, służące do tworzenia aplikacji na przenośne urządzenia. Android stanowi powód do ekscytacji, ponieważ zaprezentowano w nim wiele nowych paradygmatów dotyczących projektowania struktury apli kacji (pomimo ograniczeń platformy mobilnej). W tym rozdziale zajmiemy się opisem cech Androida oraz zestawu do tworze nia oprogramowania na tę platformę, zostaną krótko scharakteryzowane jego najważniejsze elementy, w skrócie przytoczymy tematykę poszczególnych roz działów', pokażemy, w jaki sposób korzystać z kodu źródłowego Androida oraz przedstawimy zalety projektowania aplikacji na tę platformę.
Android 2.
20
Tworzenie aplikacji
Nowa platforma d la nowego typu komputera osobistego Wspaniałą wieścią dla programistów jest informacja, że dotychczas wyspecjalizowane urzą dzenia, takie jak telefony komórkowe, obecnie mogą zostać zaliczone do czcigodnego grona platform obliczeniowych ogólnego przeznaczenia (rysunek 1 . 1 ). W ten sposób przenośne urzą dzenia stają się dostępne dla języków programowania ogólnego przeznaczenia, dzięki czemu powiększeniu ulegają zakres oraz udziały rynkowe aplikacji przeznaczonych dla tych urządzeń. KlubObliczeniowy Ogólnego Przeznaczenia
Mainframe
Serwer
Stacja robocza
Laptop
Rysunek 1 . 1 . Handheld jest nowym rodzajem komputera osobistego Platforma Android wdraża ideę obliczeń ogólnego przeznaczenia do urządzeń typu handheld. Jest to wszechstronne środowisko, zaopatrzone w system operacyjny oparty na Linuksie, zajmujący się zarządzaniem urządzeniami, pamięcią oraz procesami. Biblioteki Androida obsługują funkcje telefonu, wideo, graficzne, programowania interfej su użytkownika oraz wiele innych aspektów urządzenia.
,..
" l• 'l'l "fil [o::... lil l •.
Chociaż platforma Android została zaprojektowana pod kątem urządzeń przenośnych, posiada szkielet pełnoprawnego systemu operacyjnego. Firma Google udostępnia ten szkielet programistom języka Java poprzez zestaw SOK (ang. Software Developer Kit zestaw do tworzenia oprogramowania) o nazwie Android SOK. Podczas pracy na tym zestawie rzadko pojawia się wrażenie, że tworzy się aplikację na urządzenie przenośne, ponieważ jest dostępna większość bibliotek klas, używanych na stacji roboczej lub serwerze - wśród nich relacyjne bazy danych.
Zesta;v Android SDK obsługuje w większości platformę Java Standard Edition (Java SE), wyłączywszy narzędzia Abstract Window Toolkit (A WT) oraz Swing. Zamiast tych narzędzi Android SDK został zaopatrzony we własny, obszerny, nowoczesny szkielet interfejsu użyt kownika. Ponieważ językiem programowania jest Java, należy zaopatrzyć się w środowisko JVM (ang. Java Virtual Machine - wirtualna maszyna Java), odpowiedzialne za interpre towanie uruchomionego kodu bajtowego. Generalnie dzięki środowisku JVM uzyskujemy niezbędną optymalizację, pozwalającą osiągnąć wydajność porównywalną do wydajności aplikacji skompilowanych w językach takich jak C oraz C++. Android zawiera własne, zop tymalizowane środowisko JVM, umożliwiające uruchomienie skompilowanych plików kla sy Java w celu określenia takich ograniczeń urządzenia typu handheld, jak pojemność pa mięci, szybkość procesora oraz moc. Ta wirtualna maszyna, nazwana Dalvik VM, zostanie dokładniej omówiona '" podrozd7.iale „Zapoznanie ze środowiskiem Dalvik VM".
Rozdział 1 • Wprowadzenie do platformy obliczeniowej Android
21
Podobieństwo języka Java d o jego wersji stosowanej w komputerach P C oraz jego prostota połączeniu z rozbudowaną biblioteką klas Androida sprawiają, że jest to bardzo atrakcyjna platforma programistyczna.
K
:\a rysunku J .2. został ukazany stos programovvy Androida (więcej informacji na ten temat można znaleźć w podrozdziale „Stos programowy Androida".
Aplikacje użytkownika
Biblioteki Java Aktywności/usługi Ul/grafika/widoki
Zasoby/dostawcy treści
Telefon/aparat fotograficzny
Multimedia Baza danych SQLite Http/łączność Java SE/Java Apache
DalvikVM Podstawowe biblioteki C Linux
Rysunek 1 .2. Wysokopoziomowy widok stosu programowego Androida
Historia Androida ?:-zyjrzyjmy się, w jaki sposób Android pojawił się w świecie mobilnych systemów opera0jnych. Na telefony komórkowe stworzono wiele różnych systemów operacyjnych, takich ak Symbian OS, Windows Mobile firmy Microsoft, Mobile Linux, iPhone OS (stworzony oparciu o system Mac OS X), Moblin {od firmy Intel) oraz wiele innych, opatentowanych srodowisk. Do tej pory żaden z tych systemów nie stał się faktycznym standardem. Dostępne ;..,terfejsy API oraz środowiska projektowania aplikacji na urządzenia przenośne są zbyt ograniczone i pozostają w tyle w porównaniu z analogicznymi szkieletami, dostępnymi na s:acjach roboczych. W tym momencie pojawia się firma Google. Platforma Android była obietnicą otwartości, przystępności, jawności kodu źródłowego oraz nowoczesności szkie '.etu projektowania. · -
22
Android 2. Tworzenie aplikacji
W 2005 roku firma Google wykupiła młode przedsiębiorstwo Android Inc„ które rozpo częło projektowanie platformy Android (rysunek 1.3). Wśród najważniejszych pracowników firmy Android Inc. byli w owym czasie Andy Rubin, Rich Miner, Nick Sears oraz Chris White.
lłlł& ·rlrnz •mm�ml&m • VW • . ... . 11 111µ;1 • .-. 2008
8 200
2005
•.
' J '
Rysunek 1 .3. Chronologia powstawania Androida
Pod koniec 2007 roku grupa przemysłowych liderów utworzyła wokół platformy Android zrzeszenie Open Handset Alliance (http://www.openhandsetalliance.com). Niektórzy człon kowie tego zrzeszenia to: •
Sprint Nextel
• T-Mobile • Motorola • •
Samsung Sony Ericsson
•
Toshiba Vodafone • Google • Intel • Texas Instruments •
Niektórymi z celów zrzeszenia były szybkie wprowadzanie innowacji oraz lepsza odpowiedź na potrzeby konsumentów, a jednym z jego pierwszych ważnych osiągnięć była platforma Android. Została ona zaprojektowana w celu zaspokojenia potrzeb operatorów sieci ko mórkowych, producentów urządzeń oraz twórców aplikacji. Członkowie zobowiązali się wydać istotną własność intelektualną poprzez posiadającą j awny kod źródłowy licencję Apache License 2.0.
••wwr• 111111 1 • •· -11
Producenci przenośnych urządzeń nie muszą uiszczać opłat licencyjnych za umieszczenie Androida w telefonach lub innych urządzeniach.
Zestaw Android SDK został wydany jako „wczesna wersja" w listopadzie 2007 roku. We wrześniu 2008 roku firma T-Mobile zapowiedziała ·wydanie T-Mobile GI, pierwszego smartfonu bazującego na platformie Android. Kilka dni później firma Google ogłosiła v..ry puszczenie zestawu Android SDK Release Candidate 1.01 . W październiku 2008 roku firma Google udostępniła kod źródłowy platformy Android w ramach licencji Apache. 1
-
Release Candidatc to niemal finalna wersja oprogramowania, w której mogą jeszcze zostać wprowadzone drobne poprawki
przyp. tłum.
Rozdział 1 • Wprowadzenie do platformy obliczeniowej Android
23
·,
. trakcie tworzenia Androida jednym z najważniejszych celów było umożliwienie współ
? racy różnych aplikacji ze sobą, a także wielokrotnego wykor zystywania składników jednej
aplikacji przez inną. Takie u żywanie fragmentów innych programów dotyczy nie tylko !ug, lecz również danych oraz interfejsu Ul (ang. user interface - interfejs użytkownika). '\' efekcie platfo rma Android posiada szereg funkcji konstrukcyjnych , dzięki którym jej "ltwartość stała się rzeczywistością. Niektóre z tych funkcji zostaną omówione w rozdziale 3. JS
:\ndroid przyciągnął wcześnie wielu zwolenników również dlatego, gdyż posiada w pełni zaprojektowane narzędzia, umożliwiające wykorzystanie modelu przetwarzania w chmurze ang. cloud computing) udostępnionego przez zasoby sieciowe, oraz usprawniające funkcjo nowanie lokalnych magazynów danych w samym urządzeniu przenośnym. Na ciepłe przy �cie Androida wpływ miała również możliwość obsługi relacyjnych baz danych przez urzą .izenie przenośne.
?od koniec 2008 roku firma Google wydała urządzenie typu handheld Android Dev Phone 1 , ?
·:e wrześniu 2009 roku pojawiła się we rsj a 1 .6 systemu Android, a w przeciągu miesiąca została wydana wersj a opatrzo n a numerem 2.0, dzięki czemu nastąpił przedświąteczny v"y .e\,. urządzeń obsługujących ten system. W tej wersji zaprezentowano funk cje zaawansowa ::ego wyszukiwani a danych oraz przetwarzania tekstu na mowę (funkcja przetwarzania tck ,:u na mowę jest opisana w rozdziale 1 5„ a v.ryszukiwaniu w systemie Android poświęcono :-...zdział 14.). Została również wprowadzona obsługa gestów oraz ekran wielod o tykowy. ....bydwie funkcje są omówione w rozdziale 16. ·
::>zięki obsłudze języka HTML 5 system Android 2.0 posiada interes ujące możliwośc i wyko "'Z'·stania stron WWW. Zostały one opisane w rozdziale 17„ poświęconym oprogramowa l:"-U Titanium Mobile. Codziennie pojawiają się nowe aplikacje oparte na systemie Android, -' również nowe rodzaje sklepów z aplikacjami niezależnych od obsługiwanego przez firr:ę Google sklepu internetowego Android Market, które są opisane w rozdziale 18. W roz .:„nale 19. jest przeprowadzona analiza sytuacji Androida na rynku urządzeń przenośnych.
Zapoznanie ze środowiskiem Dalvik VM �
24
Android 2. Tworzenie aplikacji
Hił\ifa++ Wydany pod koniec 2008 roku telefon T-Mobile G 1 posiada 1 92 MB pamięci RAM, kartę SD o p ojem n ości 1 GB oraz procesor Qualcomm MSM7201 A o taktowaniu 528 MHz. Wypuszczony rok później telefon Motorola Droid zawiera 256 M B pamięci RAM, 16 GB pamięci na karcie MicroSD oraz procesor Arm Cort ex o częstotliwości 550 MHz. Dla porównania, najtańszy laptop firmy Dell p osiada dwurdzeniowy procesor taktowany zegarem 2.1 GHz oraz 4 GB pamięci RAM.
Jak widać, wymagania dotyczące wydajności są bardzo surowe, przez co projektanci muszą optymalizować wszystkie możliwe elementy aplikacji. Jeżeli przyjrzeć się liście pakietów Androida, można zauważyć, że są one doskonale wyposażone oraz jest ich bardzo wiele. Według firmy Google, biblioteki systemowe mogą wykorzystywać od 1 0 MB do 20 MB pa mięci, nawet przy zastosowaniu zoptymalizowanego środowiska JVM. Powyższe problemy sprawiły, że firma Google musiała ponownie przyjrzeć się w nowym świetle standardowej implementacji JVM (osobą odpowiedzialną za implementację JVM firmy Google jest Dan Bornstein, twórca Dalvik VM - Dalvik jest nazwą islandzkiego mia steczka). Po pierwsze, Dalvik VM pobiera wygenerowane pliki klas Java i przetwarza je na jeden lub więcej plików wykonawczych Dalvik (.dex). Następnie wykorzystuje powtarzające się informacje z różnych plików klas i w ten sposób wydajnie zmniejsza zużycie pamięci o połowę w stosunku do tradycyjnego pliku .jar (nieskompresowanego). Na przykład plik .dex przeglądarki internetowej w Androidzie ma rozmiar około 200 KB, podczas gdy analogiczny, nieskompresowany plik .jar zawiera około 500 KB kodu. Plik .dex aplikacji budzika ma około 50 KB, a jego wersja .jar jest dwukrotnie większa. Po drugie, firma Google usprawniła zarządzanie niepotrzebnymi plikami w środowisku Dalvik JVM, jednak we wczesnych edycjach zdecydowała się pominąć kompilator JIT (ang. just-in-time - dokładnie na czas). Kod źródłowy wersji 2.0 posiada procedury niezbędne do obsługi kompilatora JIT, jednak nie zostały one uaktywnione w ostatecznej wersji. Prze widuje się, że kompilator JIT będzie dostępny w przyszłych edycjach. Producent usprawie dliwia tę decyzję obecnością wśród podstawowych bibliotek (w tym bibliotek graficznych) Androida wielu bibliotek zaimplementowanych w językach C oraz C++. Na przykład inter fejsy API w grafice Java są w rzeczywistości cienkimi klasami osłonowymi wokół kodu ma cierzystego, wykorzystującymi interfejs JNI (ang. Java Native Interface - macierzysty interfejs Java). W podobny sposób wykorzystywana jest zoptymalizowana, macierzysta biblioteka, oparta na języku C, służąca do uzyskania dostępu do bazy danych SQLite, jednak ta biblio teka jest obsługiwana przez interfejs Java API wyższego poziomu. Ponieważ większość pod stawowego kodu została napisana w językach C oraz C++, firma Google uznała, że wpro wadzenie kompilacji JIT nie będzie miało istotnego wpływu. W końcu po trzecie, Dalvik VM wykorzystuje inną metodę generowania kodu maszynowego, w której podstawowymi jednostkami przechowywania danych są rejestry, a nie stosy. Firma Google ma nadzieję, że w ten sposób ilość instrukcji zostanie zmniejszona o 30 procent. Należy zauważyć, że w przypadku Androida ostateczny plik wykonawczy oparty jest nie na kodzie bajtowym Java, a na plikach .dex, właśnie dzięki środowisku Dalvik VM. Oznacza to, że nie można bezpośrednio uruchomić kodu bajtowego Java; najpierw należy uruchomić pliki klas Java, a następnie przekonwertować je na pliki .dex. Taka paranoja związana z v.rydajnością dotyczy także pozostałych elementów zestawu An droid SDK. Na przykład wykorzystuje on powszechnie język XML do definiowania wyglądu interfejsu użytkownika. Jednak pliki XML zostają przekonwertowane na pliki binarne, za nim zostaną zapisane na urządzeniu. Android posiada specjalne mechanizmy, umożliwiające
Rozdział 1 • Wprowadzenie do platformy obliczeniowej Android
25
orzystanie z tych plików XML. Podczas rozpatrywania cech konstrukcyjnych Androida :::!.leży zadać sobie następujące pytanie: W jaki sposób można porównać i odróżnić Androida o
Porównanie platform Android oraz Java ME 'a.'-: można zauważyć, Android stanowi kompleksową, \.\')'Specjalizowaną oraz zwartą tech - logię, wykraczającą poza standardowe rozwiązania oferowane przez środowisko JVM. �atforma Android zawiera w jednym pakiecie wszystko, co jest wymagane do tworzenia opro ;:amowania: system operaC)iny, stemwniki urządze1'1, podstawowe biblioteki, interfejs JNI, :;:.o?t)malizowane środowisko Dalvik VM oraz środowisko projektowe Java. Twórcy oprogra ::::o\,·ania mogą mieć pewność, że wszystkie kluczowe biblioteki będą dostępne w urządzeniu.
::iaczej jest w przypadku pozostałych środowisk przeznaczonych dla urządzeń przenośnych, -:::. przykład Java ME. Przyjrzyjmy się pokrótce temu środowisku, zanim przejdziemy do �!"ównania obydwu technologii Rysunek 1.4 przedstawia dostępność środowiska Java dla róż =··ch rodzajów urządzeń obliczeniowych. Platforma Java Enterprise Edition (Java EE) prze z::aczona jest dla konfiguracji serwerowych. Wersje platformy Java
� Vamframe
Serwer
s. Stacja
l Laptop
robocza
Podłączone urządzenie
PDA/telefon/ urządzenie
� Nieregularnie podłączane urządzenie
konsumenta
multimedialne
�15 u nek 1 .4. Wersje
platformy Java
••
..:i:iorma Java Micro Edition (Java ME) jest wersją przystosowaną do mniejszych urządzeń. �eją dwa zestawy konfiguracyjne platformy Java ME. Pierwszy z nich to konfiguracja Con -=--ced Device Configuration (CDC). Java ME posiadająca konfigurację CDC stanowi platformę ""':! SE, z której usunięto część pakietów oraz bibliotek, a nawet niektóre pola i metody w do �nych klasach. Dla urządzeń posiadających jeszcze większe ograniczenia przygotowano .:ulgurację Connected Light Device Configuration (CLDC). Na rysunku 1.5 zostały przed �·\·ione dostępne interfejsy API dla różnych konfiguracji platformy Java.
26
Android 2. Tworzenie aplikacji
M@iM Javax m1croed1tion ",
Rysunek 1.5. Dostępność interfejsów API w środowisku Java
Każdy dodatkowy pakiet instalowany na bazowych interfejsach API konfiguracji CDC oraz CLDC jest traktowany jako „profil", znormalizowany przy użyciu procesu JSR. Dzięki każ demu dodatkowemu profilowi projektant uzyskuje nowy zestaw interfejsu API.
1·1ifdddl
Konfiguracje CDC oraz CLDC mogą obsługiwać część interfejsów API spoza środowiska Java SE, których nazwy klas nie muszą koniecznie rozpoczynać się od przestrzeni nazw Java.". Należy pamiętać, że w przypadku korzystania na komputerze z programu napisanego w języku Java nie ma gwarancji, że uruchomi się on na urządzeniach obsługiwanych jedynie przez wersję Java ME.
Platforma Java CLDC jest przechowywana w wyspecjalizowanym oraz mocno zredukowa nym środowisku JVM, zwanym KVM (ang. K Virtual Machine wirtualna maszyna K), umożliwiającym działanie na maszynach posiadających zaledwie 1 28 KB pamięci (litera „K" w nazwie KVM oznacza kilobajty). Konfiguracja CLDC może uruchamiać dodatkowe in terfejsy API dzięki profilowi MIDP 2.0 (ang. Mobile lnformation Device Profile). Profil ten zawiera pakiety z przestrzeni nazw javax.microedition. *. Podstawowymi pakietami są MIDlety (proste aplikacje), pakiet interfejsu UJ zwany LCDUI, gry oraz media. -
Wśród interfejsów API konfiguracji CDC znajdują się następujące: java.awt, Java.net, do datkowe interfejsy API zabezpieczeń oraz wszystkie interfejsy API z konfiguracji CLDC. Dodatkowe profile, dostępne na wierzchu ko nfigu ra cj i CDC, dają dostęp programistom aplikacji do interfejsu API javax.microedition.xlet ()Uety są reprezentacjami aplikacji w konfigu racji CDC). Na szczycie konfiguracji CDC można znaleźć około dziesięciu dodatkowych, uruchamialnych pakietów, takich jak Bluetooth, Media API, OpenGL for Embedded Sys tems (OpenGL ES), Java API for XML Processing (JAXP), JAXP-RPC, Java 20, Swing, Java Remote Method Invocation (Java RMI), Java Database Connectivity (JDBC) oraz Java API. Generalnie specyfikacja Java ME obejmuje ponad 20 specyfikacji JSR. Wedle prognoz rów nież technologia JavaFX (http://javafx.com) będzie odgrywała coraz bardziej znaczącą rolę na rynku urządzeń przenośnych.
Rozdział 1 • Wprowadzenie do platformy obliczeniowej Android
27
Java FX jest próbą drastycznego zwiększenia funkcjonalności apletów I Jf Technologia przeglądarkach przez firmę Sun. Oferowany jest bardziej przystępny dla ·
w
projektantów model deklaracyjnego programowania interfejsu u żytkownika.
P"
omówieniu środowiska Java ME nadszedł czas na porównanie go z Androidem: • Wiele konfiguracji urządzenia. Srodowisko Java ME przeznaczone jest dla dwóch grup mikrourządzeń oraz oferuje znormalizowane i oddzielne standardy dla każdej z nich. Z drugiej strony, platforma Android przeznaczona jest wyłącznie dla jednego modelu. Nie da się jej uruchomić na urządzeniach posiadających gorsze parametry techniczne, dopóki te charakterystyki nie zostaną zmodernizowane. •
Przystępność. Ponieważ Android jest przeznaczony tylko dla jednego modelu urządzenia, łatwiej ten system zrozumieć, niż platformę Java ME. Java ME posiada wiele modeli interfejs u Ul dla każdej konfiguracji, w zależności od funkcji dostępnych w urządzeniu, takich jak: MIDlety, xlety, biblioteka AWT oraz Swing. Trudniej jest zrozumieć specyfikacje JSR dla każdej konfiguracji Java ME. Trzeba do nich dłużej dojrzewać oraz ciężko znaleźć dla nich implementacje.
• Czas reakcji. Teoretycznie środowisko Dalvik VM jest bardziej zoptymalizowane oraz posiada szybszy czas odpowiedzi od standardowej maszyny JVM obsługiwanej w urządzeniu posiadającym podobne parametry. Można porównać Dalvik VM do KVM, jednak środowisko KVM jest używane na niskopoziomowych urządzeniach, posiadających o wiele mniej pamięci. • Kompatybilność z językiem Java. Z powodu obecności środowiska Dalvik VM Android obsługuje kod bajtowy w formie plików o rozszerzeniu .dex zamiast tradycyjnego kodu bajtowego. Nie powinno to stanowić większego problemu, dopóki język Java jest kompilowany do standardowych plików klas Java. Jedynie interpretacja wykonalności kodu bajtowego Java jest niemożliwa. • Adopcja. Bardzo wiele telefonów komórkowych posiada możliwość obsługi środowiska Java ME. Jednak jednorodność, niski koszt oraz łatwość projektowania na platformę Android stanowią kuszące zalety dla programistów Java. • Obsługa środowiska Java SE. W porównaniu do obsługi środowiska Java SE w konfiguracji CDC platforma Android posiada pod tym względem nieco więcej możliwości, nie licząc braku bibliotek AWT oraz Swing. Jak zostało wcześniej wspomniane, Android posiada własne wersje tych brakujących bibliotek. W rzeczywistości deklaracyjny interfejs użytkownika bardziej przypomina zaawansowane platformy Ul, takie jak Microsoft Silverlight lub JavaFX firmy Sun.
Stos programowy Androida :ej pory zajmowaliśmy się historią Androida oraz jego funkcjami optymalizacyjnymi, w tym �e środowiskiem Dalvik VM; wspomnieliśmy także o dostępności stosu programowego a. W obecnym podrozdziale chcielibyśmy zająć się aspektem projektowania w Andro ..::::...e. �ajlepszym miejscem, od którego można rozpocząć, jest rysunek l . 6. „
�
.:.zcniem platformy Android jest jądro Linuksa w wersji 2.6, odpowiedzialne za sterowniki �-z
28
Android 2. Tworzenie aplikacji
ApJ� i 1 Aktywności
Animacja
OpenGL
Widoki
Telefonia
Aparat fotograficzny
Zasoby
Dostawcy treści
SQLite
Biblioteki natywne SQLlte
Srodowisko wykonawcze Androidu
Dalv1kVM OpenGL
WebK1t
FreeType
Graphics
Sterowniki urządzenia
Jadro Linuksa
Rysunek 1 .6. Szczegółowy stos progra mowy zestawu Android SOK
I
komunikacja mię flash, audio oraz komunikację IPC (ang. Inteprocess Communication dzyprocesowa). Chociaż rdzeniem systemu jest Linux, większość aplikacji - jeśli nie wszystkie - w urządzeniach takich jak T-Mobile Gl oraz Motorola Droid jest projektowa na w języku Java oraz uruchamiana w środowisku Dalvik VM. -
Na kolejnym poziomie, ponad rdzeniem Linuksa, umieszczona została duża ilość bibliotek CIC++, wśród których znajdują się biblioteki OpenGL, WebKit, FreeType, SSL (ang. Secure Sockets Layer - warstwa bezpiecznych gniazd), biblioteka vvykonawcza języka C (libc), SQ Lite oraz Media. Biblioteka systemowa języka C oparta na systemie Berkeley Software Di stribution (BSD) jest dopasowana (obcięta o ponad połowę pierwotnej objętości) do urzą dzeń posiadających wbudowany system bazujący na Linuksie. Biblioteki multimediów oparte są na standardzie OpenCORE PacketVideo (http://www.packetvideo.com). Są one odpowiedzialne za nagrywanie oraz odtwarzanie formatów audio i wideo. Biblioteka Surfa ce Manager kontroluje dostęp do systemu wyświetlania, a także obsługuje grafikę dwu- oraz trójwymiarową. Biblioteka WebKit odpowiada za obsługę przeglądarki; to właśnie ona obsługuje przeglą darki Google Chrome oraz Safari. Biblioteka FreeType zajmuje się obsługą czcionek. SQLite (http://www.sqlite.org) jest relacyjną bazą danych dostępną na samym urządzeniu przeno śnym. Jest to także forma niezależnej, posiadającej jawny kod źródłowy, technologii relacyj nej bazy danych niezwiązanej bezpośrednio z Androidem. Można również pobrać narzędzia przeznaczone dla bazy SQLite i używać ich do baz danych Androida. Większość szkieletu aplikacji uzyskuje dostęp do tych bibliotek podstawowych poprzez śro dowisko Dalvik VM, stanowiące bramę do platformy Android. Jak zostało wyjaśnione w po przednich podrozdziałach, środowisko Dalvik zostało zoptymalizowane do jednoczesnego uruchamiania wielu instancji wirtualnych maszyn. Podczas uzyskiwania dostępu do pod stawowych bibliotek przez aplikacje Java każda z tych aplikacji otrzymuje własną instancję maszynyVM.
Rozdział 1 • Wprowadzenie do platformy obliczeniowej Android
29
Główne biblioteki interfejsu API środowiska Java w Androidzie obejmują telefonię, zasoby, lokacje, interfejs użytkownika, dostawców (dane) treści oraz menedżery pakietów (instala cja, zabezpieczenia i tak dalej). Programiści projektują aplikacje dla użytkownika ostatecz nego w górnej warstwie tego interfejsu API. Przykładami takich aplikacji są Home, Contacts, Phone, Browser i tak dalej. Android posiada również własną bibliotekę graficzną Skia, zawierającą dwuwymiarową gra rlkę firmy Google oraz napisaną ·w językach C i C++. Tworzy ona również rdzeń przeglądarki Google Chrome. Jednak interfejsy API odpowiedzialne za grafikę trójwymiarową bazują w Androidzie na implementacji pakietu OpenGL ES grupy Khronos (http://www.khronos.org). Pakiet ten zawiera podzbiory funkcji OpenGL, których adresatami są wbudowane systemy. . Pod kątem multimediów platforma Android obsługuje najpopularniejsze formaty obrazów, dźwięków oraz wideo. Z perspektywy sieci bezprzewodowych dostępne są interfejsy A PI obsługujące sieci Bluetooth, EDGE, 3G, WiFi oraz telefonię GSM (ang. Global System for Mobile Communication - globalny system komunikacji mobilnej), w zależności od parametrów �przętowych urządzenia.
Projektowanie aplikacji dla użytkownika ostatecznego za pomocą zestawu Android SOK . obecnym podrozdziale zostaną zaprezentowane wysokopoziomowe interfejsy Java, słu zą.:e do projektowania aplikacji przeznaczonych dla użytkownika końcowego na platformie ."illdroid. Krótko omówimy emulator Androida, podstawowe składniki środowiska Andro ...:. programowanie interfejsu Ul, usługi, multimedia, telefonię, animacje oraz technologię JpenGL. Zaprezentujemy również przykładowe fragmenty kodu. ·,
Emulator Androida :estaw Android SDK posiada wtyczkę organizacji Eclipse, nazwaną narzędziami ADT (ang. -. :droid Development Tools - narzędzia projektowe dla środowiska Android). To środowi1'0 IDE (ang. Integrated Development Environment - zintegrowane środowisko projektoe będzie używane do projektowania, usuwania błędów oraz testowania aplikacji Java z.:: zegółowe informacje na temat narzędzi ADT znajdują się w rozdziale 2.). Można rów c..ez używać zestawu Android SDK bez używania narzędzi ADT; wykorzystywane są wtedy .::.;:z ędzia wiersza poleceń. Obydwie metody pozwalają na uruchamianie, usuwanie błędów -11 testowanie aplikacji. W 90% przypadków nie będzie nawet potrzebne fizyczne urzą....ć.: :tie do projektowania aplikacji. Emulator Androida naśladuje większość funkcji urzą -=:iia. Do ograniczeń emulatora należą połączenia USB, robienie zdjęć oraz nagrywanie ao, słuchawki, symulacja baterii oraz sieć Bluetooth. E.:::!.!lator Androida spełnia swoje zadanie dzięki posiadającej jawny kod źródłowy techno p „emulacji procesora", nazwanej QEMU (http://bellard.org!qemu), zaprojektowanej --uz Fabrice Bellard. Ta sama technologia umożliwia emulację jednego systemu operacyj-"'P wewnątrz drugiego bez względu na działanie procesora. QEMU pozwala na emulację na nomie jednostki centralnej. :"::.ęki emulatorowi Androida procesor przechodzi w tryb technologii ARM (ang. Advanced � ·:c .\1achine - zaawansowana maszyna RISC). ARM jest architekturą procesora 32�-.,·ego, działającego w oparciu o technologię RISC (ang. Reduced Instruction Set Computer
30
Android 2. Tworzenie aplikacji
- komputer z ograniczonym zestawem instrukcji). Technologia ta umożliwia uzyskanie prostoty projektowania oraz szybkości poprzez ograniczenie ilości instrukcji w zestawie in strukcji. Emulator powoduje uruchomienie systemu Linux, dostępnego na platformie An droid, na takim symulowanym procesorze.
Wiele nowoczesnych graficznych oraz badawczych stacji roboczych, produkowanych
• •• • •s.11 00 1 r,•r1• ·• • przez firmy HP oraz Sun, jest opartych na zaawansowanych procesorach RISC. , .
Technologia ARM jest szeroko stosowana w handheldach oraz w innych urządzeniach elektronicznych, w których istotne jest niskie zużycie energii. Większość rynku urządzeń przenośnych wykorzystuje procesory oparte na tej architekturze. Na przykład urządzenie Apple Newton bazowało na procesorze ARM6. Takie urządzenia jak iPod, Nintendo DS oraz Game Boy Advance działają w oparciu o architekturę ARM 4 i zbudowane są z około 30 OOO tranzystorów. W porównaniu do tego klasyczny procesor Pentium zawiera 3 200 OOO (3,2 miliona) tranzystorów. Więcej informacji dotyczących emulatora można znaleźć w dokumentacji zestawu Android SDK, dostępnej na stronie http://developer. android. com/guide!developi ng/tools/emulator. html.
Interfejs użytkownika na platformie Android Android wykorzystuje szkielet interfejsu Ul bardzo podobny do analogicznych szkieletów, używanych w komputerach osobistych. W rzeczywistości jest on nowocześniejszy oraz bar dziej asynchroniczny. W istocie interfejs Uf Androida stanowi czwartą generację szkieletów interfejsów użytkownika, jeżeli uznać tradycyjny interfejs API systemu Windows, napisany w języku C, za pierwszą generację, a stworzony w języku C++ zbiór klas MFC (ang. Microsoft Foundation Classes) za drugą. Szkielet Ul biblioteki Swing, napisany w języku Java, można uznać za trzecią generację, gdzie została wprowadzona o wiele większa elastyczność projektowa nia, niż to miało miejsce w przypadku zbioru MFC. Interfejs Ul Androida, JavaFX, Micro soft Silverlight oraz język XUL (ang. XML User Interface Language - język XML interfejsu użytkownika) należą do czwartego pokolenia szkieletu Ul, w którym interfejs użytkownika jest deklaracyjny oraz tworzony niezależnie.
W Androidzie programujemy aplikacje za pomocą wspólczesnego paradygmatu
•• • 'r1111 rn 1 r•r11 ·• · interfejsu Ul, nawet jeśli urządzenie docelowe jest handheldem. • • • •
Programowanie w interfejsie Ul Androida wiąże się z zadeklarowaniem interfejsu przy użyciu plików XML. Następnie można wczytać takie definicje widoków XML jako okna w aplikacji interfejsu użytkownika. Nawet listy opcji aplikacji są wczytywane z plików XML. Ekrany lub okna w Androidzie są często nazywane aktywnościami, składającymi się z wielu widoków wymaganych przez użytkownika do wykonania czynności. Widoki są podstawowymi blo kami budulcowymi interfejsu użytkownika, które można łączyć ze sobą w celu uzyskania grup widoków. Widoki wykorzystują w swoim wnętrzu znajome pojęcia kanw, rysowania oraz interakcji użytkownika. Aktywność obsługująca takie złożone widoki, zawierająca widoki lub grupy widoków, jest logicznym, zamiennym składnikiem interfejsu Ul w Androidzie. Jedną z najważniejszych koncepcji szkieletu Androida jest zarządzanie cyklem życia okien aktywności. Zostają wstawione protokoły, dzięki którym Android może modyfikować stan działania podczas ukrywania, przywracania, zatrzymywania oraz zamykania okien aktywności
Rozdział 1
•
Wprowadzenie do platformy obliczeniowej Android
31
przez użytkowników. Te podstawowe zasady staną się bardziej zrozumiałe p o przeczytaniu rozdziału 2., stanowiącego również wstęp do skonfigurowania środowiska projektowego Android.
Podstawowe składniki Androida Szkielet interfejsu Ul, wraz z innymi elementami platformy Android, jest zależny od nowej koncepcji, zwanej intencjq (ang. Intent). Intencja jest połączeniem takich pomysłów, jak informacje wywoływane w oknach, działania, modele publikowania i (lub) subskrybowania, komunikacja międzyprocesowa, a także rejestry aplikacji. Poniżej przedstawiono przykład wykorzystania klasy I n t ent do wywołania lub uruchomienia przeglądarki internetowej: public static void invokeWebBrowse r (Activity activity) { Intent intent = new Intent ( Intent . ACTION_VIEW ) ; intent . setData ( U ri . pa rse( " http : //www. google . com" ) ) ; activity . st a rtActivit y ( intent ) ; }
W tym przykładzie zmuszamy Androida poprzez intencję do otwarcia odpowiedniego okna, w którym wyświetlana będzie zawartość sieci WWW. W zależności od listy dostęp nych przeglądarek zainstalowanych w urządzeniu Android wybierze najodpowiedniejszą. Intencje zostały szczegółowo omówione w rozdziale 3. Android został zaopatrzony także w rozbudowaną obsługę zasobów, obejmujących znajome kategorie elementów oraz plików, na przykład ciągi tekstowe oraz mapy bitowe, jak również mniej znane składniki typu definicje widoku oparte na języku XML Są one wykorzystywane w nowoczesny, łatwy, intuicyjny oraz wygodny dla użytkownika sposób. Poniżej został za mieszczony przykład, w którym identyfikatory zasobów zostają wygenerowane automa tycznie dla zasobów zdefiniowanych w plikach XML: public final class R { public static final class attr { } public static final class d rawable { public static final int myanimation=Ox7f020001 ; public static final int numbers19=0x7f02000e; public static final class id { public static final int textViewidl=Ox7f080003; public static final class layout { public static final int frame_animations_layout=Ox7f03000 1 ; public static final i n t main=Ox7f030002; public static final class string { public static final int hello=Ox7f070000;
Każdy identyfikator wygenerowany w tej klasie odpowiada albo elementowi znajdującemu się w pliku XNIL, albo samemu plikowi. Można teraz używać tych wygenerowanych identy iikatorów zamiast definicji XML. Taka pośrednia droga jest bardzo przydatna podczas pro cesu lokalizacji (w rozdziale 3. zostały szczegółowo omówione zasoby oraz plik R.java).
32
Android 2. Tworzenie aplikacji
Kolejną nową koncepcją w Androidzie jest dostawca treści. Jest to abstrakcyjne uięcie źródła danych, przyjmujące formę wystawcy oraz adresata usług RESTful. Stano\nąca podłoże ba za danych SQLite sprawia, że jest to potężne narzędzie dla projektantów aplikacji (w roz dziale 3. zostanie wyjaśnione, w jaki sposób intencje, zasoby oraz dostawcy treści wpływają na otwartość platformy Android).
Zaawansowane koncepcje interfejsu użytkownika Stwierdziliśmy już, że decydującą rolę w opisie interfejsu UI Androida stanowi język XML. Zobaczmy, w jaki sposób można dzięki niemu stworzyć prosty układ graficzny, zawierający widok tekstu: c?xml version= " l . O " encoding="utf - 8 " ?> clinearlayout xmlns : android=htt p : //schemas . android . com/apk/res/and roid>
W celu załadowania układu graficznego do okna aktywności będzie używany identyfikator wygenerowany dla tego pliku XML (proces ten zostanie przeanalizowany w rozdziale 4.). Android obsługuje także menu, od standardowych do kontekstowych. Praca przy takich menu jest bardzo wygodna, gdyż są one również wczytywane jako pliki XML, a ich identyfi katory zasobów są generowane automatycznie. Można deklarować menu w pliku XML w nastę pujący sposób:
1 44
Android 2. Tworzenie aplikacji
cTextView and roid : layout_width="fill_parent" android : layout_height="wrap_content" android : text="Adres : " /> 'vV
katalogu src tego projektu jest umieszczony domyślny plik .Java, zawierający definicję klasy Activity. Dwukrotne kliknięcie tego pliku spowoduje wyświetlenie jego treści. Zwróć my uwagę na instrukcję setContentView ( R . layout . ma i n ) . Fragment kodu XML umiesz czony w listingu 4.2 w połączeniu z wywołaniem set C ontentView ( R . layout . mai n l spo woduje wyświetlenie takiego samego interfejsu użytkownika, jak w przypadku listingu 4. 1 . Nie trzeba omawiać pliku XML, warto jednak zwrócić uwagę, że zostały zdefiniowane trzy widoki pojemników. Pierwszy, Linea rlayout, jest odpowiednikiem pojemnika nadrzędne go. Jego położenie zostaje ustalone na pionowe poprzez zdefiniowanie odpowiedniej wła ściwości: android : o rientation="vertica l " . W pojemniku nadrzędnym są umieszczone dwa podrzędne elementy Linearlayout, reprezentujące odpowiednio pojemniki nameContainer oraz add ressCont a i ne r. Listing 4.2 przedstawia wymyślny przykład, szczególnie pod względem kodowania wartości kontrolek TextView w układzie graficznym XML. Byłoby najlepiej, gdyby nasz interfejs użytkownika został zaprojektowany w języku XML, a następnie zostały utworzone odnie sienia do kontrolek. Taka technika pozwala na dołączenie danych zmieniających się dyna micznie do kontrolek zdefiniowanych w trakcie tworzenia projektu. W istocie jest to zale cana metoda. W listingu 4.3 został pokazany ten sam interfejs Ul, lecz z nieco zmodyfikowanym kodem
XML. Zostają tutaj przydzielone identyfikatory kontrolek TextView, dzięki czemu możemy się do nich odnieść w kodzie. Listing 4.3. Utworzenie interfejsu użytkownika w kodzie XML z dołączonymi identyfikatorami
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 45
Kod umieszczony w listingu 4.4 demonstruje, w jaki sposób uzyskać odniesienia do kon trolek zdefiniowanych w kodzie XML w celu skonfigurowania ich właściwości.
Listing 4.4. Tworzenie odniesień do kontrolek w zasobach w czasie działania setContentView ( R . layout . main ) ; TextView nameValue = (TextViev1) f indViewByid ( R . i d . nameValueText ) ; nameValue . setText ( "Gall Anonim " ) ; TextView add rValue (TextView) findViewByi d ( R . id . add rValueText ) ; addrValue. setText ( " Ulica Sezamkowa 90210 " ) ; =
Powyższy kod nie jest skomplikowany, jednak należy zauważyć, że wczytujemy zasób (poprzez wywołanie setContentView ( R . layout . mai n ) ), zanim wywołamy metodę f i nd ViewBy i d ( ) - nie możemy przekazać zasobów widokom , j eżeli nie zostały jeszcze załadowane.
Sta ndardowe kontrolki Androida Zajmiemy się teraz omówieniem st andardmvych kontrolek, dostępnych w środowisku An droid SDK. Rozpoczniemy od kontrolek tekstu, a następnie przejdziemy do przycisków, pól wyboru, przycisków opcji, list, siatek, kontrolek daty i czasu oraz widoku mapy. Przedys kutujemy także kwestię kontrolek układu graficznego. Rozdział zakończymy, pokazując, w jaki sposób pisać własne, niestandardowe kontrolki .
Kontrolki tekstu Prawdopodobnie kontrolki tekstu są pierwszym rodzajem kontrolek, na które natykają się programiści . Android posiada pełny, lecz nieprzytłaczający ogromem zestaw kontrolek tekstu. W kolejnych paragrafach omówimy kontrolki Textview, E d itText, Auto Comple teTex tView oraz Mul tiCompleteTextView. Na rysunku 4.2 zostały pokazane te kontrolki w akcji.
1 46
Android 2. Tworzenie aplikacji
TextView Kontrolka TextView wyświetla tekst, jednak nie pozwala na jego edycję. Można wysnuć wniosek, że jest to jedynie zwykła etykieta. Nieprawda. Kontrolka ta posiada kilka interesu jących właściwości, dzięki którym staje się bardzo przydatna. Na przykład: jeżeli wiadomo, że zawartością kontrolki TextView będzie adres URL, można ustanowić właściwość autolink wobec obiektu web, dzięki czemu kontrolka znajdzie i podświetli dany adres. Co więcej, kie dy użytkownik kliknie tę kontrolkę. system uruchomi przeglądarkę stron v\TVvW z tym ad resem URL. Tak naprawdę jeszcze ciekawszy użytek można zrobić z kontrolki TextView dzięki klasie a n d ro i d . text . ut i l . Linki f y (listing 4.5).
Listing 4.5. Zastosowanie klasy Linkify z kontrolką TextView TextView tv = ( TextView) this . findViewByl d ( R . i d . cctvex ) ; t v . s etText ( " Odz11iedź m o j ą s t ronę, http: //wwv1. sayedhashimi . com lub napisz do mnie na a d res sayed@sayedhashimi . co m . " ) ; Linkify . addLin k s ( t v , Linkif y . ALL) ; Jak zostało pokazane, możemy przekazać kontrolkę TextView klasie Linki fy po to, żeby były wyszukiwane oraz dodawane łącza do treści tej kontrolki. W naszym przykładzie wy wołujemy metodę a d d l i n ks ( } klasy Linki fy, tym samym przekazujemy kontrolkę TextVievi oraz maskę określającą, jakie rodzaje łączy ma wyszukiwać klasa Linki fy. Klasa ta może tworzyć łącza dla tekstu przypominającego numer telefonu, adres e-mail, adres URL lub mapę adresu. Przekazanie flagi Linki f y . ALL sprawia, że będą tworzone łącza dla wszystkich wymienionych typów danych. Kliknięcie takiego łącza powoduje, że zostanie wywołana domyślna intencja tego działania. Na przykład po kliknięciu adresu URL zostanie uruchomiona przeglądarka internetowa z wklejonym weń adresem. Kliknięcie numeru te lefonu otworzy ekran wybierania numeru itd. Klasa Linki f y może sprzęgnąć te czynności bez najmniejszego problemu. Można także dołączyć do tej klasy inne typy treści (na przy kład nazwisko), przypisując im regularne wyrażenia oraz identyfikatory URI dostawcy treści.
EditText Kontrolka EditText jest kontrolką obiektu TextView. Jak sugeruje jej nazwa, umożliwia edytowanie tekstu. Kontrolka ta nie jest tak rozbudowana jak na przykład analogiczne obiekty środowiska JFC, jednak UŻ}tkownicy urządzeń obsługujących Androida nie będą raczej pisali w nim dokumentów - co najwyżej kilka akapitów. Zatem klasa ta posiada ograniczony, lecz rozsądnie dobrany zestaw funkcji. Przykładowo można skonfigurować właściwość autoText, żeby kontrolka poprawiała powszechnie popełniane błędy w trakcie pisania. Właściwość c a p it a l i ze powoduje przekształcanie małych liter w duże na początku zdania, wyrazów itd. Właściwość phoneNumber przydaje się podczas zapisywania numeru telefonu. Jeżeli potrzebne będzie pole wpisywania hasła, dostępna jest właściwość p a s swo rd. Domyślnym zachowaniem kontrolki Edi tText jest wyświetlanie tekstu w jednym wierszu oraz przechodzenie do następnych wierszy w razie potrzeby. Inaczej mówiąc. jeżeli użyt kownik wypełni tekstem cały pierwszy wiersz, pojawi się wiersz drugi, i tak dalej. Można jednak wymusić przebywanie w jednym wierszu poprzez wstawienie wartości t rue we właściwo ści singleline. W takim wypadku użytkownik będzie musiał umieścić cały tekst w jednej linijce.
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 47
Celem projektowania aplikacji na urządzenia przenośne jest pomoc użytkownikowi w szyb kim podjęciu decyzj i . Zatem powszechnym zwyczajem jest podświetlenie lub zaznaczenie odmiennym stylem jakiegoś fragmentu treści zawartej w kontrolce Edi tText. Można tego dokonać w sposób statyczny lub dynamiczny. Metoda statyczna polega na wstawieniu znaczni ków bezpośrednio do ciągu znaków w zasobach typu string ( Styl statyczny w pol u Edi tText . < / s t ring>), d o którego następnie jest tworzone odniesienie w pliku XML lub kodzie. Warto wiedzieć, że w zasobach typu string dostępne są wyłącznie znaczniki , ora7. języka HTML. Programistyczne tworzenie stylów treści u mieszczon ej w kontrolce Edi tText vqmaga nieco więcej v,rysiłku, jednak oferuje o wiele więcej możliwości (listing 4.6).
Listing 4.6. Dy namiczne umieszczanie styl ów w treści kontrolki EditText EditText et = ( EditText ) t his . findViewByid ( R . id . cctvexS ) ; e t . setText ( "Dynamiczne przypisywanie stylów zawa rtości pola EditText " ) ; Spannable spn = et . getText ( ) ; spn . setSpan( new BackgroundColo rSpan (Colo r . RED ) , 11, 3 1 , Spannable . SPAN_EXCLUSIVE_ EXCLUSIVE ) ; s p n . setSpan ( new StyleSpan (android . g raphics . Typeface. BOLD_ITALIC ) , 1 1 , 31, Spannable . SPAN_ EXCLUSIVE_EXCLUSIVE) ; Jak zostało pokazane w listingu 4.6, można po brać treść kontrolki EditText (jako obiekt Spannable), a następnie umieszczać s tyle dla różnych fragmentów tekstu. Powyższy kod na rzuca na tekst pogrubienie i ku rsywę oraz czerwone tło. Tutaj style nie są ograniczone wy łącznie do pogrubienia, kursywy i podkreślenia. Można wprowadzać indeksy górne, dolne, przekreślenia itd.
AutoCompleteTextView Kontrolka AutoCompleteTextView jest obiektem TextView z dodaną funkcją automatycznego wypełniania. Innymi słowy, podczas pisania tekstu przez użytkownika w oknie TextView będą wyświetlane sugestie dokończenia wyrazu. W listingu 4.7 zaprezentowano kod kon trolki AutoCompleteTextView.
Listing 4.7. Stosowanie kontrolki AutoCompleteTextView AutoCompleteTextView actv = (AutoCompleteTextView) this . findViewByi d ( R . id . ccactv ) ; A r rayAdapter aa = new A rrayAdapter (this , and roid . R . layout . s imple_d ropdown_item_ lline, new String [ ] { "Angiels ki", "Hebrajski " , "Hindi", "Hiszpański " , "Niemiecki" , "Grecki" } ) ; actv . setAdapte r ( aa ) ;
Kontrolka
AutoCompleteTextView przedstawiona w listingu 4.7 sugeruje użytkownikowi określony język. Na przykład jeżeli zostan ie wp isa ny tekst an, kontrolka zasugeruje język angielski. Po wpisaniu g r zaproponowany zostanie język grecki i tak dalej.
Kontrolki sugestii lub podobne kontrolki automatycznego wyp ełnian ia składają się z dwóch części: kontrolki widoku tekstu oraz kontrolki odpowiedzialnej za wyświetlan ie s ugestii.
1 48
Android 2. Tworzenie aplikacji
Taka jest ogólna koncepcja. Żeby móc stosować taką kontrolkę, należy utworzyć najpierw ją, a następnie listę sugestii, którą trzeba przypisać do tej kontrolki, oraz określić, w jaki spo sób sugestie mają być wyświetlane. Ewentualnie można utworzyć drugą kontrolkę odpo wiedzialną za sugestie, a następnie powiązać obydwie kontrolki ze sobą. Wprowadzenie możliwości automatycznego 'vypełniania w Androidzie jest proste, czego dowodem jest listing
4.7. Żeby korzystać z kontrolki AutoCompleteTextView, można ją
zdefiniować w pliku układu graficznego, a następnie odnosić się do niego w aktywności. Następnie tworzy się klasę przejściową, przechowującą sugestie, oraz definiuje się identyfi kator kontrolki, która będzie je wyświetlała (w tym przypadku prostą listę elementów). Drugi parametr przedstawionego w listingu
4.7 obiektu A r rayAdapter wskazuje klasie przejścio
wej , że sugestie mają być przedstawiane w postaci prostej listy. Ostatnim krok.iem jest po wiązanie klasy przejściowej z kontrolką pomocą metody
AutoCompleteTextView, co zostaje osiągnięte za
setAdapte r ( ) .
MultiAutoCompleteTextView Osoby korzystające z kontrolki
AutoCompleteTextView vviedzą, że oferuje ona jedynie sugestie całego tekstu w oknie widoku. Inaczej mówiąc, podczas pisania zdania nie będą wyświetlane sugestie dla każdego v,ryrazu. W tym miejscu pojawia się kontrolka Mul tiAutoComplete .....TextView. Służy ona do wyświetlania sugestii w trakcie wpisywania tekstu przez użytkownika. Na rysunku 4.2 pokazano, że użytkownik wpisał V\ryraz Angielski, a po przecinku Hi, co powoduje wyświetlenie sugerowanego wyrazu Hindi. Jeżeli użytkownik będzie wypisywał dla
inne języki, będą wyświetlane kolejne sugestie.
Styl statyczny w polu
EditText.
Rysunek 4.2. Kontrolki tekstu w Androidzie Kontrolki
MultiAutoCompleteTextView używa się tak samo, jak obiektu AutoComplete ....TextView. . Różnica polega na konieczności określenia miejsca, w którym będzie wyświetlana kolejna sugestia. Na przykład na rysunku 4.2 pokazano, że sugestie mogą być proponowane na początku zdania oraz po przecinku. Konieczne jest zatem umieszczenie tokenizera analizują
cego zdanie oraz wskazującego kontrolce kolejną sugestię. W listingu
-..rextView.
MultiAutoCompleteTextView, kiedy ma wyświetlić 4.8 został zaprezentowany kod kontrolki Mult iAutoComplete
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 49
Listing 4.8. Stosowanie kontrolki MultiAutoCompleteTextView MultiAutoCompleteTextView mactv = (MultiAutoCompleteTextView) this . findViewBy!d ( R . i d . ccmactv ) ; A rrayAdapter aa2 = new Ar rayAdapter (this , and roid . R . layout . simple_d ropdown_ item_ lline , new String [ ) {"Angiels ki " , "Hebra j s ki " , "Hindi " , "Hiszpański " , "Niemiecki " , "Grecki" } ) ; mactv. setAdapte r(aa2 ) ; mactv . setTokenizer( new MultiAutoCompleteTextView . CommaTokenize r ( ) ) ; Jedyną istotną różnicą pomiędzy listingiem 4. 7 a listingiem 4.8 jest zastosowanie klasy Mul tiAutoCompleteTextView oraz wywołanie metody setTok enize r ( ) . Ponieważ w tym wypadku został umieszczony obiekt CommaTokenizer, po wprowadzeniu przecinka w polu EditText znów pojawi się okno sugestii korzystające z tablicy zawierającej ciągi znaków. In ne znaki nie spowodują wyświetlenia pola sugestii. Zatem jeśli nawet zostaną wpisane wyra zy Francuski Hiszpań, nie pojaw·i się sugestia dokończenia drugiego słowa, ponieważ nie został wstawiony przed nim przecinek.
Kontrolki przycisków Przyciski są standardem w każdym środowisku obsługującym widgety, a Android nie jest wyjątkiem. Dostępny jest tradycyjny zestaw przycisków oraz kilka dodatków. W następnych paragrafach zajmiemy się trzema rodzajami kontrolek przycisków: przyciskiem podstawo wym, przyciskiem obrazkowym oraz przyciskiem przełączania. Na rysunku 4.3 został poka zany interfejs użytkownika posiadający wszystkie trzy rodzaje przycisków. Są to kolejno: przycisk podstawowy, przycisk obrazkO"wy oraz przycisk przełączania.
Rysunek 4.3. Kontrolki przycisków w Androidzie Zajmijmy się najpierw przyciskiem podstawowym.
Kontrolka Button Klasą przycisku podstawowego w Androidzie jest a n d ro i d . widget . Button. Niewiele można pm'liedzieć na temat tego rodzaju przycisków, poza obsługą zdarzeń wyzwalanych kliknięciem (listing 4.9). Listing 4.9. Obsługa zdarzeń wyzwalanych kliknięciem w obiekcie Button
1 50
Android 2. Tworzenie aplikacji
and roid : layout_width="fill_parent" and ro id : layout_height="wrap_ con tent " /> Button btn = ( Button ) this . findViewByid { R . id . ccbtn l ) ; btn. setOnClicklistene r ( new OnClickListener ( ) { public void onClick( View v ) { Intent intent = getButtonintent ( ) ; intent . setAction ( " j akies dane intenc j i " ) ; setResult( RESULT OK, intent ) ; finish ( ) ; } }) ;
W listingu 4.9 został zaprezentowany sposób rejestrowania zdarzenia wywołanego kliknię ciem. Dokonuje się Lego poprzez wywołanie metody setOnClicklistener{ ) wobec obiektu onClicklistener. Został na bieżąco utworzony anonimowy nasłuchiwacz dla obiektu btn, obsługujący zdarzenia wywoływane kliknięciem. Po kliknięciu przycisku zostaje wywołana metoda onClick { ) nasłuchiwacza. Wraz z wydaniem środowiska Android SDK w wersji 1 .6 wprowadzono łatwiejszy sposób konfigurowania obsługi kliknięcia przycisku (przycisków). W języku XML dla obiektu Button określa się następujący atrybut: and roid : onClick="myClickHandler"
wraz z wprowadzeniem odpowiedniej metody obsługi przycisku do klasy aktywności, na przykład w następujący sposób: public void myClickHandler( View target) { swit c h ( target . getid { ) ) { case R . i d . ccbtnl:
Metoda funkcji obsługi, zawierająca docelowy zestaw, jest wywoływana dla obiektu View, reprezentującego naciśnięty przycisk. Należy zwrócić uwagę, w jak.i sposób instrukcja prze łączania w metodzie funkcji obsługi wykorzystuje identyfikatory zasobów przycisku do uru chomienia procesu. Stosowanie tej metody oznacza, że obiekty Button nie będą ja\vnie tworzone w kodzie oraz że ta sama metoda może być wykorzystywana także w wielu przyciskach jed nocześnie; struktura interfejsu staje się generalnie bardziej 7,rozumiała i przejrzysta. Metoda ta działa również w przypadku pozostałych typów przycisków.
Kontrolka lmageButton Przycisk.i obrazkowe są dostępne w Androidzie dzięki klasie and ro id . widget . ImageButton. Używanie tego rodzaju obiektu przypomina korzystanie z przycisku podstawowego (li sting 4.10). Listing 4 . 1 O . Stosowanie kontrolki Image Button
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1S1
ImageButton btn ( ImageButto n ) this . findViewByi d ( R . id . imageBt n ) ; bt n . setimageResource ( R . d rawable . icon ) ; =
:viożna wstawiać rysunek przycisku w sposób dynamiczny poprzez wywołanie metody setimageResource ( ) lub modyfikację pliku XML układu graficznego (wstawiając we wła ściwości a nd ro id : s re identyfikator rysunku), co zostało zademonstrowane w listingu 4. 1 1 . Listing 4.1 1 . Wstawianie obrazu lmageButton w kodzi e XML
Kontrolka ToggleButton Obiekt ToggleButton, taki jak pole wybo ru lub przycisk opcji, reprezentuje kategorię przy cisków dwustanowych. P rzycisk taki może znajdować się w stanie włączonym lub wyłączo nym. Domyślnym zachowaniem przycisku ToggleButton jest ukazywanie zielonego paska w stanie włączonym i wyszarzanego w stanie wyłączonym. Co więcej, tekst przycisku brzmi On, gdy przycisk jest włączony, i zmienia się na Off po jego wyłączeniu. W listingu 4.12 pokazano przykład. Listing 4.1 2. Kontrolka ToggleButton
Istnieje możliwość modyfikowania tekstu pojawiającego się w poszczególnych stanach kontrolki ToggleButton, jeżeli domyślne ustawienia nie pasują do tworzonej aplikacji. Jeśli na przykład za pomocą takiego przycisku ma być kontrolowany proces przebiegający w tle, można umieścić wyrazy Uruchom oraz Zatrzymaj, poprzez zdefiniowanie właściwości a n d roid : textOn oraz and ro i d : textOff (listing 4 . 1 3). Ponieważ teksty wyświetlane pod czas włączenia lub wyłączenia przycisku ToggleButton są oddzielnymi atrybutami, atrybut and ro i d : t ext właściwie nie jest wykorzystywany. Jest on dostępny, ponieważ został odziedzi czony (po obiekcie TextView), jednak w tym przypadku okazuje się niepotrzebny. Listing 4.13. Konfigurowanie etykiety kontrol ki ToggleB utton
1 52
Android 2. Tworzenie aplikacji
Kontrolka CheckBox Kontrolka pola wyboru odgrywa rolę dosłownie we wszystkich środowiskach obsługujących widgety. Wszystkie standardy HTML, JFC oraz JSF zawierają koncepcję pola wyboru. Kontrolka CheckBox jest przyciskiem dwustanowym, umożli,,.viającym użytkownikowi wybranie stanu. W Androidzie pole v.ryboru tworzone jest poprzez ustanowienie instancji klasy and ro i d . "-widget . CheckBox (listing 4.14 oraz rysunek 4.4). Listing 4.14. Tworzenie pól wyboru '
Rysunek 4.4. Zastosowanie kontrolki CheckBox Zarząd7,anie stanem pola wyboru odbywa się za pomocą wywołania metod setChecked ( ) lub toggl e ( ) . Informacje o stanie uzyskiwane są dzięki wywołaniu metody i s C h e c ked ( ) . Jeżeli po zaznaczeniu pola wyboru lub usunięciu tego zaznaczenia ma nastąpić określone wydarzenie, można je zarejestrować poprzez wywołanie metody setOnCheckedChange "-Listen e r ( ) wraz z implementacją interfejsu OnCheckedChangeListener. Konieczne bę dzie także zaimplementowanie metody o n C h e c kedChange ( ) , wywoływanej po zaznaczeniu lub cofnięciu zaznaczenia pola wyboru.
Kontrolka RadioButton Kontrolki przycisków opcji są integralnym elementem każdego środowiska projektowego interfejsów UL Przycisk opcji daje użytkownikowi kilka możliwości wyboru i tylko jedna z nich może zostać zaznaczona. Żeby taki model działał skutecznie, przyciski opcji prze ważnie należą do grupy i każda taka grupa może mieć zaznaczony tylko jeden element. Żeby utworzyć grupę przycisków opcji w Androidzie, należy najpierw stworzyć element Radio <.+Group, a następnie wypełnić go tymi przyciskami. W listingu 4.15 został zaprezentowa ny przykład, a rysunek 4.5 stanowi jego ilustrację.
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 53
Listing 4.15. Stosowanie wid g etów RadioButton w Androidzie
Rysunek 4.5. Używanie przycisków op cji
W Androidzie grupa opcji implementowana jest za pomocą klasy and ro id . widget . RadioG ro up, a przycisk opcji - poprzez klasę and ro i d . widget . RadioButton. Zwróćmy uwagę, że wszystkie przyciski opcji wewnątrz grupy opcji są domyślnie niezazna czone, chociaż istnieje możliwość zaznaczenia jednego z nich w definicji XML. Żeby zapro gramować takie predefiniowane zaznaczenie jednego z przycisków opcji, można utworzyć odniesienie do tego przycisku i wywołać metodę s e t C hecked ( ) : RadioButton rbtn = ( RadioButto n ) t h i s . f indViewByid ( R . id . stkRBtn ) ; rbtn . setChecked ( t rue ) ; Można również wykorzystać metodę toggle ( ) do przełączania stanów przycisku. Podobnie jak w przypadku kontrolki C h e c kBox, będzie wyświetlane powiadomienie przy każdym zda rzeniu zaznaczenia lub cofnięcia go, jeżeli zostanie wywołana metoda setOnCheckedChange -+Listener( ) wraz z implementacją interfejsu OnCheckedChangelistener.
1 54
Android 2. Tworzenie aplikacji
Nale ży sobie uświadomić, że w elemencie RadioGroup mogą zostać umieszczone również inne widoki, nie tyl ko przyciski opcji. Na przykład w listingu 4 . 1 6 po ostatnim przycisku opcji została dodana kontrolka TextView. Zauważmy, że przycisk op cji został umieszczony poza grupą opcji.
Listing 4.16. Grupa opcji zawierająca nie tylko przyciski opcji Listing 4. 1 6 udowadnia, że można posiadać kontrolki nieb ędące częścią obiektu RadioGroup wewnątrz grupy opcji. Co więcej, dobrze wiedzieć że grupa opcji może wymuszać zazna czenie tylko jednego przycisku jedynie wobec przycisków opcji znajdujących się w tym po jemniku. Inaczej mówiąc, przycisk opcji posiadający identyfikator anotherRadBtn nie będzie objęty działaniem gr upy opcji przedstawionej w listingu 4.16, ponieważ nie jest jej elemen tem podrzędnym. ,
Istnieje również możliwość zaprogramowania obiektu RadioGroup. Na przykład można w ten sposób uzyskać odniesienie do grupy opcji oraz dodać przycisk op cj i (lub inny r odzaj kon trolki}:
RadioGroup rdgrp = (RadioGrou p ) findViewByid ( R . id . rdGrp ) ; RadioButton newRadioBtn = new RadioButto n ( t h i s ) ; newRadioBt n . setTex t ( "Wiep rzowina " ) ; rdg rp. addView( newRadioBtn ) ;
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 55
Na koniec warto wspomnieć, że po zaznaczeniu przez użytkownika przycisku opcji w grupie opcji nie będzie można usunąć tego zaznaczenia za pomocą powtórnego kliknięcia. Jedynym sposobem usunięcia zaznaczenia wszystkich przycisków opcji w tej grupie jest "'')'Wołanie metody clearCheck ( ) w obiekcie RadioGroup.
Kontrolki listy Ś rodowisko Android SDK posiada kilka rodzajów kontrolek związanych z listami. W tym paragrafie zajmiemy się kontrolką ListView, ukazaną na rysunku 4.6.
Rysunek 4.6. Zastosowanie kontrolki ListView Kontrolka ListView wyświetla pionowo listę elementó,v. Przeważnie stosuje się tę kontrol kę poprzez napisanie nowej aktywności, rozszerzającej klasę and ro i d . app . ListActivi ty. Obiekt ListActivity zawiera kontrolkę ListView i dane w niej są umieszczane poprzez wywołanie metody setlistAdapte r ( ) . W poniższym ćwiczeniu wypełnimy cały ekran kontrolką ListView, więc nie będziemy musieli jej nawet określać w pliku układu graficzne go main.xml. Musimy jednak ;vprowadzić oddzielne układy graficzne dla każdego wiersza listy. W listingu 4 . 1 7 umieszczony został kod układu graficznego dla jednego wiersza listy oraz kod Java kontrolki ListActi vity.
listing 4.17. Dodawanie elementów do kontrolki ListView
1 56
Android 2. Tworzenie aplikacji
public class ListDemoActivity extends ListActivity {
private SimpleC u r s o rAdapter adapter;
@Over ride protected void onCreate(Bundle savedinstanceState) { supe r . onCreate{ savedinstanceStat e ) ; C u r s o r c = getContentResolv e r ( ) . qu e ry ( People . CONTENT_URI , n u l l , null, null , n u ll ) ; sta rtManagingCu rso r ( c ) ; String [ ) cols = new String [ ) {People. NAME } ; int [ ) names = new int [ J {R . id . row t v} ; adapter = new SimpleCursorAdapte r ( this , R . layout . list_item , c , col s , names ) ; this . setlistAdapt e r ( adapte r ) ; } Kod z listingu 4.17 powoduje utworzenie kontrolki ListView, zapełnionej pobraną z urzą dzenia listą kontaktów. Po lewej stronie każdego kontaktu umieszczone jest pole wyboru. Jak już stwierdziliśmy w poprzednim akapicie, kolejnym krokiem jest rozszerzenie metody ListActivity oraz skonfigurowanie adaptera listy poprzez wywołanie metody setlist '+Adapt e r ( ) w tej aktywności. W naszym przykładzie żądamy od urządzenia listy kontak tów, a następnie tworzymy projekcję, dzięki której wybieramy wyłącznie nazwy kontaktów - projekcja definiuje interesujące nas kolumny. W dalszej kolejności mapujemy nazwę wo bec kontrolki TextView. W kolejnym etapie tworzymy adapter kursora i konfigurujemy ad apter listy. Klasa adaptera jest przystosowana do przeglądania krotek w danych źródłowych i pobierania nazw kontaktów w sposób umożliwiający zapełnienie interfejsu użytkownika. Musimy wykonać jeszcze jedną czynność, zanim lista nam zadziała. Ponieważ w tym ćwi czeniu nasza aplikacja uzyskuje dostęp do listy kontaktów telefonu, należy przydzielić jej odpowiednie uprawnienia. Informacje dotyczące zabezpieczeń omówione zostały w roz dziale 7., teraz więc wyjaśnimy jedynie, w jaki sposób udostępnić dane kontrolce ListView. Należy dwukrotnie kliknąć plik AndroidManifest.xml w naszym projekcie, a następnie wy brać zakładkę Permissions. Następnie trzeba kliknąć przycisk Add„ . , dalej zaznaczyć opcję Uses Permission i na końcu kliknąć OK. Należy przewinąć listę Name aż do pozycji andro id.permission.READ_CONTACTS. Okno aplikacji Eclipse powinno wyglądać tak, jak na rysunku 4.7. Można teraz zapisać plik AndroidManifest.xml i uruchomić aplikację w emulatorze. Może zaistnieć potrzeba dodania kontaktów za pomocą aplikacji Contacts, zanim jakiekolwiek nazwiska pojawią się w naszym przykładowym programie. Zauważmy, że metoda one reate ( ) nie ustanawia widoku treści danej aktywności. Ponieważ bazowa klasa ListAc rivity zawiera już kontrolkę ListView, należy jedynie zapewnić jej dostęp do danych. W przypadku chęci wstawienia do układu graficznego dodatkmvych kontrolek wystarczy umieścić odpowiedni plik XML, wstawić weń kontrolkę ListView oraz dodać wymagane elementy. Można na przykład wstawić w interfejsie Ul przycisk poniżej kontrolki ListViev1, który po zwala na wysłanie listy zaznaczonych elementów, co zostało pokazane na rysunku 4.8.
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
i:-, main.xml c.
C1 "'t.istViewDemo Manifest
. l listDemoAdivRy.iava
Android Manifest Permissions
Permissions
LI
<>
(;> android.permission.READ_C
o r:
'
A:
l[:·:��CL�::.!I .1 I Remove . .
Up
�
1 57
= El
Attributes for Uses Permission
'\ł) The uses-permission tag requests a
"permission" that the containing package must be granted in order for it to operate correctly.
Name
Iandroid.permission.READ_CONTACTS :::J
Down
Manifest Application Permiss1ons Instrumentation AndroidManifest.xml
Rysunek 4.7. Modyfikowanie pliku AndroidManifest.xml, umożliwiające uruchomienie aplikacji
Rysunek 4.8. Dodatkowy przycisk umożliwiający użytkownikowi wysłanie listy zaznaczonych elementów
W naszym przykładzie plik XML układu graficznego jest podzielony na dwa pliki. Pierwszy plik zawiera definicję interfejsu aktywności - kontrolkę ListView oraz przycisk (rysunek 4.8 oraz listing 4.18).
1 58
Android 2. Tworzenie aplikacji
Listing 4.18. Przesłonięcie kontrolki ListView, do której odnosi się klasa ListActivity Ten plik umieszczony jest w podkatalogu /res/layout//ist.xml --> protected void onCreate(Bundle savedlnstanceState) { supe r . onCreate( savedinstanceStat e ) ; setContentView ( R . layout . g ridview ) ;
GridView gv = ( G ridVie1� ) this . findVie1�Byi d ( R . id . dataGrid ) ; C u rs o r c = getContentResolve r ( ) . qu e r y ( People. CONTENT URI , null, n u l l , nul l , null ) ; startManaging C u rso r ( c ) ;
St ring [ ] cols = new String [ ] {People. NAME } ; int [ ] names = new int [ ] {android . R . id . text l } ; SimpleCursorAdapter adapter = new SimpleCursorAdapt e r { t hi s , a n d roid . R . l ayo u t . sim p le_ l i s t_ i tem 1 , c , c o l s , names ) ; g v . setAdapt e r ( adapt e r ) ; }
kontrolka G ridView w pliku XML ul
W listingu 4.20 została zdefiniowana prosta
1 60
Android 2. Tworzenie aplikacji
Rysunek 4.9. Kon trol ka GridView wypełniona nazwami kontaktów Siatka przedstawiona na rysunku 4.9 v.ryświetla nazwy kontaktów przechowywanych w urządze niu. Postanowiliśmy umieścić kontrolki TextView z tymi nazwami, j ednak równie dobrze można w ich miejsce wstawić rysunki lub inne typy danych. W rzeczywistości zastosowali śmy w tym przykładzie jeszcze jeden skrót. Zamiast stworzyć własny plik układu graficzne go, zawierający elementy siatki, wykorzystaliśmy pliki z predefiniowanymi układami gra ficznymi systemu Android. Zwróćmy uwagę, że prefiksem w adresie zasobów związanych z układem graficznym elementu siatki oraz polem elementu siatki jest android:. Każemy Androidowi przeszukiwać nie lokalny katalog /res, lecz jego własny katalog z zasobami. Można przejrzeć ten folder, wchodząc do katalogu aplikacji Android SDK i dalej podążając ścież ką platforms/ldata/res/layout. Znajduje się tam plik simple_list_item_l.xml, wewnątrz którego jest zdefiniowana prosta kontrolka TextView, określona identyfikatorem @android:id!textl. Właśnie dlatego umieściliśmy identyfikator and ro id . R . i d . text 1 dla adaptera kursora. W przypadku kontrolki G ridView interesująca jest inform acj a, że a dapterem stosowanym przez siatkę jest ListAdapter. Listy są zazwyczaj jed nowym iaro we, po dc zas gdy siatki po siadają dwa wymiary. Wynika z tego taki wniosek, że siatka tak na prawdę wyświetla dane w postaci listy. Faktycznie, jeżeli wywołamy metodę getSelction ( ) , zostanie zwrócona liczba całkowita, reprezentująca indeks wyznaczonego elementu. W podob ny sposób, żeby ustanowić wybór w siatce, należy wywołać metodę setSelection wraz z numerem ele mentu, który ma zostać zaznaczony.
Kontrolki daty i czasu Kontrolki daty i czasu są standardem w wielu zestawac h widgetów. W Androidzie zostało zawartych kilka kontrolek związanych z datą i czasem, niektóre z nich zostaną omówione w następnych paragrafach. W szczególności zajmiemy się kontrolkami DatePicker, TimePicker, AnalogClock oraz D i g i t a lClock.
Kontrolki DatePicker oraz TimePicker Zgodnie z ich nazwami kontrolka Da te Picke r służy do wybierania daty, natomiast kontrolka TimePicker umożliwia ustawienie czasu. Listing 4.21 oraz rysunek 4 . 1 0 przedstawiają przy kłady tych kontrolek.
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 61
Listing 4.21. Kontrolki DatePicker oraz Ti mePicker w kodzie XML < _.:.nea rlayout xmlns : and roid="http : //schemas . and ro id. com/apk/ res/and roid" android : o rientation="vertical" and roid : layout_ width=" fill_ pa rent" android : layout_ height= " fill parent " > < Linearlayout>
Rysunek 4.1 O. Interfejsy U l kontrolek DatePicker i TimePicker Jeżeli prz}jrzeć się układowi graficznem u w kodzie Xl\ilL, to m ożna stwierdzić, że definio wanie tych kontrolek nie jest skomplikowane. Jednak interfejs użytkownika wydaje się nie· co wyolbrzymiony. Obydwie kontrolki wydaj ą się zbyt duże, lecz w przypadku urządzeń przenośnych nie ma co narzekać. Podobnie jak w przypadku innych kontrol ek w Androidzie, także i w tym przypadku można zaprogramować je, żeby uruchamiały się lub żeby można było pob ierać z n ich dane. Na przy klad kod inicjalizacji ty ch kontrolek może wyglądać tak, jak w listingu 4.22.
Listing 4.22. Inicjacja daty w kontrolce DatePicker oraz godziny w kontrolce TimePicker protected void onCreate ( Bundle savedinstanceState) s u pe r . onCreate ( savedinstanceStat e ) ; setContentView ( R . layout . datet ime ) ; DatePicker dp = (OatePicke r ) this . findViewByid ( R . id . datePicke r ) ; d p . init( 2008, 1 1 , 10, null ) ; TimePicker tp = (TimePicker ) this . findViewByld ( R . i d . timePicke r ) ; t p . setis24HourView ( t rue ) ; t p . setCurrentHou r ( new Intege r ( lO ) ) ; t p . setCurrentMinute( new Integer ( lO ) ) ;
1 62
Android 2. Tworzenie aplikacji
Kod w listingu 4.22 ustawia datę na 10 listopada 2008 roku. Podobnie została wybrana go dzina 10:10. Warto wiedzieć, że kontrolka obsługuje wyświetlanie czasu dwudziestocztero godzinnego. Jeżeli tym kontrolkom nie zostaną przydzielone żadne wartości, domyślnymi wartościami będą aktualne data i czas, skonfigurowane w urządzeniu. Android zawiera również te kontrolki w wersji okien dialogowych, na przykład DatePicker ._. Dialog oraz TimePickerDialog. Kontrolki te przydają się w przypadku, gdy mają zostać wyświetlone użytkownikowi oraz wymusić na nim dokonanie wyboru opcji. Okna dialogo we zostały szczegółowo omówione w rozdziale 5.
Kontrolki AnalogClock i DigitalClock Na rysunku 4. 1 1 przedstawiliśmy dostępne D i g i talClock.
w
Androidzie kontrolki AnalogCl o c k oraz
Rysunek 4.1 1 . Zastosowanie kontrolek AnalogClock i DigitalClock Jak widać, zegar analogowy w Androidzie posiada dwie wskazówki, jedną odpowiedzialną za wskazywanie godzin, a drugą za wyznaczanie minut. W zegarze cyfrowym są dodatkowo dostępne sekundy. Kontrolki te nie są tak bardzo interesujące, ponieważ nie ma w nich możliwości modyfiko wania daty lub czasu. Są to zatem zwykłe zegary, których jedyną funkcją jest wyświetlanie aktualnego czasu. Zatem w przypadku chęci zmiany daty lub czasu należy stosować kon trolki DatePicke r/Ti me P i c ke r lub DatePicke rDialog/TimePic ke rDialog.
Inne interesujące kontrolki w Androidzie Dotychczas omówione kontrolki stanowią podstawę każdej aplikacji napisanej dla plat formy Android. Poza nimi dostępnych jest również kilka innych, interesujących kontrolek. W kolejnych sekcjach pokrótce je omówimy.
Kontrolka MapView Kontrolka com. google . and roid . maps . MapView umożliwia wyśv.rietlanie mapy. Można utwo rzyć egzemplarz tej kontrolki w pliku XML układu graficznego lub w kodzie Java, jednak v.ryko rzystująca ją aktywność musi rozszerzyć klasę MapActivity. Klasa ta zajmuje się przetwa rzaniem wielowątkowych żądań ładowania mapy, przeprowadzaniem procesu buforowania i tak dalej.
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 63
W listingu 4.23 został zaprezentowany przykład utworzenia obiektu MapView.
Listing 4.23. Utworzenie kontrolki MapView w pliku XML układu graficznego ccom . google . and roid . maps . MapView android: layout_width="fill_parent" android : layout_height= " fill_parent" and ro id : enabled="t rue " and roid : clickable="true" android : apiKey="myAPIKey" /> Szczegółowe informacje na temat kontrolki MapVie1-1 zostały zawarte w rozdziale 7., w któ rym zajmujemy się usługami opartymi na wyznaczaniu położenia geograficznego. Znajdują się tam również informacje, w jaki sposób uzyskać własny klucz API mapowania.
Kontrolka Gallery Kontrolka Galle ry tworzy listę przewijaną w poziomie, która zawsze skupia się na środku listy. Kontrolka ta przeważnie jest stosowana jako galeria obrazów w trybie dotykowym. Można ją utworzyć w pliku XML ukladu graficznego lub w kodzie Java: Stosowanie kontrolki Gallery przypomina używanie kontrolki listy. To znaczy jest uzyski wane odniesienie do galerii, następnie wywoływana metoda setAdapte r ( ) , służąca do umieszczenia danych w tej galerii, na końcu są rejestrowane wybierane zdarzenia.
Kontrolka Spinner Kontrolka S p i n n e r pełni funkcję rozwijanego menu. Można ją utworzyć w pliku XML ukladu graficznego lub w kodzie Java: Również korzystanie z kontrolki Spinner przypomina używanie kontrolki listy. To znaczy najpierw uzyskuje się odniesienie do spinnera, następnie wywołuje się metodę setAdap t e r ( ) w celu umieszczenia danych, a potem rejestruje się zdarzenia wywoływane zaznaczaniem. Kontrolka S p i n n e r posłuży nam za przykład w podrozdziale „Zapoznanie z klasą Array Adapter".
1 64
Android 2. Tworzenie aplikacji
Na tym zakończymy omawianie zestawu kontrolek w Androidzie. Jak zostało wspomniane na początku rozdziału opa nowan ie sztuki budowania interfejsów użytkowni ka wymaga znajomości dwóch elementów: zestawu kontrolek oraz menedżerów układu graficznego. W na stępnym podrozdziale zajmiemy się drugim z wymienionych składników. ,
Menedżery układu g raficznego Android - podobnie jak w p rzypadku biblioteki Swing - zawiera zbiór klas widoku, peł niących rolę pojemników na widoki. Te kontenerowe klasy noszą nazwę układów graficznych (ang. layout) lub menedżerów układów graficznych (ang. layout manager), każda z nich wnosi określony sposób za rządzan ia rozmiarem oraz pozycją elementów podrzędnych. Na przy kład klasa LinearLayout umieszcza elementy potomne jeden za drugim w orientacji pozio mej lub pionowej. Dostępne w środowisku Android SDK menedżery układu graficz nego zostały zdefiniowane w tabeli 4.2. Tabela 4.2. Menedżery układu graficznego w Androidzie Menedżer układu graficznego
Opis
LinearLayout
Rozmieszcza elementy podrzędne w pionie lub poziomie.
Table Layout
Rozmieszcza elementy podrzędne w postaci tabelarycznej.
Re l a t i veLayout
Rozmieszcza elementy podrzędne względem innych elementów lub pojemnika nadrzędnego.
F rameLayout
Umożliwia dynamiczne zmienianie kontrolki (kontrolek) w układzie g raficznym.
Wymienione menedżery układu graficznego zo staną omówione w kolejnych ustępach. Wcześniej był dostępny również menedżer AbsoluteLayout, stał się jednak przestarzały i zosta nie w tej ksi ążce pominięty.
Menedżer układu graficznego Linearlayout Klasa Linea rLayout stanowi przykład n ajprostszego układu graficznego. Menedżer ten rozmieszcza elementy potomne w poziomie lub w pionie, zależnie od wartości właściwości o rientation. W listingu 4.24 została przedstawiona konfiguracja pozioma potomków. Listing 4.24. Klasa Linearlayout z wprowadzoną konfiguracją poziomą
Można przekształcić klasę Linea r L a y o u t na orientację pio nową poprzez zmianę wartości właściwości o rientation na vertical.
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 65
Ciężar oraz grawitacja Właściwość orientation jest pierwszym z istotnych atrybutów ro zpo zn awanych przez menedżer LinearLayout. In nym i ważnymi właściwościami, wpływającymi na rozmiary oraz rozmieszczenie kontrolek poto mnyc h , są ciężar (ang. weight) i grawitacja (ang. gravi ty). Dzięki ciężarowi przypisywany jest stopień ważności kontrolki w odniesieniu do innych ko ntrolek w pojemniku. Załóżmy, że w poj emniku umieszczone są trzy kontrolki: jedna ma zdefiniowany ciężar równy 1 (najwyższa możliwa wartość), pozostałym dwóm została na tomiast przypisana wartość O. W takim przypadku kontrolka o wartości ciężaru 1 zajmie całą niezajętą przestrzeń pojemnika. Grawitacja jest w zasadzie wyrównaniem elementu. Na p rzykład jeżeli tekst etykiety ma zostać vvyrównan y do prawej strony, należy przypisać atry butowi g ravi ty wartość right. Dostępnych jest kilka różnych wartości grawitacji, na przykład left, center, right, top, bot tom, cente r_ vertical, clip_ ho rizontal i inne. Szczegóły doty czące tych oraz pozostałych wartości grawitacji można znaleźć w dokum en tacji Androida.
"'!)ff§"
Menedżery układu graficznego rozszerzają klasę a n d ro id . widget . ViewG ro up, podobnie jak wiele klas pojemników opartych na kontrolkach, n a przykład ListView. Chociaż rozszerzają one tę samą klasę, klasy menedżerów układu graficznego zajmują się wyłącznie definiowaniem rozmiarów oraz rozmieszczenia kontrolek, nie interakcją użytkownika z kontrolkami potomnymi. Na przykład porównajmy obiekty Linea r La yo u t i L i s tView. Na ekranie wyglądają podobnie, gdyż obydwa mogą organizować potomków w orientacji pionowej. Jednak kontrolka L i s tView zawiera interfejsy API umożliwiające użytkownikowi zaznaczanie elementów, czego nie można powiedzieć o klasie Linea rLayout. Innymi słowy, pojemnik oparty na kontro l kach (List View) obsługuje interakcję użytkownika z elementami weń umieszczonymi, pod czas gdy menedżer układu graficznego (Linea r L a y ou t) zajmuje się jedynie określaniem rozmiarów i rozmieszczaniem potomków.
Przyjrzyjmy się przykładowi związanemu z właściwościami ciężaru i grawitacji (rysunek 4.12). lilll .:i 9:47
raą dwa trzy
�-
li!ll fU 10:00
'li'll!lll ai 9:59
-
-
raz
ra.j
dwa trzy
i�
. ,
Rysunek 4.12. Stosowanie menedżera układu graficznego LinearLayout >l'a rysunku 4. 1 2 zos tały pokazane trzy interfejsy użytkownika wykorzystujące menedżer Linea rLayout, z których każdy posiada inną konfigu rację ciężaru i grawitacji. Interfejs uka zany po lewej stronie posiada domyślne ustawienia ciężaru i grawitacji. Plik XML tego układu graficznego zawiera kod zaprezentowany w listingu 4.25.
Listing 4.25. Trzy pola tekstowe umieszczone pionowo w menedżerze LinearLayout przy domyślnych ustawieniach ciężaru i grawitacji
1 66
Android 2. Tworzenie aplikacji
Ś rodkowy interfejs na rysunku 4 . 1 2 zawiera domyślną wartość ciężaru, ale parametry ar gumentu a n d ro i d : g rav it y zostały zdefiniowane dla kontrolek w kolejności: le ft, center, right. W przykładzie po prawej atrybut a n d ro id : layout_ weight środkowego elementu wynosi 1.0, natomiast w pozostałych dwóch kontrolkach nie zmieniono domyślnej wartości 1.0 (listing 4.26). Sprawiamy w ten sposób, że środkowy element zajmie całą wolną prze strze11 pojemnika nadrzędnego, a dwie skrajne kontrolki pozostaną przy swoich domyśl nych rozmiarach. Listing 4.26. Konfigurowanie ciężaru w menedżerze Linearlayout Analogicznie, jeżeli chcemy, żeby dwie kontrolki z trzech podzieliły między siebie pozostałą wolną przestrzeń, wprowadzamy im wartość ciężaru równą 1 .0, w trzeciej wartości nato miast pozostawiamy ten argument niezmieniony. W końcu trzecią możliwością jest współ dzielenie ekranu przez trzy kontrolki w równym stopniu, osiągnięte poprzez przydzielenie każdej z nich wartości ciężaru wynoszącej 1.0. W ten sposób każde pole tekstowe zostanie rozciągnięte w takim samym stopniu.
Porównanie atrybutów android:gravity i android:layout_gravity Zwróćmy uwagę, że w Androidzie są zdefiniowane dwa podobne atrybuty: and roid : g ravit y oraz a n d ro i d : layout_ g ravity. Różnica polega na tym, że and ro i d : g ravi t y jest używany przez widok, a and roid : layout_g ravit y przez pojemnik (a n d roid . view. ViewGroup). Na przykład można ustanowić wartość atrybutu android: g ravi t y na c e n t e r, aby wyśrodkować
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 67
tekst zawarty w kontrolce EditText. Natomiast aby umieścić kontrolkę E d i tText po prawej stronie pojemnika Linea rlayout, powinien zostać wpisany następujący wiersz: and ro id : l ayou t_ g ravity=" right" (rysunek 4 . 1 3 i listing 4.27).
Rysunek 4.13. Wprowadzanie ustawień g rawitacji Listing 4.27. Pokazanie różnicy pomiędzy atrybutami android:gravity a android:layout_gravity Na rysunku 4.13 widać, że wewnątrz kontrolki EditText zawartość jest vqśrodkowana, natomiast sama kontrolka została umieszczona po prawej stronie pojemnika Linea rlayout.
Menedżer układu graficznego Tablelayout Menedżer układu graficznego Tablelayout jest rozwinięciem menedżera Linea rLayout. Potomne kontrolki są w nim układane w wierszach i kolumnach. W listingu 4.28 został po kazany przykład: Listing 4.28. Prosty men edżer Table layo ut
1 68
Android 2. Tworzenie aplikacji
W celu skorzystania z tego menedżera należy utworzyć jego wystąpienie, a następnie umie ścić w nim elementy TableRow. W nich są przechowywane kontrolki tabeli. Interfejs użyt kownika stworzony w listingu 4.28 został pokazany na rysunku 4.14.
Rysunek 4.14. Menedżer układu graficznego Tablelayout Ponieważ elementy menedżera Tablelayout są definiowane w wierszach, a nie w kolum nach, Android określa liczbę kolumn tabeli poprzez wyszukanie wiersza zawierającego naj większą ilość komórek. Na przykład w listingu 4.29 zostały utworzone dwa wiersze, gdzie pierwszy wiersz zawiera dwie komórki, a drugi - trzy (rysunek 4. 15). W takim przypadku Android tworzy tabelę zawierającą dwa wiersze i trzy kolumny. Komórka znajdująca się w dru giej kolumnie pierwszego wiersza jest pusta.
Listing 4.29. Definicja nieregularnej tabel i
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
Ftit!MI @
1 69
10:40
Rysunek 4.15. Menedżer Tablelayo ut reprezentujący niereg ularną tabelę W listingach 4.28 oraz 4.29 wypełniliśmy menedżer Tablelayout elementami TableRow. Chociaż jest to tradycyjne podejście, można także umieścić dowolny element a n d roid . 4-Widget . View jako potomka tabeli. Na przykład w listingu 4.30 zostaje utworzona tabela, w której pierwszym wierszem jest kon trolka E d i tText (rysunek 4 . 1 6).
Listing 4.30. Zastosowanie kontrolki EditText zam iast Ta bleRow
Rysunek 4.16. Kontrolka EditText jako potomek w menedżerze Tablelayout Interfejs użytkownika stworzony w listingu 4.30 został ukazany na rysunku 4 . 1 6. Zauważ my, że kontrolka Edi tText zajmuje całą szerokość ekranu, chociaż nawet nie zawarliśmy odpowiedniego parametru w układzie graficznym XML. Wynika to z faktu, że całe potom stwo menedżera Tablelayout jest zawsze rozciągane na długość wiersza. Innymi słowy, elementy potomne menedżera Ta ble layout nie mają możliwości wprowadzenia parame tru and ro i d : layout_ width= "w r a p co n tent - są zmuszone do akceptowania wartości f il l_ pa rent. Można w nich jednak dostosować argument a n d ro id : layout_ height. "
1 70
Android 2. Tworzenie aplikacji
Ponieważ zawartość tabeli nie zawsze jest znana podczas etapu projektowania, menedżer Tablelayout posiada kilka atrybutów umożliwiających kontrolowanie układu graficznego tabe li. Na przykład w listingu 4.30 widać, że parametry właściwości a n d roid : s t retchColumns wynoszą odpowiednio 0,1,2. Dla menedżera Tablelayout oznacza to, że w zależności od treści kolumny nr O, 1 oraz 2 zostaną rozciągnięte.
W podobny sposób można za pomocą argumentu and ro id : sh rinkColumns ścisnąć zawar tość wybranych kolumn, gdy inne kolumny potrzebują więcej miejsca. Można także wpro wadzić właściwość and ro i d : collapseColumns, dzięki której wybrane kolumny stają się niewidoczne. Należy pamiętać, że kolumny są numerowane, począwszy od cyfry O. Menedżer Tablelayout zawiera także atrybut android : layout span. Jest on używany do rozciągnięcia komórki na wiele kolumn. Atrybut ten przypomina właściwość colspan w ję zyku HTML. Nieraz pojawi się potrzeba utworzenia odstępó'v wewnątrz zawartości komórki lub kon trolki. Przydatny wtedy okazuje się atrybut and ro id : padding oraz jego krewniacy. Dzięki niemu możliwe jest kontrolowanie przestrzeni pomiędzy zewnętrznymi granicami widoku a jego treścią (listing 4.3 1 ) .
Listing 4.31 . Zastosowanie właściwości android:padding
W listingu 4.31 zostały wyznaczone odstępy równe 40 px . W ten sposób 40 pikseli białej przestrzeni oddziela treść kontrolki Edi tTex t od jej krańców. Na rysunku 4. 17 zademon strowano tę samą kontrolkę Edi tTex t posiadającą dwie różne wartości odstępów. Interfejs ,
po lewej stronie nie posiada zdefiniowanych żadnych odstępó,v, a interfejs po prawej posia da dopisaną linijkę and ro id : padding=" 40px".
�!l!ll �
10:47
Rysunek 4.17. Zastosowanie odstępów Atrybut and roid : padding tworzy odstępy we wszystkich kierunkach: w lewo, w prawo, w górę i w dół. Można kontrolować rozmiar odstępu dla każdej strony dzięki atrybutom andro id: leftPadding, android : rightPadding, android : topPadding oraz and roid : bottomPadding.
W Androidzie można również zdefiniować atrybut android: layout_ma rgin, bardzo przypo minający android : padding. W rzeczywistości różnica pomiędzy atrybutami and roid : padd i n g/ and ro id : l ayo ut_ma rg i n jest analogiczna do przypadku and ro id : g ravity/and ro i d : layout_ g ravity. To znaczy jeden atrybut jest przeznaczony dla widoku, a drugi dla pojemnika.
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
171
W końcu należy zwrócić uwagę, że wartość odstępu jest zawsze w typie danych wymiarów.
Android obsługuje następujące typy wymiarów:
• Piksele. W skrócie px. Wymiar ten reprezentuj e fizyczne piksele ekranu. • Cale. W skrócie i n . • Milimetry. W skrócie mm. • Punkty. W skrócie pt. Jeden punkt jest równy 1/72 cala. • Piksele niezależne od gęstości. W skrócie d i p albo dp. Tego typu dane wymiaru wykorzystują ekran posiadający gęstość 160 pikseli na cal jako ramkę odniesienia, a następnie mapują go na rzeczywisty ekran. Na przykład ekran o szerokości 160 pikseli b ędzie mapował l dip na l piksel . • Piksele niezależne od skali. W skrócie sp. \ĄTymiar używany przeważnie przy pracy z czcionkami. Są tu brane pod uwagę ustawienia użytkownika oraz rozmiar czcionki w celu określenia rzeczywistego wymiaru. Pamiętajmy, że wymieni one tu rodzaje wymiarów nie są przeznaczone wyłącznie do defi niowania odstępów - każdy ob iekt w Androidzie akceptujący wartości wymiaru (na przykład and roi d : layout_width lub androi d : layout_ heigh t) będzie umiał odczytać te jednostki .
Menedżer układu graficznego Relativelayout Kolej nym interesującym menedżerem układu graficznego jest Relativelayout. Jak sama nazwa sugeruje, menedżer ten implementuje zasady, dzięki którym pozycja kontrolek znaj dujących się w pojemniku jest zależna od pojemnika lub innej w nim umieszczonej kontro lki. Przykład został pokazany w listingu 4.32 oraz na rysunku 4.18.
Listing 4.32. Zastosowanie menedżera układu graficznego Relativelayout
1 72
Android 2. Tworzenie aplikacji
Rysunek 4.18. Interfejs użytkownika utworzony za po mocą menedżera układu graficznego Relativelayout Jak widać, interfejs użytkownika przypomina prosty formularz logowania. Etykieta nazwy użytkownika została przypięta do górnej części pojemnika, ponieważ dla atrybutu and ro i d : layout_alignParentTop przypisaliśmy wartość t rue. W podobny sposób pole wpisywania nazwy użytkownika znalazło się poniżej etykiety, gdyż wprowadziliśmy atrybut and ro i d : layout_ below. Etykieta hasła jest umieszczona jeszcze niżej, pod nią wprowadziliśmy pole wpisywania hasła, a na samym dole pojemnika znalazło się zastrzeżenie, gdyż zamieściliśmy w tej kontrolce atrybut and roid : layo u t_ alignPa rentBottorn z wartością t rue. Poza trzema wymienionymi atrybutami dostępne są takie, jak layout_ above, layout_ toRightOf, layout_t oLeftOf , layout_ cente rinPa rent i inne. Praca z menedżerem RelativeLayout jest przyjemna z powodu łatwości jego obsługi. Istotnie, dla każdego, kto zacznie go używać, stanie się on ulubionym menedżerem układu graficznego - bez przerwy będzie się do niego wracać.
Rozdział 4 • Budowanie inteńejsów użytkownika oraz używanie kontrolek
1 73
Menedżer układu graficznego Framelayout Menedżery układu graficznego omówione do tej pory wprowadzają różne strategie kompo zycji elementów. Innymi słowy, każdy z nich w określony sposób wstawia i orientuje kontrolki potomne na ekranie. Dzięki menedżerom można wstawić na jednym ekranie jednocześnie wiele kontrolek, z których każda zajmuje określony fragment pojemnika. W Androidzie do stępny jest także menedżer układu graficznego, używany przede wszystkim do wyświetlania pojedynczego składnika. Nosi on nazwę menedżera F ramelayout. Ta klasa układu graficz nego służy głównie do dynamicznego wyświetlania pojedynczego widoku, można ją jednak zapełnić wieloma elementami, z których jeden będzie widoczny, a pozostałe będą niewi doczne. W listingu 4.33 zostało zademonstrowane zastosowanie menedżera Framelayout. Listing 4.33. Zapełnia nie
menedżera Frame layout
cf ramelayout xmln s : android="htt p : //schema s . android . com/apk/ res/android" androi d : id="@+id/frmLayout" android : layout_width=" fill_ pa ren t " and roid : layout_height="fill_parent"> @Ove r ride protected void onCreate( Bundle savedinstanceState) super. onCreate( savedinstanceState ) ;
{
setContentView ( R . layout . f rame ) ; ImageView one ( ImageView) this . findViewByid ( R . i d . onelmgView ) ; ImageView two = ( ImageView) this . f indViewByld ( R . i d . twolmgView) ; one . setOnClicklistene r ( new OnClicklistene r ( ) { @Over ride public void onClick(View view) { ImageView two = ( ImageView ) F ramelayoutActivit y . t h i s . findViewByld ( R . id . twoimgView) ; two . setVisibili ty (Vie1-1 . VISIBLE) ; view.setVisibility(View . GONE) ; }} ) ; two . setOnClicklisten e r ( new OnClicklistene r ( ) {
1 74
Android 2. Tworzenie aplikacji
@Over ride public void onClic k ( View view) { ImageView one ( ImageView ) F ramelayoutActivity . this . findViewByid ( R . id . oneimgView) ; =
o n e . setVisibility(View . VISIBLE ) ;
}
view . setVisibility(View . GONE ) ; }} ) ;
Listing 4.33 przedstawia plik układu graficznego oraz metodę onCreate( ) aktywności. Celem ćwiczenia jest wczytanie dwóch obiektów ImageView w menedżerze F rame l a yo ut w taki sposób, żeby w danym momencie był widoczny tylko jeden z nich. Na poziomie interfejsu użytkownika, kiedy użytkownik kliknie widoczny obrazek, następuje jego schowanie i wy świetlenie drugiego obiektu. Przyjrzyjmy się bliżej kodowi zamieszczonemu w listingu 4.33, począwszy od układu gra ficznego. Można zauważyć, że definiujemy menedżer F rameLayout zawierający dwie kon trolki ImageView (są one odpowiedzialne za właściwe wyświetlanie obrazów). Zwróćmy uwagę, że widoczność drugiego obiektu ImageView przyjmuje wartość gone, dzięki czemu staje się on niewidoczny. Spójrzmy teraz na metodę one rea te ( ) . Rejestrujemy w niej ele menty nasłuchujące, reagujące na kliknięcia obiektów ImageView. W procedurze obsługi kliknięcia programujemy ukrycie jednego obiektu ImageView wraz z jednoczesnym wy świetleniem drugiego obiektu. Jak już wcześniej wspomnieliśmy, menedżera Framelayout zazwyczaj używa się podczas dynamicznego konfigurowania treści widoku w pojedynczej kontrolce. Chociaż to jest stan dardowa praktyka, pokazaliśmy również, że kontrolka akceptuje również wiele obiektów potomnych. W listingu 4.33 do układu graficznego są dodane dwie kontrolki, jednak w da nym momencie widoczna jest tylko jedna z nich. Menedżer F ramelayout nie wymusza jed nakże takiego rozwiązania. Jeżeli do układu graficznego zostanie dodanych wiele kontrolek, zostanie po prostu utworzony ich stos, w którym jedna kontrolka będzie nałożona na drugą, ostatnia natomiast będzie znajdować się na jego szczycie. W ten sposób można stworzyć interesujący interfejs użytkownika. Na przykład na rysunku 4.19 został zilustrowany mene dżer F ramelayout, w którym są widoczne dwa obiekty ImageView. Widać, że kontrolki są ułożone w stos, a ta znajdująca się na wierzchu częściowo zasłania obiekt umieszczony za nią. Kolejny interesujący fakt dotyczący menedżera F rameLayout jest taki, że po dodaniu do układu graficznego więcej niż jednej kontrolki rozmiar tego układu jest definiowany jako rozmiar największego elementu w pojemniku. Na rysunku 4.19 element znajdujący się na wierzchu jest w rzeczywistości znacznie mniejszy od elementu umieszczonego pod spodem, jednak ponieważ układ graficzny jest dopasowany do największego obiektu, obraz z pierw szego planu zostaje rozciągnięty. Warto także pamiętać, że jeśli umieści się w menedżerze Framelayout wiele elementów, z któ rych część zostanie zdefiniowana jako niewidoczne, można rozważyć wykorzystanie metody setConsid e rGoneChild renWhenMeasu ring ( ) . Skoro największy element podrzędny defi niuje rozmiar całego układu graficznego, może pojawić się problem, gdy takim elementem okaże się obiekt niewidoczny. To znaczy jeżeli pojawi się na pierwszym planie, będzie widoczna jedynie jego część. Po wprowadzeniu metody setCon s i d e rGoneChild renWhenMeasu ring ( l z wartości<} t rue wszystkie elementy powinny być prawidłowo wyświetlane.
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 75
Rysunek 4.19. Menedżer Framelayout zawierający dwa obiekty lmageView
Dostosowanie układu graficznego do konfiguracji różnych urządzeń Już doskonale wiemy, że Android zawiera wiele menedżerów układu graficznego pomagają cych w budowaniu interfejsów użytkownika. Jeżeli ktoś już wypróbował omawiane przez nas menedżery, będzie wiedział, że można je łączyć na wiele sposobów w celu uzyskania po żądanego wyglądu i sposobu funkcjonowania danego interfejsu. Ale nawet za ich pomocą tworzenie poprawnie działających interfejsów UT stanowi wyzwanie. Dotyczy to zwłaszcza urządzeń przenośnych. Oczekiwania użytkowników oraz producentów urządzeń przeno śnych stają się coraz bardziej wysublimowane, przez co poprzeczka stojąca przed programi stą aplikacji zostaje podniesiona jeszcze bardziej. Jednym z takich wyzwań jest stworzenie interfejsu użytkownika aplikacji, która będzie wy świetlona w różnych konfiguracjach wyświetlacza. Na przykład jak wyglądałby stworzony interfejs Ul na wyświetlaczu ułożonym w orientacji poziomej, a następnie pionowej? Czy telnicy, którzy jeszcze nie spotkali się z tym problemem, prawdopodobnie usilnie starają się teraz wyobrazić sobie rozwiązanie tego dosyć powszechnego scenariusza. Na szcząścic Android w interesujący sposób pomaga poradzić sobie z tym problemem. Działa to w następujący sposób: Android na podstawie konfiguracji urządzenia znajduje oraz wczytuje układy graficzne z określonych folderów. Urządzenie może znajdować się w jed nej z trzech konfiguracji: pionowej (ang. portrait), poziomej (ang. landscape) lub umożli wiającej wyświetlanie kwadratowego obrazu (ang. square). Żeby układy graficzne były wła ściwie wyświetlane w tych różnych konfiguracjach, należy utworzyć dla nich oddzielne foldery, z których Android będzie wczytywał właściwe układy graficzne. Jak wiadomo, do myślnym folderem układu graficznego jest res/layout. Żeby była obsługiwana konfiguracja pionowa, należy stworzyć folder res/layout-port; dla trybu poziomego będzie to res/layout-land, a dla kwadratowego res/layout-square. W tym momencie nasuwa się pytanie: Czy mając te trzy foldery, potrzebuję jeszcze domyślnego
katalogu układu graficznego (res/layout)? Zasadniczo tak. Pamiętajmy, że logika Androida, polegająca na rozpoznawaniu zasobów, sprawdza najpierw katalog określony w konfiguracji.
1 76
Android 2. Tworzenie aplikacji
Jeżeli nie zostaną tam znalezione zasoby, system przechodzi do domyślnego folderu układu graficznego. Zatem można umieścić domyślne definicje układu graficznego w folderze res/layout, a ich odpowiednio dostosowane wersje w folderach konfiguracji. Zauważmy, że środowisko Android SDK nie posiada interfejsów API, które pozwalałyby w sposób programistyczny określić rodzaj wczytywanej konfiguracji - system sam wybiera folder w oparciu o konfigurację urządzenia. Można jednak w kodzie określić orientację urządzenia, na przykład w sposób przedstawiony poniżej: import android . content . pm . Activity!nfo; setRequestedO rientation (Activityi n f o . SCREEN ORIENTATION_LANDSCAPE ) ;
W ten sposób urządzenie zostaje zmuszone do wyświetlenia aplikacji w orientacji poziomej.
Można śmiało wypróbować ten sposób we wcześniejszych projektach. Powyższy fragment kodu należy dodać do metody one reate ( ) danej aktywności, uruchomić ją w emulatorze i podziwiać odwróconą aplikację na wyświetlaczu. Układ graficzny nie jest jedynym zasobem korzystającym z konfiguracji, istnieją również inne kwalifikatory konfiguracji urządzenia, brane pod uwagę po znalezieniu odpowiedniego zasobu. Cała zawartość folderu res może posiadać odpowiedniki w każdej konfiguracji. Na przykład żeby dla każdej konfiguracji byty przygotowane obie1..-ty rysowane, należy utworzyć katalogi drawable-port, drawable-land oraz drawable-square. Jednak możliwości są jeszcze większe. W tabeli 4.3 wypisano pełną listę kwalifikatorów, które są wykorzystywane po znale zieniu zasobów. Tabela 4.3. Kwalifikatory zasobów Kwalifikator
Opis
Numery MCC i MNC
Iden tyfikato ry kraj u oraz operatora sieci.
Język i reg ion
Dwuliterowy kod języka, po dodaniu l itery r także dwulit erowy kod regi on u.
Rozmiary ekranu
Daje ogólne p ojęcie na temat rozmiaru ekranu; wartości: small, no rmal, l a rge.
Szersze/wyższe ekrany
Związane z proporcjami obrazu; wartości: long, notlong.
Orientacja ekranu
Wartości: land, port, squa re.
Gęstość pikseli na ekranie
Wartości: ldpi, mdpi, hdpi, nodpi oznaczające odpowiednio 120, 1 60, 240.
Typ ekranu dotykowego
Wartości: finger, notouch, stylus.
Klawiatura
Rodzaj klawiatury. Wartości: keysexposed, keyshidden, keys s o f t .
Rodzaj tekstowych danych wejściowych
Wartości: nokeys, qwe rty, 12key (numeryczne).
Sterowanie przy braku klawiatury dotykowej
Wartości: dpad, nonav, t ra c kball, v1heel.
Wersja środowiska SDK
Wartości: v4 (SDK 1 .6), vS (SDK 2.0) itd.
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 77
Więcej informacji na temat tych kwalifikatorów można znaleźć na stronie Androida pod adresem:
http:!ldeveloper.android.com!guide/topics/resources/resources-il8n.html#table2 Powyższe kwalifikatory mogą być używane w wielu kombinacjach, aby uzyskać pożądane zachowanie. Nazwa katalogu zasobów może nie zawierać żadnej z wartości kwalifikatorów lub zawierać wiele takich wartości oddzielonych myślnikami. Na przykład poniżej pokazali śmy poprawną pod względem technicznym (chociaż niezalecaną) nazwę katalogu zasobów typu drawable:
drawa ble-mcc31 0-en-r US-la rge-long-port-m dpi-stylus-keyssoft-q werty-dpad-v3 Lecz również można następująco:
drawable-en-rUS-land (obrazy dla wersji angielskiej 1.e Stanów Zjednoczonych, w orientacji poziomej)
values-fr (ciąg znaków w języku francuskim) Bez względu na to, ile kwalifikatorów zostaje użytych w zasobach aplikacji, należy pamiętać, że w kodzie cały czas należy odwoływać się do zasobów w postaci R . rodzaj_ zasobu . nazwa, bez kwalifikatorów. Jeśli na przykład istnieje wiele różnych odmian pliku układu graficzne go main.xml w różnych kwalifikowanych katalogach zasobów, w kodzie nadal będziemy się do niego odwoływać za pomocą wyrażenia R . l ay o ut . ma in. Android sam zajmuje się od nalezieniem właściwego pliku main.xml.
Działanie adapterów Jak zobaczymy, adaptery mają kilka obowiązków, lecz zasadniczo odpowiedzialne są za ułatwienie i usprawnienie powiązania danych z kontrolkami. W Androidzie adaptery są wykorzystywane przez widgety rozszerzające klasę a n d ro i d . widget . Adapte rView. Wśród obiektów rozszerzających klasę AdapterView znajdują si� kontrolki ListView, G ridView, Spi n ne r oraz Gallery (rysunek 4.20). Sama klasa Adapte rView rozszerza klasę and roid . <+widget . ViewG roup, co oznacza, że obiekty ListView, GridView i inne są kontrolkami pojemnikami. Innymi sło,vy, wyświetlają one zbiór kontrolek potomnych. Widok
ViewGroup
AdapterView
ListView Rysunek 4.20. Hierarchia klasy AdapterView
Spinner
Gallery
Android 2. Tworzenie aplikacji
1 78
Zadaniem adaptera jest dostarczenie potomnych widoków do pojemnika. Potrzebne są dane oraz metadane widoku do utworzenia każdego widoku podrzędnego. Przyjrzyjmy się, jak to działa, analizując adapter SimpleCu rso rA d a p t e r.
Zapoznanie z klasą SimpleCursorAdapter Wielokrotnie przez nas używany adapter SimpleCu r s o rAdapter został naszkicowany na rysunku 4.2 1 . Zestaw wy niko wy
ListView
I I I I
TextView
TextView
TextView
TextView
I I� I I
Dane dla wiersza O
l
I
S i mp leCu rso rAd a pter
'�
!
Dane dla wiersza 1 � '
�
R.layout.widokPotom
Dane dla wiersza
2
Dane dla wiersza
3
Dane dla wiersza 4 Dane dla wiersza 5
Rysunek 4.21. Klasa SimpleCursorAdapter Konstruktor klasy Sim ple C u rsorAdapter wygląda następująco: SimpleCu rso rAda p t e r ( Context context , int layout , C u r s o r c , St ring [ ] f rom, int [ ] t o ) . Adapter ten przekształca krotkę w kursorze na widok podrzędny w kontrolce-pojemniku. Definicja widoku potomnego została umieszczona w zasobie XML (parametr layout). Zwróćmy uwagę, że z powodu możliwości występowania wielu kolumn to za pomocą tablicy z nazwami kolumn (korzy stając z parametru f rom) wskazujemy adapterowi SimpleCu rsorAdapter, które z nich mają zostać zaznaczone. W podobny sposób, ponieważ każda zaznaczona kolumna jest mapowana do kontrolki
TextView, należy utworzyć identyfikatory dla parametru to. Istnieje relacja jeden do jednego pomiędzy zaznaczaną kolumną a kontrolką TextView wyświetlającą dane w kolumnie, za tem wartości parametrów f rom i to muszą mieć ten sam rozmiar. Na rysunku 4.21 można zauważyć pewną elastyczność w stosowaniu adapterów. Ponieważ kontrolka-pojemnik przeprowadza działania na adapterze, można podstawiać różne rodzaje adapterów w oparciu o dane oraz widoki podrzędne. Na przyklad jeżeli klasa AdapterView nie będzie zapełniana danymi z bazy danych, nie ma potrzeby, żeby używać adaptera Simple -.cu rso rAd a p t e r. Można wtedy zastosować jeszcze „prostszy" adapter - A r ra yAd a pt e r.
Zapoznanie z klasą ArrayAdapter Klasa A r rayAda p t e r jest najprostszym adapterem dostępnym w Androidzie. Jej grupą do celową są kontrolki listy oraz zaklada ona, że kontrolki TextView reprezentują elementy li sty (widoki potomne). Utworzenie adaptera A r rayAd a p t e r zazwyczaj wygląda następująco:
A rrayAdapter adapter n ew Ar rayAdapter( thi s , android . R . layout . simple_list_ item_ l , new st ring [ ] { " s ayed " , " s atya" } ) ; =
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 79
W powyższym kodzie konstruktor tworzy klasę A r rayAdapter, w której dane kontrolki TextView są reprezentowane przez ciągi znaków. Zauważmy, że wyrażenie android . R . layout . simple_list_item_l wskazuje kontrolkę TextView zdefiniowaną przez środowisko An droid SDK. W przypadku adaptera ArrayAdapter dostępna jest przydatna metoda, jeśli dane listy po chodzą z pliku zasobów. VY_ listingu 4.34 został pokazany przykład.
Listing 3.34. Utworzen ie adaptera ArrayAdapter z pliku zasobów typu string Spinner s2
=
(Spinner) findViewByid ( R . id . spinner2 ) ;
adapter = A rrayAdapt e r . c reateFromResource ( this, R . a rray . planet s , and roid . R . layout . simple_ spinne r_item ) ; adapte r . setDropD01·mViev1Resou rce( and ro id . R . layout . simple_ spinner_ d ropdown_itern ) ; s 2 . setAdapt e r ( adapte r ) ; cstring - a rray name= " planet s " > - Me rku ryc/item>
- Wenus
- Ziemia
- Mars
citem>Jowiszc/item> - Satu rnc/item>
- Uran
- Neptunc/item> W listingu 4.34 widać, że w klasie A r rayAdapter znajduje się metoda createFromSo u rce( ) , dzięki której można utworzyć adapter A r rayAdapter zawierający dane źródłowe zdefinio wane w pliku zasobów typu string. Za pomocą tej metody można nie tylko uzewnętrznić zawartość listy do pliku XML, lecz również korzystać ze zlokalizowanej wersji takiej treści.
Tworzenie niestandardowych adapterów Adaptery w Androidzie są proste w użyciu, posiadają jednak pewne ograniczenia. W tym celu Android dostarcza abstrakcyjną klasę BaseAdapter, którą można rozszerzyć w razie konieczności utworzenia niestandardowego adaptera. Wszystkie adaptery dostępne w śro dowisku Android SDK rozszerzają tę bazową klasę. Zatem, jeżeli będzie trzeba rozszerzyć adapter, można wziąć pod uwagę następujące klasy:
• A r rayAdapter . Adapter ten znajduje się na szczycie ogólnej tabeli obiektów do·wolnych. Przeznaczony jest do stosowania z kontrolkami ListView. • C u r s o rAdapte r. Adapter ten, również używany przy kontrolkach ListViev1, dostarcza dane listy poprzez kursor.
• SimpleAdapte r. Jak sama nazwa sugeruje, mamy do czynienia z prostym adapterem. Zazv.ryczaj użY"vany jest do zapełniania listy danymi statycznymi (również z zasobów).
• Re s o u rceCu rso rAdapte r. Ten adapter rozszerza klasę Cu r s o rAdapte r i potrafi tworzyć widoki z zasobów.
1 80
Android 2. Tworzenie aplikacji
• SimpleCursorAdapter. Adapter ten rozszerza klasę Res o u rceCursorAdapter i tworzy widoki Textview/ImageView z kolumn w kursorze. Widoki są zdefiniowane w zasobach. Na tym zakończymy omawianie tworzenia interfejsów użytkownika. W następnym podroz dziale zajmiemy się narzędziem Hierarchy Viewer. Służy ono do usuwania błędów oraz optymalizowania interfejsów Ul.
Usuwanie błędów i optymalizacja układów g raficznych za pomocą narzędzia Hierarchy Viewer W środowisku Android SDK znalazło się wiele narzędzi, które znacznie ułatwiają życie projektanta aplikacji. Ponieważ zajmujemy się tematem twmzenia interfejsów użytkownika, warto zapoznać się z narzędziem Hierarchy Viewer. Narzędzie to, pokazane na rysunku 4.22, pozwala na usuwanie błędów w interfejsach ur z poziomu układu graficznego. Fale \.'.ew
Hle1Mchv
�f'v'ł'(
5top�v,i;1 i:.:l'ł1ie-:łh '#r�'łS C>e·.ic1::;
lc»d'łw:w tt�r�rct;·f �pł�yVret1 InvoirdYt> R�'JeS�la'i')l.Jt >.
----- - ----
Propie-rty
V�
®5�e_A
o SO
.;b-soM.e_y
!ąetSo,.,'"'<() :qetHe!Qhl:1)
·I
!C)etPe-rstSteritOr��··
//('.1_10
'oetT.;.01'
Con'.,lftllłe 1Jr18'�
4'XI s.i.:s:icun.....-; f'll..1�
LJ St� E·tr�:;
Lm.:arLJ1ou1 �Jt.Oef.ł 110_10
I
F'r.im„L.11•it11 �1�b:1:s
fram'!LJ1ou: uo_ t>
r--. r--:::;:-
�� �
Rysunek 4.22. Widok układu graficznego w aplikacji Hierarchy Viewer Jak widać na rysunku 4.22, narzędzie to ukazuje hierarchię widoków w formie drzewa. Koncep cja działania narzędzia jest następująca: najpienv zostaje wczytany układ graficzny, a następnie jest on analizowany pod względem ( 1 ) określenia możliwych problemów oraz/lub (2) próby zoptymalizowania układu graficznego pod kątem minimalizacji ilości widoków (kwestia wydajności). W celu wyszukania błędów w interfejsie użytkownika należy uruchomić emulator i '"")'szu kać interfejs Ul, który będzie sprawdzony. Następnie trzeba odnaleźć narzędzie Hierarchy Viewer w katalogu /tools środowiska Android SDK. W przypadku systemu Windows będzie się tam znajdował plik wsadowy hierarchyviewer. bat. Po jego uruchomieniu zostanie wy świetlony ekran urządzeń (rysunek 4.23).
Rozdział 4 • Budowanie interfejsów użytkownika oraz używanie kontrolek
1 81
Tr«i"1'"11!-""
5tatus8a
Rysunek 4.23. Ekran urządzeń w aplikacji Hierarchy Viewer
Znajdujące się po lewej stronie ekranu okno Devices wyświetla listę uruchomionych urzą dzeń (w naszym przypadku emulatorów). Po zaznaczeniu urządzenia w prawym panelu ukaże się lista ekranów dostępnych w tym urządzeniu. Żeby zobaczyć hierarchię widoków dla danego ekranu, należy go zaznaczyć w prawym panelu (przeważnie jest to pełna nazwa aktywności, w której przedrostkiem jest nazwa pakietu aplikacji), a następnie kliknąć przy cisk Load View Hierarchy. Na ekranie View Hierarchy zostanie wyświetlona hierarchia widoków w panelu po lewej stronie (rysunek 4.22). Po zaznaczeniu elementu widoku w środkowym panelu zostaną wy świetlone jego właściwości, a w obrazie szkieletowym po prawej stronie także relatywna wo bec innych widoków lokalizacja tego widoku. Zaznaczony widok będzie podświetlony na czerwono. Mając w ten sposób ukazaną hierarchię widoków, programista może znaleźć spo sób na ich zmniejszenie, co jest równoznaczne z przyśpieszeniem aplikacji. Na rysunku 4.22 można dostrzec dwa przyciski w lewym dolnym rogu okna Hierarchy Viewer. Przycisk po lewej włącza omówiony powyżej widok drzewa. Przycisk po prawej wy świetla bieżący układ graficzny w widoku Pixel Perfect. Widok ten jest bardzo interesujący, ponieważ przedstawia układ graficzny w siatce pikselowej (rysunek 4.24). Znajduje się tu kilka ciekawych elementów. Po lewej stronie jest umieszczony widok eksploratora wszyst kich składników ekranu. Jeżeli zostanie kliknięty jeden z umieszczonych tu elementów, zo stanie podświetlony na czerwono w środkowym panelu. Znajdujące się w nim celowniki pozwalają wyświetlić w prawym panelu przybliżenie wybranego fragmentu ekranu (jest to lupa). Dostępna jest również opcja powiększenia, pozwalająca na jeszcze większe przybliże nie danego rejonu ekranu. Lupa pokazuje również dokładne współrzędne (x, y) wybranego piksela oraz wartość jego koloru.
182
Android 2 . Tworzenie aplikacji t##ł4 SłopServet" Refreshwndows
�es
X
,;
�o<'td\flewHieriwchy
Oi>r.hylfieW
==-==: Inv�te
ReQUe:StLayout
- -' Lu-ie.arlaycU - ...) Fr�Layout • Textview - -..) Framelayout ..:::J Unc�rL.ir(QUI: ,;. 0 Lnearlayout - ;.,j listView - �) Lnearlayout • Check.Box • TextView � U lnearlayout • CheckSox •
Text\l'lew
- ,_) ltterlayout • Chei;JSox • TextView -- U linea.Layoul:
• Ched.Ęox
• Text\.'iew -::, ,_} Ltne�ilayout • + 9
Ched:.Box TextView
Text�
- J lnearlaYoUt • Button
I Ir
efresh R<)te:
ls -
!oom: �
--
1------
Rysunek 4.24. Tryb Pixel Perfect narzędzia Hierarchy Viewer Ostatnią bardzo interesującą funkcją w tym oknie jest przycisk Load oraz suwak Overlay. Istnieje możliwość załadowania pliku obrazu pod v.ryświetlanym ekranem \V celu porówna nia go z tym obrazem (być może stanowił makietę dla tworzonego układu graficznego) oraz zwiększania lub zmniejszania jego widoczności za pomocą suwaka Overlay. Obraz ten po jawia się w lewym dolnym rogu. Domyślnie nie jest on pokazywany w widoku lupy, jednak można to zmienić poprzez zaznaczenie odpowiedniej opcji. Dzięki takim narzędziom pro gramista posiada wszechstronną kontrolę nad wyglądem oraz działaniem aplikacji.
Podsumowanie W tym momencie Czytelnik powinien posiadać już dobre pojęcie na temat kontrolek do stępnych w środowisku SDK. Nieobce powinny także być już menedżery układu graficznego, a także adaptery. Znając wymagania określonego typu ekranu, nie powinno być teraz trud ne szybkie identyfikowanie kontrolek oraz menedżerów układów graficznych potrzebnych do skonstruowania wyświetlanego ekranu.
W następnym rozdziale będziemy dalej zajmowali się interfejsem użytkownika - naszym celem będą opcje menu oraz okna dialogowe.
ROZDZIAŁ
s Praca z menu i oknami d ialog owymi
W rozdziale 3. omówiliśmy pojęcia zasobów, dostawców treści oraz intencji podstawowych bloków budulcowych w środowisku Android SDK. Następnie w rozdziale 4. zajęliśmy się kontrolkami interfejsu użytkownika i układami graficznymi. W niniejszym rozdziale pokażemy, jak posługiwać się w Androidzie menu oraz oknami dialogo,,-ymi. Zestaw Android SDK pozwala na dokladną obsługę menu oraz okien dialogowych. W tym rozdziale wyjaśnimy, jak korzystać z kilku rodzajów menu obsługiwa nych przez Androida: standardowych menu, podmenu, menu kontekstowych, menu w postaci ikon, menu drugorzędnych oraz menu alternatywnych. W Androidzie menu są widziane jako zasoby. Podobnie jak w przypadku pozo stałych rodzajów zasobów można je wczytywać z plików XML. Dla każdego wczytanego elementu menu zostaje utworzony identyfikator zasobów. W tym rozdziale szczegółowo zajmujemy się takimi zasobami menu w formie XNlL. Pokażemy także, w jaki sposób wykorzystywać automatycznie wygenerowane identyfikatory zasobów dla każdego rodzaju elementów menu. .l\astępnie zwrócimy uwagę na okna dialogowe. \\' Androidzie są one asynchro niczne, dzięki czemu zostaje zapewniona większa elastyczność. Osoby przyzwy czajone do pracy w środowiskach programistycznych obsługujących okna dia logowe synchroniczne (na przyklad Microsoft Windows) mogą uznać okna dialogowe asynchroniczne za nieco nieintuicyjne. Dzięki omówieniu podstaw two rzenia oraz stosowania okien dialogowych w Androidzie wprowadzimy pewną intuicyjną abstrakcję, ułatwiającą pracę z oknami dialogowymi asynchronicznymi.
Menu w Androidzie Bez wątpienia osoby pracujące w takim środowisku jak Swing obsługiwane przez język Java, Windows Presentation Foundation (WPF) w systemie Win dows lub w jakimkolwiek innym szkielecie interfejsu użytkownika z pewnością zetknęły się z menu. Poza obszerną obsługą menu Android wprowadza rów nież nowe wzorce menu, takie jak menu X.\!1L oraz menu alternatywne.
1 84
Android 2. Tworzenie aplikacji
Rozdział ten rozpoczniemy od omówienia podstawo>vych klas tworzących szkielet menu w Androidzie. W międzyczasie opiszemy, jak tworzyć menu i ich elementy, a także w jaki sposób system ma reagować na te elementy. Najważniejszą klasą obsługującą menu w Androidzie jest klasa a nd ro i d . view. Menu. Każda aktywność w Androidzie jest powiązana z obiektem menu tego typu, w którym można zawrzeć wiele elementów menu oraz podmenu. Elementy menu są reprezentowane przez klasę android . view.Menuitem, a podmenu przez android . view. SubMenu. Związki pomiędzy nimi zostały naszkicowane na rysunku 5 . 1 . Ściślej mówiąc, nie jest to diagram klas, lecz diagram strukturalny, zaprojektowany po to, aby pomóc dostrzec powiązania pomiędzy różnymi klasami i funkcjami dotyczącymi menu. Modu/menu
Aktywność
.J "
Zawiera pojedyncze menu
.J "
onCreateOptionMenu (wywołanie)
, ....
onoptionsltemSelected (wywołanie)
--
Rysunek 5.1 . Rysunek klas menu w Androidzie Można tworzyć grupy elementów menu poprzez przydzielenie każdemu z nich identyfika tora grupy, stanowiącego w istocie atrybut. Elementy menu posiadające ten sam identyfi kator są uznawane za członków grupy. Poza identyfikatorem grupy element menu posiada także nazwę (tytuł), swój niepowtarzalny identyfikator oraz identyfikator kolejności na liście (numer). Ten ostatni służy do uporządkowania kolejności elementów wewnątrz menu. Na przyklad: jeśli jeden z elementów posiada wartość identyfikatora kolejności równą 4, a inny element ma przyporządkowaną wartość 6, pierwszy z obiektów będzie widniał ponad obiektem drugim. Pewne zakresy wartości są zarezerwowane dla określonych rodzajów menu. Kolejność ele mentów menu drugorzędnych, które są uznawane za mniej istotne od pozostałych, rozpo czyna się od wartości Ox30000 i jest definiowana przez stałą Men u . CATEGORY_ SECONDARY. Inne rodzaje kategorii menu - na przykład menu systemowe, menu alternatywne czy menu kontenerowe - posiadają odmienne zakresy wartości kolejności. Wartości elementów menu systemowego rozpoczynają się od Ox20000 i są defmiowane przez stałą Menu . CATEGORY_SYSTEM. Wartości elementów menu alternatywnego rozpoczynają się od Ox40000 i są definiowane przez stałą Men u . CATEGORY_ALTERNATIVE. Wartości elementów menu kontenerowego roz poczynają się od OxlOOOO i są definiowane przez stałą Men u . CATEGORY_ CONTAINER. Przyglą-
Rozdział 5 • Praca z menu i oknami dialogowymi
1 85
dając się zakresom wartości tych stałych, można stwierdzić, w jakiej kolejności elementy menu pojawiają się na ekranie (omówimy rodzaje elementów menu w paragrafie „Praca z innymi rodzajami menu"). Na rysunku 5 . 1 widoczne są również dwie wywoływane metody, dzięki którym można two rzyć i odpowiadać na elementy menu: onC reateOptionsMenu oraz onOptionsitemSelected. Zajmiemy się nimi w następnych ustępach.
Tworzenie menu W środowisku Android SDK nie ma potrzeby tworzenia obiektu menu od podstaw. Ponie waż aktywność jest powiązana z pojedynczym menu, jest ono tworzone dla tej aktywności i przekazywane do metody wywoławczej one reateOptionsMenu klasy tej aktywności (jak wskazuje nazwa metody, menu w Androidzie zwane są także menu opcji). Metoda ta umoż liwia zapełnienie przekazywanego jej menu zestawem elementów menu (listing 5 . 1 ).
Listing 5.1 . Sygnatura metody onCreateOptionsMenu @Over ride public boolean onC reateOptionsMenu(Menu menu) {
li zapełnia elementami
menu
. . . ret u rn t rue ; } Po zapełnieniu menu elementami kod powinien zwrócić wartość t rue, co oznacza uwi docznienie tego menu. Jeżeli metoda zwróci wartość false, menu będzie niewidoczne. Kod przedstawiony w listingu 5.2 pokazuje, w jaki sposób dodać trzy elementy do menu za po mocą identyfikatora grupy oraz identyfikatorów elementów menu i identyfikatora kolejno ści o wartościach wzrastających.
Listing 5.2. Dodawanie elementów do menu @Over ride public boolean onCreateOptionsMenu ( Menu men u ) {
li wywołuje klasę bazową,
zawiera)Qcą listy menu systemowych s u p e r . onCreateOptionsMe n u ( menu ) ;
menu add ( O // Grupa , 1 // identyfikator elementu O I/kolejność , "Dodaj " ) ; li tytuł .
,
menu . add ( 0 , 2 , l , " element2 " ) ; men u . add ( 0 , 3 , 2 , "Wyczyść" ) ; li
Ważne jest, żeby została zwrócona wartość true, co spowoduje wyświetlenie menu return t ru e ;
1 86
Android 2. Tworzenie aplikacji
Powinna także zostać v.rywołana implementacja klasy podstawowej tej metody, aby system miał możliwość zapełnienia menu elementami menu systemowego. Żeby elementy menu systemowego były oddzielone od pozostałych elementów, Android dodaje je, począwszy od wartości kolejności równej Ox20000 (wspomnieliśmy wcześniej, że stała Menu . CATEGORY_SYSTEM definiuje identyfikator kolejności dla tego typu elementów). Pierwszym elementem wymaganym do dodania elementu jest identyfikator grupy (liczba całkowita). Drugim jest identyfikator elementu menu, odsyłany do wywoł)7'vanej funkcji po wybraniu tego elementu. Trzeci argument reprezentuje identyfikator kolejności. Ostatnim argumentem jest nazwa lub tytuł elementu menu. Można skorzystać z zasobu ty pu string u mieszczonego w pliku stałych R.java, zamiast wpisywać tekst. Identyfikatory grupy, elementu oraz kolejności są całkowicie opcjonalne; jeżeli nie ma potrzeby ich defi niowania, można wprowadzić argument Menu. NONE.
Praca z grupami menu Pokażemy teraz, w jaki sposób można pracować z grupami menu. Listing 5.3 przedstawia sposób dodania dwóch grup menu: Grupy 1 oraz Grupy 2. Listing 5.3. Zastosowanie identyfikatorów grup do utworzenia grup menu @Over ride public boolean onCreateOptionsMe n u ( Menu men u ) {
//Grupa 1 int groupl l; men u . ad d ( g roupl , l , l , " g l . item l " ) ; menu . a dd ( g roupl , 2 , 2 , " g l . item2 " ) ; =
//Grupa 2 int g roup2 = 2 ; menu . ad d ( g roup2 , 3 , 3 , "g2 . iteml " ) ; menu . ad d ( g roup2 , 4 , 4 , "g2 . item2 " ) ;
return t rue; li ważne, żeby została zwrócona wartość true }
Zwróćmy uwagę, że identyfikatory elementów oraz kolejności są niezależne dla każdej gru py. Jaki jest więc pożytek z grupy? W Androidzie dostępny jest zbiór metod, korzystających z identyfikatorów grupy. Za ich pomocą można kontrolować elementy menu w danej grupie: removeGroup ( id ) setGroupCheckable ( id , chec k able , exclusive) setGroupEnable d ( i d , boolean enabled) setGroupVisible ( id , visible) Metoda removeGroup usuwa wszystkie elementy z grupy o podanym identyfikatorze. Można włączać lub wyłączać elementy menu w danej grupie za pomocą metody setGroupEnabled. W podobny sposób, stosując metodę setG roupVisible, kontrolujemy widoczność grupy elementów menu.
Rozdział 5 • Praca z menu i oknami dialogowymi
1 87
Metoda setGroupCheckable jest dosyć interesująca. Dzięki niej pojawia się znak zaznacze nia obok elementu, który zostanie wybrany przez użytkownika. Jeżeli metoda ta zostanie użyta wobec całej grupy, wszystkie jej elementy uzyskają tę właściwość. W przypadku usta nowienia w tej metodzie flagi wyłączności możliwe stanie się zaznaczenie wyłącznie jednego elementu w grupie. Pozostałe elementy grupy będą niezaznaczone. Wiemy już, w jaki sposób zapełnić główne menu aktywności elementami oraz pogrupować je zgodnie z ich przeznaczeniem. Teraz pokażemy, w jaki sposób ustanowić reakcję systemu na wybór elementu menu.
Odpowiedź na wybór elementów menu Istnieje wiele sposobów przypisania odpowiedzi n a kliknięcie elementu menu
w
Andro
idzie. Można wykorzystać metodę onOptionsitemSelected klasy aktywności, wprowadzić samodzielne obiekty nasłuchujące lub zastosować intencje. v\T kolejnych paragrafach omó
wiiny każdą z wymienionych metod.
Odpowiedź na kliknięcie za pomocą metody onOptionsltemSelected Po kliknięciu elementu menu Android wywołuje metodę Activity (listing
onOptionsitemSelected w klasie
5.4).
Listing 5.4. Sygnatura oraz treść metody onOptionsltemSelected @Ove r ride public boolean onOptionsitemSelected ( Menuitem it em ) { switch ( item . getitemid ( ) ) { } li dla elementów klikniętych return t ru e ; li dla pozostałych elementów . . . return super. onOptionsitemSelected ( item ) ; } Podstawą działającego tu algorytmu jest sprawdzenie identyfikatora elementu menu po przez metodę getitemid ( ) klasy Menu I t em oraz wykonanie odpowiedniej czynności. Jeżeli
metoda onOp t i o n s i t emSelected ( ) przetwarza element menu, zostaje zwrócona wartość t rue. Zdarzenie z menu nie będzie przenoszone dalej. Jeżeli wywołaniami elementu menu nie zajmuje się metoda onOptionsitemSelected ( ) , wywołuje ona metodę nadrzędną poprzez super. onOptionsitemSelected. Domyślna iinplementacja metody onOptionsitemSelected ( ) zwraca wartość false, co powoduje „zwykłe" przetwarzanie zdarzenia. Taka forma przetwarza nia obejmuje alternatywne metody wywołania odpmviedzi na kliknięcie elementu menu.
Odpowiedź na kliknięcie za pomocą nasłuch iwaczy Odpowiedzi na kliknięcie definiuje się przeważnie
za
pomocą przesłonięcia metody onOption
ItemSelcted; jest to technika zalecana z powodu poprawienia wydajności. Jednak element me nu pozwala również na zarejestrowanie nasłuchiwacza, pełniącego rolę wywołania zwrotnego.
1 88
Android
2. Tworzenie aplikacji
Jest to proces dwuetapowy. Pierwszy etap polega na zaimplementowaniu interfejsu OnMenu c+Clicklistener. Następnie przekazuje się wystąpienie tej implementacji do elementu menu.
Po kliknięciu elementu zostanie wywołana metoda onMenuitemClick ( ) interfejsu onMenu '+Clicklistene r (listing 5.5).
Listing 5.5. Zastosowanie nasłuchiwacza jako wywołania zwrotnego w przypadku kliknięcia elementu menu li Etap 1
public class MyResponse implements OnMenuClickListener { li Jakaś zmienna lokalna, na której można pracować li. . .
li Jakieś konstruktory
@override boolean onMenuitemClick(Menuitem item)
{
}
li Wykonuje zadanie return t rue;
}
li Etap 2 MyResponse myResponse = new MyResponse( . . . ) ; menuitem. setOnMenuitemClicklistener(myRespons e ) ;
Metoda onMenuitemClick zostaje wywołana po wyświetleniu elementu menu. Kod zostaje wykonany natychmiast po kliknięciu elementu, jeszcze zanim zostanie wywołana metoda onOptionsitemSelected. Jeżeli metoda onMenuitemClick zwróci wartość t rue, nie zostaną wykonane następne wywołania zwrotne - w tym także metoda onOptionsitemSelected. Oznacza to, że kod nasłuchiwacza ma pierwszeilstwo przed metodą onOption sitemSelected.
Wykorzystanie intencji do wywołania odpowiedzi na kliknięcie elementu menu Istnieje także możliwość powiązania elementu menu z intencją poprzez wykorzystanie metody setintent ( inten t ) klasy Menu Item. Domyślnie element menu nie jest powiązany z żadną intencją. Jednak jeśli intencja jest powiązana z takim elementem i nic innego go nie prze twarza, to domyślnym zachowaniem jest przywołanie intencji za pomocą metody s t a rt c+Activity ( intent ) . Jeśli ten sposób ma zadziałać, wszystkie procedury obsługujące zwłaszcza metoda onOptionsitemSelected - powinny wywoływać nadrzędną klasę metody onOptionsitemSelected ( ) dla elementów nieprzetwarzanych. Ewentualnie można na ten sposób spojrzeć następująco: system daje metodzie onOptionsitemSelected pierwszeńsnvo w przetworzeniu elementu listy menu (nie licząc oczywiście nasłuchiwacza). Jeżeli metoda onOptionsitemSelected nie zostanie przesłonięta, to podstawowa klasa szkieletu Androida v.rykona czynności potrzebne do przywołania intencji wobec elementu menu. Jednak jeżeli metoda ta zostanie przesłonięta, ale nie interesuje nas dany element menu, należy v.ywołać metodę nadrzędną, która z kolei zajmie się wywołaniem intencji.
Rozdział 5
•
Praca z menu i oknami dialogowymi
1 89
Podsumowując: albo nie należy przesłaniać metody onOptions itemSelected, albo należy ją przesłonić i przywołać metodę nadrzędną dla elementów menu, które nie są przetwarzane.
Utworzenie środowiska testowego do sprawdzania menu Jak n a razie wszystko jest proste i zrozumiałe. Czytelnicy nauczyli się, w jaki sposób tworzyć menu oraz jak definiować dla nich reakcje za pomocą różnych rodzajów "'')'Wołań. Teraz pokażemy przykładową aktywność testującą interfejsy API, które do tej pory przedstawiliśmy. Celem tego ćwiczenia jest utworzenie prostej aktywności, w której będzie zawarty widok tekstowy. Widok ten będzie pełnił rolę testera. Przy każdym menu będziemy wypisywać na zwę oraz identyfikator elementu menu w tym widoku tekstowym. Efekt końcowy będzie wyglądał tak, jak na rysunku 5.2.
Rysunek 5.2. Przykładowa aplikacja men u Na rysunku 5.2 widoczne są dwie interesujące nas rzeczy: menu oraz widok tekstowy. Menu pojawia się u spodu ekranu. Nie będzie jednak widoczne po uruchomieniu aplikacji; ko nieczne będzie kliknięcie przycisku Menu na emulatorze lub urządzeniu, żeby menu zostało wyświetlone. Drugim zajmującym nas elementem jest widok tekstu na górze ekranu, w któ rym wyświetlane są wiadomości dotyczące błędów. Podczas klikania elementów dostępnych w menu ich nazvvy będą wyświetlane w widoku tekstowym. Po kliknięciu elementu Wyczyść program usunie zawartość widoku tekstowego.
Na rysunku 5.2 nie jest przedstawiony początkowy stan a plikacji. Stanowi on ilustrację
"•1'f'1""i11!01i11?"• omawianych typów menu w tym rozdziale.
Żeby zaimplementować środowisko testowe, należy wykonać następujące czynności: l.
Stwórz plik XML układu graficznego, w którym będzie umieszczony widok tekstowy.
2. Stwórz klasę Activity, która będzie przecho;vywać układ graficzny zdefiniowany w punkcie 1 . 3 . Skonfiguruj menu. 4. Dodaj standardowe elementy do menu.
1 90
Android 2. Tworzenie aplikacji
5. Dodaj elementy drugorzędne do menu. 6. Stwórz odpowiedzi na kliknięcie dla tych elementów. 7. Zmodyfikuj plik AndroidManifest.xml w taki sposób, żeby była wyświetlana właściwa nazwa aplikacji. Każdy z tych etapów zostanie poniżej szczegółowo omówiony oraz zostanie zaprezentowa ny kod potrzebny do utworzenia uprzęży testowej.
Utworzenie układu graficznego w pliku XML Pierwszy etap polega na utworzeniu prostego pliku XML układu graficznego, zawierającego widok tekstowy (listing 5.6). Plik ten może być wczytywany do aktywności podczas jej uru chamiania. Listing 5.6. Plik XML układu graficznego, zastosowany w środowisku testowym c?xml version= " l . O " encoding="utf - 8 " ?>
Utworzenie aktywności W drugim etapie tworzymy aktywność, co jest równie łatwym procesem. Założywszy, że plik układu graficznego z pierwszego etapu jest dostępny w katalogu \res\layout\main.xm/, można skorzystać z jego identyfikatora zasobów do zapełnienia widoków aktywności (listing 5. 7). Listing 5.7. Klasa aktywności menu w środowisku testowym public class SampleMenusActivity extends Activity { li Należy to zainicjować w metodzie onCreateOptions Menu myMenu = n u l l ;
@Over ride public void onCreate(Bundle savedinstanceState) { supe r . onCreate( savedinstanceState) ; setContentView ( R . layout . main ) ; } }
Rozdział 5 • Praca z menu i oknami dialogowymi
1 91
',- celu zachowania przejrzystości nie zawarliśmy tutaj instrukcji importu. W środowisku Eclipse można automatycznie tego dokonać poprzez v.rybranie opcji Source/Organize Imports w menu kontekstov.rym edytora.
Konfiguracja menu Gdy już mamy widok oraz aktywność, możemy przejść do trzeciego etapu: przesłonięcia metody onCreateOptionsMenu i skonfigurowania menu za pomocą kodu (listing 5.8). Listing 5.8. Konfi gu rowanie menu za pomocą kodu @Over ride public boolean onCreateOptionsMenu(Menu menu) {
li należy
wywołać metodę nadrzędnq w celu dolqczenia menu systemowych supe r . onCreateOptionsMen u ( menu ) ; thi s . myMenu = menu;
li dodaje kilka
normalnych menu addRegularMenuitems (menu ) ; drugorzędnych menu add5SecondaryMenuitem s ( menu ) ;
li dodaje kilka
li W celu
uwidocznienia menu musi zostać zwrócona wartość true menu nie będzie widoczne return t rue;
li Przy wartościfa/se
}
Kod z listingu 5.8 wywołuje najpierw nadrzędną metodę onC reateOptionsMenu, dzięki czemu uzyskuje ona możliwość dodania systemowych menu.
11•1111
W dotychczasowych wersjach środowiska Android
SDK m etoda one reateOptionsMenu
-'K!OS- nie dodaje nowych elementów menu. Jednak w kolejnych edycjach może to się zmienić, więc warto wywoływać metodę nadrzędną.
Kod zapamiętuje następnie obiekt Menu, gdyż będzie on później modyfikowany w celach demonstracyjnych. Następnym etapem jest dodanie kilku standardowych elementów menu oraz kilku elementów drugorzędnych.
Dodawanie elementów menu standardowego Etap czwarty: dodanie kilku standardowych elementów do menu. Kod funkcji addRegular '-+-Menultems został przedstawiony w listingu 5.9. Listing 5.9. Funkcja addReg ul arMen ultems private void addRegularMenuitems (Menu menu)
{
int base=Menu. FIRST; li wartość wynosi 1
1 92
Android 2. Tworzenie aplikacji
menu . add ( base, base , base , " Dodaj " ) ; men u . a d d ( ba s e , base+ l , base+l , " element 2 " ) ; menu . ad d ( base , base+2, base+2, "Wyczyść" ) ; menu . add ( base, base+3, base+3 , " ukryj d rugorzędny" ) ; men u . add ( base , base+4, base+4 , " pokaż d rugorzędny " ) ; menu . add ( ba s e , base+S , base+S, "wlącz d rugorzędny" ) ; menu . add (base, base+6, base+6 , "wylącz d rugorzędny " ) ; men u . add ( base , base+7, base+7 , " zaznacz d rugorzędny" ) ; menu .add ( ba s e , base+8, base+8 , " odznacz d rugorzędny " ) ; }
Klasa Menu definiuje kilka przydatnych stałych, wśród nich stałą Men u . FIRST. Można ją wy korzystać jako podstawę numeracji identyfikatorów menu oraz innych sekwencji liczbo wych związanych z menu. Zauważmy, w jaki sposób możemy powiązać identyfikator grupy z wartością base i zwiększać jedynie wartości identyfikatorów kolejności oraz identyfikato rów poszczególnych elementów. Dodatkowo w kodzie zostało umieszczonych w celach de monstracyjnych kilka niestandardowych elementów menu, takich jak „ukryj drugorzędny", „włącz drugorzędny" i innych.
Dodanie elementów menu drugorzędnego Dodajmy teraz kilka elementów menu drugorzędnego, aby wykonać piąty etap (listing 5.10). Jak wcześniej wspomnieliśmy, kolejność elementów menu drugorzędnego rozpoczy na się od wartości Ox30000 i jest zdefiniowana przez stałą Men u . CATEGORY_SECONDARY. Po nieważ wartość ta jest wyższa od analogicznej wartości elementów menu standardowego, elementy te będą znajdowały się poniżej elementów standardowych w menu. Zauważmy, że jedynie kolejność rozmieszczenia odróżnia elementy standardowe od drugorzędnych. We wszystkich innych aspektach elementy te niczym się od siebie nie różnią. Listing 5.1 O. Dodawanie elementów menu drugorzędnego private void addSSecondaryMenuitems (Menu menu) { li Elementy drugorzędne sq wyświetlane tak samo, jak inne elementy
int base=Menu. CATEGORY_SECONDARY; menu . ad d ( base, base+l, base+l , " d rugorz . men u . add ( base, base+2, base+2 , "d rugorz . menu . add ( base , base+3, base+3 , "d rugorz . menu . add ( base, base+3, base+3 , " d rugorz . menu . add ( base, base+4 , base+4 , " d rugorz .
element element element element element
l" ) ; 2") ; 3" ) ; 4" ) ; S") ;
}
Odpowiedź na kliknięcie elementu menu Po skonfigurowaniu menu przechodzimy do szóstego etapu: przypisywania odpowiedzi na kliknięcie. Po kliknięciu elementu Android wywołuje metodę onOptionsiternSelcted klasy Activi t y poprzez przesłanie odniesienia do tego klikniętego elementu. Następnie metoda getiternid ( ) klasy Menu Item rozpoznaje wybrany element.
Rozdział 5 • Praca z menu i oknami dialogowymi
1 93
Nie jest rzadkością natrafienie na instrukcję switch lub kombinacje i f oraz el se, których celem jest wywołanie różnych funkcji w odpowiedzi na kliknięcie elementu. Listing 5. 1 1 przedstawia standardowy proces odpowiadania na kliknięcie za pomocą wywoły>vanej me tody onOptionsitemSelected (nieco lepszy sposób wykonania tej samej czynności został zaprezentowany w podrozdziale „Wczyty>vanie list menu z plików XML", gdzie będą sto sowane symboliczne nazwy dla identyfikatorów elementów menu).
Listing 5.1 1 . Odpowiedź na kliknięcie e lementu menu @Over ride public boolean onOptionsitemSelected(Menuitem item) { 1) { if ( item . getitemid ( ) appendText ( "\nwitaj " ) ; } else if ( item . getitemid ( ) 2) appendText ( "\nelement2 2 " ) ; } else if (item . getitemi d ( ) 3) emptyText ( ) ; } else if (item . getitemi d ( ) 4) { li ukryj drugorzędny this . appendMenuitemText ( item ) ; ==
==
==
==
this . myMenu . setGroupVisible (Men u . CATEGORY_SECONDARY , false ) ;
} else if ( item . getitemi d ( ) 5) { li pokaż drugorzędny this . appendMenuitemText ( item ) ; ==
this . myMenu . s etGroupVisible (Men u . CATEGORY_SECONDARY , t rue) ;
}
else if (item. getitemi d ( ) li włącz drugorzędny
==
6) {
this . appendMenuitemText (item ) ; this . myMenu . setGroupEnabled ( Menu . CATEGORY_SECONDARY , true) ; }
else if ( item . getitemid ( ) 7) { li wyłącz drugorzędny this . appendMenuitemText (item ) ; ==
this . myMenu . setGroupEnabled {Menu. CATEGORY_SECONDARY , false ) ;
}
else i f (item. getitemi d { ) li zaznacz drugorzędny
==
8) {
this . a ppendMenuitemText (item ) ; myMenu . setGroupCheckable (Menu. CATEGORY_SECONDARY , t rue, false ) ;
} else if ( item . getitemid ( ) 9) { li usuń zaznaczenie drugorzędnego this . appendMenu itemText ( item ) ; ==
myMe n u . setGroupCheckable {Men u . CATEGORY_SECONDARY , false, false ) ;
} else { this . a ppendMenultemTex t ( item ) ; } li powinien zwrócić wartość true, jeśli element jest przetwarzany return t ru e ; }
1 94
Android 2. Tworzenie aplikacji
W kodzie przedstawionym w listingu 5 . 1 1 są również przeprowadzane operacje na pozio mie grupy menu; wywołania tych metod zostały zaznaczone tłustym drukiem. Szczegóły klikniętego elementu są wyświetlane w widoku TextView. W listingu 5.12 zostały wypisane funkcje pozwalające umieszczać te informacje w kontrolce TextView. Zwróćmy uwagę na dodatkową metodę klasy Menu Item, pozwalającą wyświetlić tytuł elementu.
Listing 5.12. Fu nkcje umożliwiające wypisywanie danych w kontrolce testowej TextView li Dany ciqg znaków
tekstowych dodany do kontrolki TextView private void appendText ( String text) { TextView tv = (TextView ) this . findViewByid ( R . id . textViewid ) ; t v . setText ( t v . getText ( ) + text ) ;
} li Dany
element menu wyświetla swój tytuł w kontrolce Text View private void appendMenuitemText (Menuitem menu!tem) { St ring title = menuitem. getTitle ( ) . toString ( ) ; TextView tv = (TextView ) t h i s . findViewByid ( R . id . textViewid ) ; t v . setText ( t v . getText ( ) + " \ n " + title ) ;
li
}
Wyczyszczenie zawartości kontrolki TextView private void emptyText ( ) { TextView tv = ( TextView ) t h i s . findViewByid ( R . id . textViewid ) ; t v . setText ( " " ) ; }
Modyfikowanie pliku AndroidManifest.xml Ostatnim etapem tworzenia środowiska testowego jest aktualizacja pliku AndroidManifest.xml. Ten generowany automatycznie plik podczas tworzenia nowego projektu umieszczony jest w katalogu głównym projektu. Wewnątrz tego pliku przeprowadzany jest proces rejestrowania klasy A c t i vity (na przy kład SampleMenusActivity) oraz definiowany tytuł aktywności. Jak widać na rysunku 5.2, nasza aktywność nosi nazwę Przykładowa aplikacja menu. Wiersz odpowiedzialny za tytuł został pogrubiony w listingu 5.13.
Listing 5 . 1 3 . Plik AndroidManifest.xml przygotowany do testowa nia c?xml version=" l . 0" encoding="utf - 8 " ?> cactivity android : name=" . SampleMenusActivity" and roid : label="Przykładowa aplikacja menu"> ccategory and ro id : name=" and ro id . intent . ca tego ry . LAUNCHER" />
Rozdział 5 • Praca z menu i oknami dialogowymi
1 95
Za pomocą umieszczonych w tym podrozdziale fragmentów kodu można stworzyć w szybki sposób środowisko testowe pozwalające na sprawdzenie możliwości menu. Pokazaliśmy, jak stworzyć prostą aktywność uruchamianą wraz z widokiem tekstowym, a także w jaki sposób utworzyć menu i przypisać jego elementom reakcję na kliknięcie. Większość menu jest za projektowana na podstawie tego prostego, lecz funkcjonalnego wzorca. Rysunek 5.2 poka zuje, jakiego rodzaju interfejsu Ul należy się spodziewać po zakończeniu ćwiczenia. Przy pominamy jednak, że efekt końcowy może się różnić od przedstawionego na rysunku, ponieważ nie zademonstrowaliśmy sposobu, w jaki należy dodawać ikony do menu. Nawet po wstawieniu ikon interfejs środowiska testowego może się różnić od naszej wersji, ponie waż Czytelnik może zastosować inne obrazy.
Praca z innymi rodzajami menu Do tej pory zajmowaliśmy się prostszymi, chociaż funkcjonalnymi rodzajami menu. W trakcie korzystania ze środowiska SDK można zauważyć, że Android obsługuje również menu w formie ikon, podmenu, menu kontekstowe oraz menu alternatywne. Ten ostatni typ występuje wyłącznie w Androidzie. Następne paragrafy dotyczą niestandardowych rodzajów menu.
Rozszerzone menu Na rysunku 5.2 widnieje w prawym dolnym rogu ekranu element menu, zatytułowany More. W żadnym fragmencie kodu nie umieściliśmy tego elementu, zatem skąd on się tu wziął? Jeżeli aplikacja posiada więcej elementów menu, niż może wyświetlić na ekranie, Android wyświetla element More w menu, dzięki któremu użytkownik może przejść do pozostałych elementów. Takie rozszerzone menu pojawia się automatycznie, gdy trzeba wyświetlić zbyt dużą ilość elementów na małej przestrzeni. Ale rozszerzone menu mają ograniczenie: nie mogą przetwarzać ikon. Po kliknięciu przycisku More zostanie wyświetlone menu bez ikon.
Praca z menu w postaci ikon Skoro już wspomnieliśmy o menu zawierających ikony, przyjrzyjmy się im uważniej. W re pertuarze menu obsługiwany jest nie tylko tekst, lecz również obrazy lub ikony. Do repre zentowania elementów menu można używać samych ikon, jak i ikon wraz z tekstem. Pod czas stosowania ikon pojawia się jednak kilka ograniczeń. Po pierwsze, nie ma możliwości stosowania ikon w rozszerzonych menu, jak już zostało wspomniane w poprzednim para grafie. Po drugie, elementy menu zawierające ikony nie obsługują funkcji ich zaznaczania. Po trzecie, jeżeli tekst w elemencie menu jest za długi, zostanie obcięty o pewną liczbę zna ków w zależności od rozmiaru wyświetlacza (ograniczenie to dotyczy także elementów menu zawierających sam tekst). Utworzenie elementu menu w formie ikony jest bardzo proste. Najpierw buduje się stan dardov.ry element menu, a następnie stosuje się metodę seticon w klasie Menuitem do wy brania rysunku. Konieczne jest wprowadzenie identyfikatora zasobu rysunku, trzeba więc umieścić obraz lub ikonę w katalogu /res/drawable. Na przykład, jeśli plik nosi nazwę balony, jego identyfikator będzie następujący: R . d rawable . balony. Poniżej umieszczono przykład: li Dodaje element menu i zapamiętuje go, żeby móc następnie dodać ikonę do niego. Menuitem i t em 8 menu . ad d ( ba s e , base+8 , base+8 , " 0dznacz d rugorzędny " ) ; =
item8 . s eticon ( R . d rawable . balon y ) ;
1 96
Android 2. Tworzenie aplikacji
Podczas dodawania elementów do menu rzadko kiedy trzeba pilnować, żeby lokalna zmienna była zwracana przez metodę men u . add. Jednak w tym przypadku zwracany obiekt musi być zapamiętany w celu dodania ikony do obiektu menu. Z powyższego przykładu widać także, że typem zwracanym przez metodę menu . add jest Men u Item. Ikona będzie widoczna tak długo, jak długo wyświetlany będzie obiekt menu w głównym oknie aplikacji. Jeżeli obiekt ten będzie wyświetlany w rozszerzonym menu, ikona zostanie pominięta i widoczny stanie się sam tekst. Pokazany na rysunku 5.2 element menu, wy świetlający ikonę przedstawiającą balony, jest przykładem omówionego rodzaju obiektu.
Praca z podmenu Przyjrzyjmy się teraz podmenu w Androidzie. Na rysunku 5 . 1 zostały naszkicowane związki strukturalne pomiędzy obiektem SubMenu a obiektami Menu i Menu!tem. W obiekcie Menu może znajdować się wiele obiektów SubMenu. Każdy obiekt SubMenu jest dodawany do obiektu Menu poprzez wywołanie metody Me n u . addSu bMenu (listing 5 . 1 4). Elementy są do dawane do listy podmenu tak samo jak w przypadku zwykłego menu. Wynika to z faktu, że obiekt SubMenu wywodzi się z obiektu Menu. Nie można jednak umieszczać podmenu w obiekcie SubMenu. Listing 5.14. Dodawanie podmenu private void addSubMenu (Menu menu ) { li Elementy drugorzędne są pokazywane tak jak pozostałe obiekty int base=Menu . FIRST + 100; SubMenu sm menu . addSubMen u ( base, base+ l , Menu . NONE, "podmenu " ) ; s m . add ( base , base+2 , base+2 , " podelementl " ) ; s m . add ( ba s e , base+3 , base+3 , "podelement 2 " ) ; s m . add ( ba s e , base+4 , base+4, "podelement3 " ) ; =
li Ikony elementów podmenu nie są obsługiwane iteml . seticon ( R . d rawable . icon48x48_2 ) ; li Ten poniżej jest poprawny, jednak s m . seticon ( R . d rawable. icon48x48_ 1 ) ; li Spowoduje wyświetlenie wyjątku wykonawczego llsm. addSubMen u ( " spróbuj tego " ) ; }
Ponieważ obiekt SubMenu jest podklasą obiektu Menu, obsługuje metodę addSubMenu. 1 llilfl 'fil i'lil Sl • Kompilator nie wyświetli błędu przy próbie dołączenia podmenu do innego podmenu,
-'
zostanie jednak wyświetlony wyjątek wykonawczy.
W dokumentacji środowiska Android SDK widnieje także informacja, że podmenu nie ob sługują ikon elementów menu. Gdy zostanie dodana ikona do elementu menu, a następnie zostanie on przeniesiony do podmenu, ikona ta zostanie zignorowana, nawet jeśli nie pojawił się żaden błąd kompilacji lub błąd wykonawczy. Jednak samo podmenu może posiadać ikonę.
Rozdział 5 • Praca z menu i oknami dialogowymi
1 97
Zabezpieczanie menu systemowych \\-iększość aplikacji systemu Windows posiada takie menu, jak Plik, Edycja, Widok, Otwórz, Zamknij oraz Wyjście. Są to tak zwane menu systemowe. Zestaw Android SDK sugeruje ;,·stawianie podobnego zestawu menu do każdego menu opcji. Jednak żadna z aktualnych ;,·ersji środowiska Android SDK nie dołącza tych menu w procesie tworzenia menu. Można sobie wyobrazić, że systemowe menu zostaną zaimplementowane w przyszłych wersjach srodowiska programistycznego. Według dokumentacji programiści powinni w odpowiedni sposób przygotowywać kod, żeby można było wstawić menu systemowe, gdy będą dostęp ne. Dokonuje się tego poprzez wywołanie metody onCreateOptionsMenu w klasie nadrzęd :1ej, dzięki czemu możliwe stanie się dodawanie menu systemowych do grupy definiowanej przez stałą CATEGORY_SYSTEM.
Praca z menu kontekstowymi Użytkownicy aplikacji biurowych z pewnością natknęli się na menu kontekstowe. Na przy kład w programach systemu Windows dostęp do takiego menu uzyskuje się poprzez klik nięcie prawym przyciskiem dowolnego elementu interfejsu użytkownika. W Androidzie został zaadaptowany ten sam pomysł, korzystający z działania zwanego długim kliknięciem. W technice tej przycisk myszy jest przytrzymywany nieco dłużej niż w przypadku tradycyjnego kliknięcia. W takich handheldach jak telefony komórkowe kliknięcia myszy zostały zaimplementowa ne na najróżniejsze sposoby, w zależności od rodzaju sterowania. Jeżeli telefon jest zaopa trzony w kółko sterujące kursorem, jego wciśnięcie jest odpowiednikiem kliknięcia myszy. W urządzeniach posiadających panel dotykowy jego naciśnięcie lub stuknięcie pełni rolę kliknięcia. Natomiast w przypadku urządzeń posiadających klawisze sterujące kursora oraz przycisk wykonania akcji naciśnięcie tego przycisku jest równoznaczne kliknięciu przycisku myszy. Niezależnie od sposobu zaimplementowania myszy w urządzeniu dłuższe przytrzy manie elementu odpowiedzialnego za kliknięcie spowoduje wykonanie długiego kliknięcia. Pod względem struktury menu kontekstowe różni się od omawianego wcześniej standar dowego menu opcji (rysunek 5.3). Menu kontekstowe zawierają pewne niuanse, których brak w menu standardowych. Na rysunku 5.3 widać, że menu kontekstowe reprezentowane jest przez klasę ContextMenu w architekturze menu Androida. Podobnie jak klasa Menu, tak i obiekt ContextMenu może obejmować wiele elementów menu. Aby dodać elementy do menu kontekstowego, wyko rzystywane są te same metody klasy Menu. Największą różnicą pomiędzy klasami Menu a ContextMenu jest rodzaj obiektu będącego właścicielem danego rodzaju menu. Standar dowe menu przynależy do aktywności, podczas gdy menu kontekstowe - do widoku. Nale żało się tego spodziewać, ponieważ długie kliknięcia, uruchamiające menu kontekstowe, stosowane są wobec klikniętego widoku. Zatem aktywność może zawierać tylko jedno menu opcji, ale wiele menu kontekstowych. Ponieważ aktywność może obejmować wiele wido ków, a każdy z nich może posiadać własne menu kontekstowe, maksymalna ilość menu kontekstowych w aktywności jest równa ilości zawartych w niej widoków. Chociaż właścicielem menu kontekstowego jest widok, metoda potrzebna do zapełnienia takiego menu znajduje się w klasie Activi ty. Nosi ona nazwę activi t y . one reateContextMenu ( ) i z działania przypomina metodę activit y . o nC reateOptio n sMenu ( ) . Ta wywoływana metoda przenosi ze sobą również widok, w którym menu kontekstowe będzie zapełnione.
1 98
Android 2. Tworzenie aplikacji
Moduł menu
Aktywność
Zawiera pojedyncze menu +- onCreateContextMenu()
+- oncCreateltemsSelected
I
Widok
r
I
Powiizany �
ContextMenu
...._ ____.. ContextMenulnfo
rozszerza
rozszerza
,...._
Rejestr menu kontekstowego
Widok pochodny
Tworzy
i zwra ca
�
Pochodna ContextMenulnfo
Rysunek 5.3. Aktywności, widoki i menu kontekstowe istnieje jeszcze jeden godny uwagi problem z menu kontekstowymi. Podczas gdy metoda one reateOptionsMenu ( ) jest automatycznie wywoływana dla każdej aktywności, nie dotyczy to metody one reateeontextMenu ( ) . Widok w aktywności nie musi posiadać menu kontekstowe go. Na przykład mogą być obecne trzy widoki w aktywności, lecz może istnieć potrzeba włączenia menu kontekstowego tylko dla jednego z nich. Jeżeli dany widok ma posiadać menu kontekstowe, musi on zostać zarejestrowany wraz z aktywnością do pełnienia roli właściciela te go menu. Dokonuje się tego poprzez metodę a c t ivi t y . registe rForeontextMenu ( view ) , omówioną w podrozdziale „Rejestrowanie widoku dla menu kontekstowego". Zwróćmy teraz uwagę na przedstawioną na rysunku 5.3 klasę eontextMen u i nfo. Obiekt tego typu jest przepuszczany do metody o n e reateeontextMenu. Jest to jeden ze sposobów przekazywania przez widok dodatkowych informacji do tej metody. Żeby widok mógł tego dokonać, musi przesłonić metodę geteontextViewinfo ( ) i zwrócić pochodną klasę e o nt e x t --..M enu i n f o wraz z danymi reprezentującymi dodatkowe informacje. Żeby w pełni zrozu mieć tę interakcję, można zajrzeć do kodu źródłowego obiektu and ro id . view. View.
Zgodnie z dokumentacją środowiska Android SDK me nu kontekstowe nie obsługują
l"•"IWl?il'SI·"• skrótów, ikon ani podmenu.
Skoro znamy już ogólną strukturę menu kontekstowych, przyjrzyjmy się przykładowemu kodowi pokazującemu, w jaki sposób krok po kroku zaimplementować menu kontekstowe:
Rozdział 5
•
Praca z menu i oknami dialogowymi
1 99
1 . Zarejestruj widok dla danego menu kontekstowego w metodzie one reate ( )
aktywności. 2. Zapełnij menu kontekstowe za pomocą metody one reateeontextMenu ( ) . Musisz dokończyć etap 1 ., zanim ta metoda zostanie wywołana przez system Android. 3. Zdefiniuj odpowiedzi na kliknięcia poszczególnych elementów menu kontekstowego.
Rejestrowanie widoku dla menu kontekstowego Pierwszym etapem w implementacji menu kontekstowego jest zarejestrowanie widoku dla tego menu w metodzie one reate ( ) aktywności. Po utworzeniu środowiska testowego, omówio nego we wcześniejszej części rozdziału, możliwe jest zarejestrowanie widoku TextView dla menu kontekstowego w tym środowisku za pomocą kodu widocznego w listingu 5 . 1 5. �ajpierw należy znaleźć widok TextView, a następnie wywołać w aktywności metodę register '+Fo reontextMenu, jako argument wstawiając ten widok TextView. W ten sposób widok zosta nie skonfigurowany dla menu kontekstowych. Listing 5.1 S. Rejestrowanie widoku TextView dla menu kontekstowego @Ove r ride public void o n C reate( Bundle savedlnstanceState} { supe r . onereate( savedinstanceState) ; setContentView ( R . layout . main } ; TextView tv = ( TextView}this . findViewByid ( R . i d . textViewid ) ; }
registerForContextMenu (this . getTextView ( ) ) ;
Zapełnianie menu kontekstowego Po zarejestrowaniu przykładowego widoku TextView dla menu kontekstowych Android wywoła metodę one reateeontextMenu ( ) w której argumentem będzie ten widok. To wła śnie tutaj można zapełnić menu kontekstowe odpowiednimi elementami. Dzięki wywołanej metodzie one reateeontextMenu ( ) dostępne stają się trzy potrzebne argumenty. ,
Pierwszym argumentem jest domyślnie utworzony obiekt eontextMenu, drugi argument to widok (na przykład TextView), który wygenerował wywoływanie zwrotne, a trzeci argu ment stanowi klasa ContextMenuinfo, którą pokrótce omówiliśmy podczas analizowania rysunku 5.3. W wielu prostych przypadkach można po prostu zignorować trzeci argument. Jednak niektóre widoki mogą przenosić dzięki niemu dodatkowe informacje. W takich przypadkach trzeba będzie umieścić klasę eontextMenuinfo w podklasie, a następnie zasto sować dodatkowe metody, umożliwiające odczyt danych. Przykładami klas wywodzących się z obiektu ContextMenuinfo są AdapterContextMenuinfo oraz ExpandableeontextMenuinfo. Widoki, które są powiązane w Androidzie z bazodano wymi kursorami, wykorzystują klasę AdaptereontextMenuinfo do przekazywania identyfi katora krotki do widoku, w którym menu kontekstowe będzie wyświetlane. W pewnym sen sie można stosować tę klasę do dalszego zwiększania przejrzystości obiektu kryjącego się pod kliknięciem myszy, nawet w danym widoku. W listingu 5 . 1 6 została zaprezentowana metoda onereateeontextMenu ( ) .
200
Android 2. Tworzenie aplikacji
Listing 5.16. Metoda onCreateContextMenu() @Over ride public void onCreateContextMenu( ContextMenu menu, View v , ContextMenuinfo menulnfo) { menu . setHeaderTitle ( " Przyklado1·1e menu kontekstowe " ) ; menu . add(200, 200 , 200, "elemen t l " ) ; }
Tworzenie odpowiedzi na kliknięcie elementu menu kontekstowego Trzecim etapem implementacji menu kontekstowego jest zdefiniowanie odpowiedzi na kliknięcie jego elementów. Mechanizm tworzenia odpowiedzi w menu kontekstowym jest analogiczny do przeprowadzania tej czynności w menu opcji. Dostępna jest wywoływana metoda podobna do onOptionsitemSelected ( ) , nazwana onContext itemSelect ed ( ) . Obydwie są dostępne w klasie A c t i v ity. Listing 5 . 1 7 przedstawia zastosowanie metody
onContextitemSelected ( ) . Listing 5.17. Tworzenie odpowiedzi dla menu kontekstowego @Over ride public boolean onContextitemSelected ( Menuitem item) { if ( item. itemld ( ) some-men u - item - i d ) =
{ li przetwarza ten element menu return t rue; } inne przetwa rzanie wyjątku }
Praca z menu alternatywnymi Do tej pory nauczyliśmy się tworzyć i obsługiwać menu, podmenu oraz menu kontekstowe.
W Androidzie została zaprezentowana nowa koncepcja, zwana menu alternatywnymi, po
zwalająca elementom takiego menu stanowić część standardowych menu, podmenu oraz menu kontekstowych. Menu alternatywne pozwalają wielu aplikacjom w Androidzie na wzajemne użytkowanie. Takie menu alternatywne stanowią część międzyaplikacyjnego sys temu komunikacyjnego lub struktury użytkowania.
W szczególności menu alternatywne pozwalają na zamieszczanie menu jednej aplikacji we wnątrz drugiej aplikacji. Kiedy menu alternatywne są wybierane, zostaje uruchomiona do celowa aplikacja lub aktywność za pośrednictwem adresu URL, w celu przekazania danych wymaganych przez żądającą aktywność. Następnie \vywułana aktywność wykorzysta adres URL danych, które zostały przekazane za pomocą intencji. Żeby dobrze pojąć działanie menu alternatywnych, trzeba najpierw zrozumieć pojęcia dostawców treści, identyfikatorów URI treści, typów MIME treści oraz intencji (rozdział 3.). Ogólna zasada działania jest następująca: wyobraźmy sobie, że tworzymy ekran, który ma za zadanie wyświetlać dane. Najprawdopodobniej ekran ten będzie aktywnością. W tej aktyw ności będzie dostępne menu opcji pozwalających na różne sposoby modyfikowania tych
Rozdział 5 • Praca z menu i oknami dialogowymi
201
danych. Załóżmy także na chwilę, że pracujemy nad dokumentem lub notatką, definiowa nymi przez identyfikator URI oraz odpowiadający mu typ MIME. Jako programiści chce my, żeby urządzenie posiadało więcej aplikacji umożliwiających edytowanie lub wyświetla nie tych danych. Chcemy sprawić, żeby menu tych programów były wyświetlane jako część menu tworzonego dla naszej aktywności. Żeby przyłączyć elementy menu alternatywnego do naszego menu, należy wykonać nastę pujące czynności podczas konfigurowania menu w metodzie onCreateOptionsMenu:
1 . Stwórz intencję, której identyfikator URI danych jest ustanowiony dla pokazywanego w bieżącym momencie identyfikatora URI danych. 2. Przydziel tę intencję do kategorii CATEGORY_ALTERNATIVE. 3. Wyszukaj aktywności, które pozwalają na obsługę typu danych definiowanych przez identyfikator URL 4. Intencje wywołujące te aktywności dodaj jako elementy menu. Wymienione etapy mówią nam wiele na temat natury aplikacji w Androidzie, więc zasta nowimy się nad każdym z nich. Jak już wiemy, przyłączanie elementów menu alternatyw nego do standardowego menu przeprowadzane jest w metodzie one reateOptionsMenu:
@Override public boolean onC reateOptionsMe n u ( Menu men u } { }
Zastanówmy się, jaki kod tworzy tę funkcję. Najpierw musimy poznać identyfikator URI danych, na których zamierzamy pracować w danej aktywności. Można go uzyskać w nastę pujący sposób:
t hi s . getintent ( } . getData ( } Technika ta działa, ponieważ klasa Activity posiada metodę getintent ( ) , zwracającą identyfikator URI danych, dla których została przywołana aktywność. Taką aktywnością może być główna aktywność v.rywołana przez menu główne; w takim wypadku może nie po siadać intencji i metoda getintent ( ) zwróci wartość null. Pisząc kod, należy zabezpieczyć się przed podobną sytuacją. Teraz naszym celem jest odnalezienie innych programów, które potrafią pracować z tego rodzaju danymi. Wyszukiwanie przeprowadzamy, wstawiając intencję w miejsce argumen tu. Poniżej przedstawiamy kod pozwalający na skonstruowanie odpowiedniej intencji:
Intent c riterialntent = new Intent ( null, getintent ( } . getData ( ) ) ; intent . addCatego r y ( Intent . CATEGORY ALTERNATIVE) ; Po skonstruowaniu intencji dodamy także kategorię interesujących nas działań. Gwoli ści słości, interesują nas jedynie te aktywności, którą mogą być wywołane jako część menu al ternatywnego. Jesteśmy już gotowi do nakazania obiektowi Menu, aby wyszukał pasujące aktywności i dodał je jako opcje w menu (listing 5.18).
Listing 5.18. Zapełn ianie menu elementami menu alternatywnego li Wyszukuje pasujące aktywności i wypełnia nimi menu. menu . addintentOptions ( Menu . CATEGORY_ALTERNATIVE, // Grupa Menu . CATEGORY _ALTERNATIVE, li Unikatowe identyfikatory, które chcemy dołqczyć.
202
Android 2. Tworzenie aplikacji
Menu . CATEGORY ALTERNATIVE , getComponentName ( ) , n u1 1 ,
c rite rialntent , o,
null ) ;
li Kolejność li
Nazwa klasy wyświetlającej menu Name to jest ta klasa. li Bez szczegółów. li Uprzednio utworzona intencja, która li opisuje nasze wymagania.
li --tutaj,
ll Bezflag. li zwracane elementy menu
Zanim omówimy każdą linijkę kodu, wyjaśnimy, co rozumiemy pod pojęciem „pasujące aktywności". Pasujqca aktywność to taka aktywność, która potrafi przetworzyć przekazany jej identyfikator URI. Informacje dotyczące obsługiwanych identyfikatorów URI są zazwy czaj rejestrowane w plikach manifestach aktywności za pomocą identyfikatorów URI, dzia łań i kategorii. W Androidzie znajduje się mechanizm pozwalający używać obiektu Intent do wyszukiwania pasujących aktywności, jeśli ma się dane te atrybuty. Przyjrzyjmy się teraz uważniej listingowi 5.18. Metoda addintentOptions w klasie Menu odpowiedzialna jest za wyszukiwanie aktywności pasujących do identyfikatora URI intencji oraz atrybutów kategorii. Następnie metoda dodaje te aktywności do właściwej grupy w menu, korzystając z identyfikatorów elementów menu oraz identyfikatorów kolejności. Tym aspektem obowiązków metody zajmują się trzy pierwsze atrybuty. W listingu 5.18 grupą, od której za czniemy dodawanie nm·vych elementów menu, jest Me n u . CATEGORY _AL TERNATIVE. Ta sama stała jest używana jako wartość bazowa dla identyfikatorów elementów oraz kolejności. Kolejny argument wskazuje na w pełni kwalifikowaną nazwę składnika aktywności, której częścią jest nasze menu. W kodzie jest zastosowana pomocnicza metoda getComponentName ( ) ; w ramach ćwiczenia pozostawiamy Czytelnikowi znalezienie nazwy składnika z nazw klasy i pakietu. Nazwa składnika jest potrzebna, ponieważ za każdym razem, gdy jest dodawany nowy element menu, element ten będzie musiał wywoływać docelową aktywność. Żeby tego dokonać, system wymaga źródłowej aktywności, która uruchomiła aktywność docelową. Następnym argumentem jest tablica intencji, która powinna być stosowana jako filtr wobec zwracanych intencji. Kolejny argument wskazuje obiekt c rite riaintent, który dopiero co skonstruowaliśmy. Jest to kryterium wyszukiwania, którego chcemy użyć. Następujący po nim argument jest flagą typu Men u . FLAG_APPEND_ TO_ GROUP - wskazującą na to, czy elementy menu mają być dodawane do istniejącej grupy menu, czy też mają być podmieniane. Domyślna wartość wynosi O, co wskazuje na to, że elementy grupy menu mają być podmieniane. Ostatnim argumentem w listingu 5.18 jest tablica dodanych elementów menu. Można ko rzystać z takiego odniesienia do dodanych elementów w przypadku, gdy trzeba je w jakiś sposób zmodyfikować już po ich dodaniu. Wszystko brzmi prosto i pięknie. Pozostaje jednak kilka pytań bez odpowiedzi. Na przy kład: jakie będą nazwy dodanych elementów? Dokumentacja Androida jest w tym temacie wyjątkowo uboga. Zatem poszperaliśmy trochę w kodzie źródłowym, żeby dowiedzieć się, co ta funkcja w rzeczywistości robi poza wzrokiem użytkownika. Okazuje się, że klasa Menu jest jedynie interfejsem, więc nie widzieliśmy jej kodu źródłowego (w rozdziale 1. opisaliśmy, w jaki sposób uzyskać dostęp do kodu źródłowego Androidal.
Rozdział 5
•
Praca z menu i oknami dialogowymi
203
Klasą implementującą interfejs Menu jest Menu B u i lde r. W listingu 5.19 umieściliśmy kod podobnej metody, addintentOptions, z klasy MenuBuilder (wstawiliśmy ten kod wyłącznie w celach poglądowych; nie będziemy go szczegółowo objaśniać).
Listing 5.19. Metoda MenuBuilder.addlntentOptions public int addlntentOptions(int g roup, int id, int categoryOrder, ComponentName caller , Intent [ ] specifics , Intent inten t , int flags , Menu!tem [ ] outSpecificltems ) { PackageManager pm = mContext . getPackageManage r ( ) ; final List lri = pm . querylnten tActivi tyOption s ( caller, specific s , intent, O l ; final int N = lri ! = null ? l ri . size ( ) : O ; i f ( ( flags & F LAG_APPEND_TO GROUP) == 0 ) { removeGro u p ( g roup ) ; for ( int i=O; i
i tern . setintent ( rintent ) . ; if (outSpecificitems ! = n u l l && ri . specific l ndex >= O ) { outSpecificitems [ ri . specificindex] = item ;
} }
return N ;
} Zauważmy, że jeden wiersz w listingu 5.19 został wytłuszczony; ta część kodu odpowie dzialna jest za konstruowanie elementu menu. Zadanie określenia tytułu menu zostaje przekazane klasie Resolve!nfo. W kodzie źródłowym tej klasy widać, że filtr deklarujący tę intencję powinien posiadać powiązany z nią tytuł. Poniżej przykład:
Wartość label filtru intencji staje się nazwą menu. Można sprawdzić zachowanie tej klasy na podstawie przykładowej aplikacji Notepad.
204
Android 2. Tworzenie aplikacji
Praca z menu w odpowiedzi na zmianę danych Do tej pory zajmowaliśmy się menu statycznymi; zostają skonfigurowane na początku i nie zmieniają się dynamicznie w zależności od zawartości ekranu. Żeby stworzyć menu dyna miczne, należy zastosować dostępną w Androidzie metodę onPrepareOptionsMenu. Przy pomina ona metodę o n C reateOptionsMenu z wyjątkiem faktu, że jest wywoływana za każdym razem, gdy zostaje przywołane menu. Jest ona stosowana na przykład, gdy niektóre menu lub grupy menu mają być wyłączone na podstawie danych wyświetlanych na ekranie. Warto o tym pamiętać podczas projektowania menu. -
Musimy zająć się jeszcze jednym istotnym aspektem menu, zanim przejdziemy do okien dialogowych. Android umożliwia tworzenie menu za pomocą plików XML. Następny ustęp jest poświęcony właśnie zapoznaniu się z obsługą takich menu mających postać plików XML.
Wczytywanie menu poprzez pliki XML Aż do tej chwili tworzyliśmy nasze menu za pomocą kodu Java. Nie jest to najwygodniejszy sposób, ponieważ każde menu wymaga kilku identyfikatorów oraz stałych zdefiniowanych dla tych identyfikatorów. Niewątpliwie po pewnym czasie staje się to nużące. Zamiast tego można zdefiniować menu w plikach XML; Android na to pozwala, ponieważ menu są uznawane za zasoby. Tworzenie menu za pomocą plików XML ma kilka zalet, ta kich jak możliwość nadania nazwy menu, automatycznie tworzenie kolejności menu oraz przyznawanie identyfikatorów i tak dalej. Można także wprowadzić obsługę lokalizacji wo bec tekstu zawartego w menu. Żeby zaprojektować menu oparte na pliku XML, należy wykonać następujące czynności: l.
Zdefiniuj plik XML ze znacznikami menu.
2. Umieść plik w podkatalogu /res/menu. Nazwa pliku może być dowolna oraz nie istnieje ograniczenie co do ilości plików. Android wygeneruje automatycznie identyfikator tego pliku. 3. Wykorzystaj identyfikator zasobów tego menu, aby wczytać plik XML do menu. 4.
Zdefiniuj odpowiedzi na kliknięcie za pomocą identyfikatora zasobów wygenerowanego dla każdego elementu menu.
W poniższych paragrafach omówimy każdy z tych etapów oraz zaprezentujemy fragmenty kodu.
Struktura pliku XML zasobu menu Najpierw przyjrzymy się plikowi XML zawierającemu definicje menu (listing 5.20). Wszyst kie pliki menu rozpoczynają się od znacznika menu, po którym następuje seria znaczników g roup. Każdy znacznik g ro up odpowiada grupie elementów menu, omówionej na początku rozdziału. Można określić identyfikator grupy za pomocą wyrażenia @+id. W każdej grupie menu będą umieszczone elementy menu, których identyfikatory będą powiązane z nazwami symbolicznymi. W dokumentacji środowiska Android SDK można znaleźć wszystkie ar gumenty dla tych znaczników języka XNIL.
Rozdział 5 • Praca z menu i oknami dialogowymi
Listing 5.20.
Plik XML zawierający definicje
205
menu
cmenu xmln s : and roid= " h t t p : //schema s . android . com/apk/res/android">
Rozdział 6 • Prezentacja animacji dwuwymiarowej
239
and roid : layout height="fill_parent" />
Listing 6.6 przedstawia prosty menedżer Linea rlayout z umieszczonym wewnątrz niego prostym widokiem ListView. Powinniśmy jednak wyjaśnić pewną rzecz dotyczącą definicji widoku ListView. Jeżeli Czytelnik będzie pracował na aplikacji Notepad lub innych przy kładowych programach, zauważy zapewne, że identyfikator widoku ListView jest przeważ nie określany jako @android : id/list. Zgodnie z informacjami z rozdziału 3. odniesienie @and ro id : id/ list wskazuje na identyfikator predefiniowany w przestrzeni nazw and ro id. Pytanie brzmi: kiedy należy stosować odniesienie and ro i d : id, a kiedy nasz własny identyfi kator, na przykład @+ id/ li s t_ view_ id? Identyfikatora @and ro id : id/list u żywamy jedynie w przypadku, gdy aktywnością jest ListActivity. Aktywność ta zakłada, że widok ListVie1.;, określony przez ten predefi niowany identyfikator, jest dostępny do wczytania. W tym wypadku używamy raczej aktywn ości ogólnego przeznaczenia, a nie ListActivity, i musimy własnoręcznie zapełnić w jawny spos ób widok Lis tView. W związku z tym nie ma żadnych ograniczeń co do rodzaju iden tyfikatora, który ma reprezentować tę listę. Jednak można także wykorzystać o dnie sieni e @and roi d : id/list, ponieważ nie stwarza to żadnego konfliktu z powodu nieobecności ak tywności ListA c ti vit y . To taka mała dygresj a, warto jednak o niej pamiętać podczas tworzenia własnych widoków ListView poza aktywnością L i s t Ac t ivity. Gdy już posiadamy układ graficzny wymagany dla aktywności, możemy napisać kod odpowiedzialny za wczytanie tego pliku układu gra ficznego, dzięki czemu zostanie wygenerowany i nterfej s użytkownika (listing 6. 7). Listing 6.7. Kod aktywności odpowiedzialnej za animacj ę układu graficznego public class LayoutAnimationActivity extends Activity { @Over ride public void onCreate(Bundle savedlnstanceState) { s u pe r . onCreate( savedinstanceStat e ) ; setContentView ( R . layout . list_ layout ) ; setupListView( ) ; }
private void setupListView ( ) { String [ ) listltems = new String [ ) { " Element 1" , " Element 2 " , " Element 3 " " Element 4 " , " Element S " , " Element 6 " , }i ArrayAdapter listltemAdapter = new Ar rayAdapte r ( this , a n d roid . R . layout . simple list_ item_ l , listltems ) ; ListView lv = ( ListView) this . findViewByi d ( R . id . list_view id ) ; l v . setAdapt e r ( listltemAdapte r ) ; }
240
Android 2. Tworzenie aplikacji
Niektóre fragmenty kodu widocznego w listingu 5.6 są oczywiste, inne nie. Pierwsza część kodu w tradycyjny sposób wczytuje widok w oparciu o wygenerowany identyfikator układu graficznego R . layout . li s t_ layo u t Naszym zadaniem jest zapełnienie widoku L i s t View z tego układu graficznego sześcioma elementami. Te elementy tekstowe zostały wczytane do tablicy. Musimy ustanowić adapter danych wobec widoku ListView, żeby te elementy mogły zostać wyświetlone. .
Aby stworzyć wymagany adapter, musimy określić, w jaki sposób każdy z elementów będzie wstawiony podczas wyświetlania listy na ekranie. Układ graficzny określamy za pomocą p redefiniowanego układu, znajdującego się w strukturze Androida. W naszym przykładzie
układ graficzny \'\')'Znaczono jako: android . R . layout . simple_list_item_ l Innymi dostępnymi układami graficznymi widoku dla tych elementów są: simple_list_item 2 simple_list_ item_c hecked s imple_list_item_multiple_c hoice simple_list_item_single_ choice Można zajrzeć do dokumentacji Androida, aby się dowiedzieć, jak te układy graficzne wy glądają i jak się zachowują. Teraz możemy wywołać tę aktywność za pomocą dowolnego przycisku menu w aplikacji, po wstawieniu następującego kodu: Intent intent = new Intent ( inActivity, LayoutAnimationActivity . clas s ) ; inActivity . st a rtActivity ( intent ) ; Jednak - podobnie jak w przypadku wywołań innych aktywności - musimy zarejestrować aktywność LayoutAnimationActivity w pliku AndroidManifest.xrnl, jeżeli powyższe wy wołanie aktywności ma zadzi ałać Po niżej umieściliśmy potrzebny do tego kod: .
cactivity androi d : name=" . LayoutAnimationActivity" android: label="Testowa aktywność widoku animac j i "/>
Animowanie widoku ListView Po przygotowaniu środowiska testowego (listingi 6.6 i 6.7) nauczymy się wstawiać animacj ę skali do widoku ListView. Spójrzmy, w jaki sposób animacja ta zostaje zdefiniowana w pliku Xi\IIL (listing 6.8). Listing 6.8. Definiowanie animacji skali w pliku XML
Rozdział 6 • Prezentacja animacji dwuwymiarowej
241
Pliki definiujące animację są przechowywane w podkatalogu /res/anim. Przetłumaczmy te atrybuty XML na język polski. Wagi f rom i to są wskaźnikami początku oraz zakończenia procesu powiększania. W naszym wypadku powiększanie rozpoczyna się od wartości 1 i ta kie pozostaje dla osi x. Oznacza to, że element nie będzie powiększany ani zmniejszany w tej osi. Jednak w przypadku osi y powiększanie rozpoczyna się od wartości O . 1 i dąży do 1 . O. Innymi słowy, na początku animacji rozmiar obiektu stanowi jedną dziesiątą jego natural nego rozmiaru, do którego dąży w czasie trwania animacji. Cała operacja skalowania zajmie 500 milisekund. Środek działania znajduje się w połowie drogi obydwu osi (50%). Wartość s t a rt O f f s et odnosi się do czasu (w milisekundach), po którym animacja zostanie uru chomiona. Węzeł nadrzędny animacji skali wskazuje na zestaw animacji, który dopuszcza wprowadze nie większej ilości animacji. Omówimy również tego rodzaju przykład. Na razie jednak mamy do dyspozycji tylko jedną animację w zestawie. Nazwijmy ten plik s c a l e . xml i umieśćmy go w podkatalogu /res/anim. Nie jesteśmy jeszcze gotowi, żeby wstawić ten plik XML animacji jako argument w widoku ListVie1�; widok ten wymaga jeszcze jednego pliku X.NIL, który będzie zachowywał się jak pośrednik pomiędzy widokiem a zestawem animacji. Kod pliku XML, w którym zaimplementowane jest takie po wiązanie, został pokazany w listingu 6.9.
Listing 6.9. Definicja dla pliku XML stanowiącego kontroler układu graficznego clayoutAnimation xmlns : and roid="htt p : //schema s . android. com/apk/res/android" and roid : delay="30%" android: animationOrder=" reverse" android : animation="@anim/scale" /> Również ten plik należy umieścić w podkatalogu /res/anim. W naszym przykładzie zakła damy, że plik nosi nazwę l i s t_layout_ cont roller. Po przyjrzeniu się definicji pliku po średniczącego zrozumiemy, dlaczego jest on niezbędny. W pliku tym zostaje określone, że animacja tej listy powinna przebiegać w odwróconym porządku oraz że animacja każdego elementu będzie opóźniona o 30% względem całkowitego czasu trwania animacji. Znajduje się tu również odniesienie do pliku animacji, scale.xml. Zauważmy również, że w kodzie jest użyte odniesienie do tego pliku @anim/scale, zamiast jego nazwy. Gdy już posiadamy wymagane pliki XML z danymi wejściowymi, pokażemy, w jaki sposób należy zaktualizować definicję XML widoku Lis tView, żeby obejmowała ona animację XML jako argument. Najpienv przejrzyjmy dotychczas utworzone pliki Xl\II L:
li pojedyncza animacja skali / reslanimlscale . xml li plik pośredniczący l res/anim/list_ layout_ controlle r . xml li plik układu graficznego
widoku aktywności /res/layout/list_layout . xm l
Gdy t e pliki s ą gotowe, musimy zmodyfikować plik XML układu graficznego list_layout.xml w taki sposób, żeby widok ListView wskazywał plik list_layout_controller.xml (listing 6 . 1 0) .
242
Android 2. Tworzenie aplikacji
Listing 6.1 O. Zaktualizowany kod pliku List_Layout.xml
clistView and ro id : id= "@+id/list_ view_id" android : persistentDrawingCache="animation l scrolling"
android : layout_width=" fill_parent " and ro i d : layout_height=" f ill_pa rent " android: layoutAnimation="@anim/list_layout_controller" />
/>
Zmienione wiersze zostały wytłuszczone. Kluczowym znacznikiem jest a nd ro i d : layout -... A nimation, który wskazuje pośredniczący plik XML definiujący kontroler układu graficz nego za pomocą znacznika layoutAnimation (listing 6.9). Z kolei znacznik layoutAnimation odnosi się do animacji, w naszym v\Typadku animacji skali zdefiniowanej w pliku scale.xml. Android zaleca także wstawienie znacznika p e rs is t e n t D rawingCache, który optymalizuje animację i przesuwanie. Więcej informacji na jego temat można znaleźć w dokumentacji środo wiska Android SDK. Po zaktualizowaniu pliku list_layout.xml zgodnie z listingiem 6.10 wtyczka ADT środowi ska Eclipse automatycznie przekompiluje pakiet, nie zapomniawszy o tej zmianie. Gdyby śmy teraz uruchomili aplikację, zobaczylibyśmy, że animacja skali przeprowadzana jest na każdym elemencie. Zdefiniowaliśmy czas trwania animacji na 500 milisekund, zatem uj rzymy wyraźnie zmianę skali podczas rysowania obiektu. Możemy teraz eksperymentować z innymi rodzajami animacji. Sprawdzimy teraz animację typu alfa. W tym celu utworzymy plik /res/anim/alpha.xml i umieścimy w nim treść listingu 6. 1 1 . Listing 6.1 1 . Plik alpha.xml d o testowania animacji typu alfa calpha xmln s : an d roid="http: //schemas . android . com/apk/ res/android" android : interpolator="@and roid : anim/accele rate_ inte rpolator" android : f romAlpha= " 0 . 0 " a n d roid : toAlpha= " l . O " android : du ration= " lOOO" /> Animacja typu alfa odpowiedzialna jest za kontrolowanie blednięcia kolorów. W tym przy kładzie sprawiamy, że w ciągu 1000 milisekund ( 1 sekundy) kolor z przezroczystego staje się w pełni nasycony. Dobrze jest ustawić czas tnvania animacji na co najmniej I sekundę, w prze ciwnym wypadku zmiana nasycenia będzie trudna do zaobserwowania. W przypadku zmiany animacji pojedynczego elementu musimy zmienić rÓV\'11ież treść pliku pośredniczącego (listing 6.9), żeby wskazywała plik z nową animacją. Poniżej pokazaliśmy sposób wskazywania z animacji skali na animację typu alfa: clayoutAnimation xmlns : an d roid="http : //schema s . android . com/apk/res/android" android : delay="30%" android : animationOrder= " reverse" android: animation="@anim/alpha" />
Rozdział 6 • Prezentacja animacji dwuwymiarowej
243
Zmieniona linijka w tym kodzie została wytłuszczona. Spróbujmy teraz stworzyć animację łączącą zmianę położenia ze zmianą gradientu nasycenia koloru. Listing 6.12 przedstawia przykładowy kod takiej animacji. Listing 6.12. Połączenie an i macj i translacyjnej z animacją typu alfa w zestawie animacji ctranslate and roid : f romYDelta= " - 100%" and roid : toYDelta="O" android : du ration="SOO" /> calpha android : f romAlpha=" 0 . 0 " a n d roid : toAlpha=" l . 0 " android : du ration="SOO" />
Zwróćmy uwagę, w jaki sposób określiliśmy dwie animacje w zestawie animacji. Animacja translacyjna będzie przesuwała tekst z góry na dół w wydzielonym dla niego obszarze wy świetlania. Animacja typu alfa będzie powodować zmianę gradientu nasycenia koloru od przezroczystego do całkowicie nasyconego podczas zjeżdżania tekstu w dół. Wartość 500 czasu trwania animacji pozwoli użytkownikowi obserwować w wygodny sposób zmianę. Oczywiście znowu trzeba będzie zmienić plik pośredniczący layoutAnimation tak, żeby znalazło się w nim odniesienie do nowego pliku. Założywszy, że nazwą pliku zawierającego połączone animacje jest /res/anim/translate-alpha.xml, plik layoutAnimation będzie wyglą dał następująco: clayoutAnimation xmlns : an d roid="http: //schema s . android. com/apk/ res/android" and roid : delay="30%" android : animationOrder="reverse" android: animation="@anim/translate-alpha" />
Zobaczmy, w jaki sposób można używać animacji rotacyjnej (listing 6.13). Listing 6 . 1 3 . Pl i k XML a ni macj i rotacyj nej crotate xml n s : android="http : //schemas . android . com/apk/res/android" android : interpolator="@android: anim/accelerate_interpolator" and roid : f romDegrees= " 0 . 0 " android : toDeg rees="360" android : pivotX="SO%" and roid : pivotY="50%" and roid : du ration="SOO" />
Kod z listingu 6 . 1 3 spowoduje wykonanie jednego pełnego obrotu przez każdy element tek stowy wokół środka tego elementu. Czas trwania 500 milisekund całkowicie wystarczy, żeby obserwator dostrzegł animację. Podobnie jak w poprzednich wypadkach, tak i teraz muszą zostać zmodyfikowane pliki XML kontrolera animacji oraz układu graficznego L i st View, a aplikacja musi zostać ponownie uruchomiona, żeby animacja zadziałała. Omówiliśmy już podstawowe pojęcia dotyczące animacji układu graficznego, począwszy od prostego pliku animacji i przechodząc do powiązania go poprzez plik pośredniczący layout '+-Animati on z widokiem ListView. Ta wiedza wystarczy, żeby ujrzeć animowane efekty. Musi my omówić jednak jeszcze jedno pojęcie dotyczące animacji układu graficznego: interpolatory.
244
Android 2. Tworzenie aplikacji
Stosowanie interpolatorów Interpolatory wskazują animacji, w jaki sposób dana właściwość, na przykład gradient koloru, zmienia się w dziedzinie czasu. Czy będzie się ona zmieniała w sposób liniowy, czy w spo sób wykładniczy? Czy rozpocznie się szybko, lecz będzie zwalniała z biegiem czasu? Zasta nówmy się nad przykładem animacji typu alfa z listingu 6.1 1 :
and roid : f romAlpha= " O . O '' and roid : toAlpha= " l . 0 " and roid : du ration="lOOO" /> Animacja rozpoznaje zastosowany interpolator - w tym przypadku accelerate_interpolator. Istnieje odpowiedni obiekt Java, służący do definiowania tego interpolatora. Poza tym zwróćmy uwagę, że określiliśmy ten interpolator jako odniesienie do zasobów. Oznacza to, że musi istnieć plik odpowiadający identyfikatorowi anim/accelerate_interpolator, w którym opisany jest ten obiekt języka Java oraz jego dodatkowe parametry. Tak jest w istocie. Przyjrzyjmy się definicji pliku XML, do którego odniesieniem jest identyfikator @and ro id : anim/accele r a te_ '+inte rpolat o r: cacce l e ratel n te r p ola t o r xmlns : and roid="http : // schema s . an d roid . com/apk/res/android" factor= " l " />
Plik ten można odnaleźć w następującym podkatalogu pakietu Android:
/ res/ a nim/accele rate_interpolato r . x ml Znacznik XML ac cele ratelnte rpo l a to r odpowiada następującemu obiektowi środowiska Java:
android.view . a n imation . Accelerateinterpolator W dokumentacji języka Java dotyczącej tej klasy można zobaczyć, j akie znaczniki XML są dla niej dostępne. Zadaniem tego interpolatora jest zapewnienie współczynnika powielania danego przedziału czasowego w oparciu o krzywą hiperboliczną. Widać to w kodzie źródłowym interpolatora:
public float getinterpolation ( float input) { if (mFactor == l . Of ) { return ( f loat ) ( input * input ) ; }
el se { }
return ( float )Mat h . pow ( in p u t ,
2 *
mFactor) ;
}
Każdy interpolator w inny sposób implementuje metodę getinterpolation. W naszym przypadku, jeśli interpolator zostanie skonfigurowany tak, że współczynnik będzie wynosił 1 . O, zostanie zwrócony kwadrat tego współczynnika. W przeciwnym razie zostanie zwró cona potęga danych wejściowych, które będą nadal skalowane przez ten współczynnik. Za tem, jeżeli wartość współczynnika będzie wynosiła 1 . 5, zamiast funkcji kwadratowej ujrzymy funkcję sześcienną.
Rozdział 6 • Prezentacja animacji dwuwymiarowej
245
Poniżej wypisaliśmy listę obsługiwanych interpolatorów: AccelerateDecelerateinterpolator Accelerateinterpolator Cycleinterpolato r Decelerateinterpolat o r Linearinterpolator Anticipateinterpolator AnticipateOve rshootinterpolator Bounceinterpolator Overshootinterpolator
Żeby zaprezentować potencjalną elastyczność interpolatorów, przyjrzyjmy się pokrótce obiek towi Bounceinte rpolator, powodującemu podskakiwanie elementu (to znaczy jego na przemienny ruch w górę i w dół) do samego końca poniższej animacji: public class Bounceinterpolator implements Interpolator { p rivate static float bounce ( f loat t ) { return t � t * 8 . 0f ; } public float getinterpolation ( float t ) { t *= 1 . 1226f; if (t < 0 . 3535 f ) return bounce ( t ) ; else if ( t < 0 . 7408f) return bounce ( t 0 . 54719f) + 0 . 7 f ; else if ( t < 0 . 9644f) return bounce ( t 0 . 8526f) + 0 . 9f ; else return bounce ( t 1 . 0435f) + 0 . 95 f ; -
-
-
}
Zachowanie tych interpolatorów zostało omówione pod poniższym adresem:
http://developer.android.com/reference!android!view/animation/package-summary.htm W dokumentacji języka Java wymienione są również znaczniki XML, pozwalające na kontro lowanie każdej z tych klas. Jednak ciężko jest z dokumentacji wywnioskować przeznacze nie każdego typu interpolatora. Najlepiej jest samemu wypróbować wszystkie interpolatory i sprawdzić skutki ich działania. Pod poniższym adresem można również przejrzeć kod źró dłowy:
http://android.git.kernel.org!?p=platform%2Fframeworks%2Fbase.git&a=search&h=HEAD &st=grep&s=Bouncelnterpolator Na tym zakończymy wywody poświęcone animacji układu graficznego. Przejdziemy teraz do trzeciej części animowania, poświęconej programowaniu animacji widoku.
Animacja widoku Skoro zapoznaliśmy się już z animacją poklatkową oraz animacją układu graficznego, jeste śmy gotowi zająć się animacją widoku - najbardziej skomplikowanym rodzajem animacji. Stosowana jest w niej technika animowania dowolnego widoku poprzez kontrolowanie ma cierzy transformacji, służącej do wyświetlania widoku. Podrozdział rozpoczniemy od krótkiego omówienia animacji widoku. Następnie zademon strujemy środowisko testowe, pozwalające na eksperymentowanie z animacją widoku, a po nim zaprezentujemy kj!ka przykładów animacji widoku. Kolejnym etapem będzie objaśnienie
246
Android 2. Tworzenie aplikacji
zastosowania obiektu Camera wobec animacji widoku (obiekt Camera nie ma nic wspólnego z rzeczywistym aparatem fotograficznym; jest to jedynie koncepcja graficzna). Na koniec zajmiemy się dogłębnie analizą działania macierzy transformacj i .
Animacja widoku Kiedy widok jest wyświetlany na powierzchni prezentacji Androida, przechodzi on przez ma cierz transformacji. W aplikacjach graficznych macierze transformacji są używane do prze kształcenia w jakiś sposób widoku. Proces ten polega na przetłumaczeniu wejściowego ze stawu współrzędnych pikseli i kombinacji kolorów na nowy zestaw współrzędnych pikseli i kombinacji kolorów. Po przep rowadzeniu transformacji ujrzymy obraz zmieniony pod względem rozmiaru, pozycji orientacji lub koloru. ,
Za pomocą aparatu matematycznego można przeprowadzić te przekształcenia, mnożąc w okre ślony sposób wejściowy zestaw współrzędnych przez wartości macierzy transformacji, dzięki czemu zostanie zwrócony nowy zestaw współrzędnych. Poprzez zmianę macierzy transfor macji wpływamy na wygląd widoku. Macierz, która nie zmienia widoku podczas mnożenia, nazywana jest macierzą jednostkową. Transformację przeważnie rozpoczynamy od macie rzy jednostkowej i kolejno wprowadzamy serie transformacji rozmiaru, pozycji i orientacji. Następnie bierzemy macierz końcową i za jej pomocą rysujemy widok. Android odsłania taką macierz transformacji widoku poprzez umożliwienie zarejestrowania obiektu animacji wobec tego widoku. Obiekt animacji będzie posiadał procedurę wywoła nia, dzięki której uzyska dostęp do tej macierzy i w określony sposób zmieni jej wartości, co pociągnie za sobą zmianę wyświetlania widoku. Zajmiemy się teraz tym procesem. Rozpocznijmy przykład od zaplanowania an imacji widoku. Na początek zapełnimy aktyw ność kilkoma elementami w widoku ListView, podobnie jak miało to miejsce w podrozdziale „Animacja układu graficznego". Następnie stworzymy przycisk u góry ekranu, powodujący uruchomienie animacji ListView po kliknięciu (rysunek 6.5). Widoczne są zarówno lista elementów, jak i przycisk, żadna animacja nie została jednak jeszcze uruchomiona. Do tego będzie służył utworzony przycisk .
�!lllJ &) 10:19 � „ ..
Rysunek 6.5. Aktywność animacji widoku
Rozdział 6 • Prezentacja animacji dwuwymiarowej
247
Chcemy, żeby po kliknięciu przyc isku Uruchom animację pojawił się mały widok pośrodku ekranu, a następnie stopniowo się powiększał, aż do wypełnienia zarezerwowanej dla niego przestrzeni. Zaprezentujemy kod, który nam to umożliwi. W listingu 6. 14 został pokazany kod pliku XML układu grafi cznego, nadający się do zastosowania w aktywności.
Listing 6.14. Plik XML układu g raficznego dla aktywności animacji widoku c?xml version=" l . 0 " encoding= " u t f - 8 " ?>
Tłustym drukiem zwracamy Czytelnikowi uwagę na lokalizację oraz nazwę pliku. Ten układ graficzny składa się z dwóch części: pienvsza z nich to p rzycisk btn_animate, służący do uruchomienia anim acji widoku; drugą j est widok ListView, w naszym przypadku nazwany list_view_id. Skoro już posiadamy układ graficzny dla aktywności, możemy utworzyć samą aktywność, żeby wyświetlić widok i sko nfigu rować przycisk Uruchom animację (lis ti ng 6.15).
Listing 6.15. Kod dla aktywności animacji widoku, przed rozpoczęciem animacji public class Vie�1AnimationActivity extends Activity @Override public void onCreate(Bundle savedinstanceState) { supe r . on C reate( savedinstanceStat e ) ; setContentView ( R . layout . list layout ) ; setuplistView ( ) ; thi s . setupButton ( ) ; private void setuplistView ( ) {
Strin g [ ] listitems = new String [ ] { " Element l " , " Element 2 " , "Element 3 " , " Element 4 " , " E lement 5 " , " Element 6" , };
{
248
Android 2. Tworzenie aplikacji
A r rayAdapter listlternAdapter = new A r rayAdapt e r ( this , android . R . layout . sirnple_ list_ itern_ l , listlterns ) ; ListView lv = ( ListView ) this . findViewByld ( R . id . list_view_id ) ; l v . setAdapter ( listlternAdapte r ) ; } private void setupButton ( ) { Button b = ( Button ) this . findViewByld ( R . id . btn anirnat e ) ; b . setOnClicklistene r ( new Button . OnClicklistene r ( ) { public void onClick(View v ) { //anirnatelistView ( ) ; } }) ; } } Kod przedstawiony dla aktywności animacji widoku z listingu 6.15 bardzo przypomina kod aktywności animacji układu graficznego z listingu 6.7. W podobny sposób wczytaliśmy wi dok i wstawiliśmy sześć elementów tekstowych do widoku ListView. Skonfigurowaliśmy przycisk w taki sposób, żeby wywoływał metodę anirnatelistview( ) po kliknięciu. Na razie jednak oznaczymy ten fragment komunikatem, dopóki nasz przykład nie zadziała. Aktyw·ność możemy wywołać tuż po jej zarejestrowaniu w pliku AndroidManifest.xml:
Po przeprowadzeniu procesu rejestracji możemy wywołać aktywność animacji widoku za pomocą dowolnego przycisku menu w aplikacji, korzystając z poniższego fragmentu kodu:
Intent intent = new I ntent ( this , ViewAnirnationActivity . cl a s s ) ; sta rtActivity( intent ) ; Po uruchomieniu programu pojawi się ekran pokazany na rysunku 6.5.
Dodawanie animacji W tym ćwiczeniu naszym celem jest dodanie animacji do widoku L i s tView, widocznego na rysunku 6.5. W tym celu potrzebujemy klasy wywodzącej się z pakietu a n d ro i d . v i e w . '+ a n imatio n . A n i m a t i o n. Następnie musimy przesłonić metodę applyTrans f a rmation, aby można było zmodyfikować macierz transformacji. Nazwijmy tę klasę ViewAnimation. Po jej utworzeniu możemy przeprowadzić w klasie ListView następującą czynność: ListView lv ( ListView ) t hi s . findViewByld ( R . id . list_ view_id ) ; l v . sta rtAnirnation ( new ViewAnirnatio n ( ) ) ; =
Pójdźmy dalej. Przyjrzyjmy się kodowi źródłowemu klasy ViewAnimation i zastanówmy się, jaki rodzaj animacji chcemy otrzymać (listing 6 . 1 6).
Rozdział 6 • Prezentacja animacji dwuwymiarowej
249
Listing 6.16. Kod źródłowy klasy ViewAnimation public class Vie�1Animation extends Animation { public Vie1�Animation2 ( ) { } @Over ride public void initialize (int width , int height, int parentWidth , int parentHeight) { supe r . initialize(width , height, parentWidth, pa rentHeight ) ; setDuration( 2500 ) ; setFillAf t e r ( t rue) ;
setinterpolator( new Linearinterpolat o r ( ) ) ; @Over ride protected void applyTransformation ( float i nterpolatedTime , Transformatio n t ) { final Matrix matrix = t . getMatrix ( ) ; matrix . setScale ( interpolatedTime, inte rpolatedTime ) ; }
Metoda zwrotna initialize informuje nas o wymiarach widoku . W niej są równ i eż ini cjowane wszelkie posiadane przez nas parametry anim acji. W naszym przykładzie skonfigu rowaliśmy czas trwania na 2500 milisekund (2,5 sekundy) . Sprawi my także, że wyn ik koń cowy animacji pozostanie niezm ienio ny po jej zakoń cze niu, a to za sprawą przypisania parametrowi FillAfter wa rtości t rue. W dodatku określiliś my, że nasz interpolator jest li niowy, co oznacza, że animacja zmienia się stopniowo od poc zątku do końca . W szystkie wymienione właściwości pochodzą z bazowej klasy and roid . view. animation . Animation. Część zasadnicza animacji przeprowadzana jest w metodzie applyTransformation. Szkielet Androida będzie ją bez przerwy wywoływał w celu symulowania animacji. Za każdym ra zem, gdy ta meto da j est wywoływana, zmienia się wartość parametru inte rpolatedTime. Zmienia się ona w zakresie od O do 1 w zależności od tego , w jakim momencie się znajdu jemy podczas 2,5-sekundowego cyklu animacji, ustawionego na etapie j ej inicjacji. Kiedy wartość parametru inte rpolatedTime wynosi 1, znajduj emy się na końcu animacji.
Naszym kolejnym zadaniem jest zmiana m aci erzy transformacj i , dostępnej poprzez obiekt transformacji t, umi eszczony w metodzie a p pl yT r a n s f o rm a t i on . N ajpi erw należy uzyskać dostęp do macierzy i zmienić jej wartości. Gdy zostanie narysowany nowy widok, zadziała również zmodyfikowana macierz. W dokumentacji interfejsów API dotyczącej klasy and roid . '+-Q rap h i c s . Mat rix można znaleźć opis wielu metod dostępnych w obiekcie M a t rix: http://developer.android.com/reference/android/graphics/Matrix.html W kodzie z listingu 6 . 1 6 zmianą macierzy transformacji zajmuje się poniższa linijka: matrix. setScale( interpolatedTime, inte rpolatedTime ) ;
Metoda setScale zawiera dwa parametry: współczynnik skali w osi x oraz współczyn nik skali w osi y. Ponieważ wartości parametru inte rpolatedTime mieszczą się w zakresie od O do 1, można go zastosować bezpośrednio w postaci współczynnika skali. Zatem na początku animacji współczynnik ten wyn osi O w obydwu kierunkach. W p ołowie przebiegu animacji
250
Android 2. Tworzenie aplikacji
oś x oraz oś y będą miały wartość O , 5. Po zakończeniu animacji widok będzie miał pełny rozmiar, ponieważ obydwa współczynniki skali będą miały wartości równe 1. W wyniku tego widok ListView jest na początku animacji niewielki i pov.riększa się do normalnego rozmiaru. W listingu 6. 17 został zaprezentowany kompletny kod źródłowy aktywności ViewAnimation '+Activity zawierającej animację. Listing 6.17. Kod źródłowy aktywności animacji widok, wraz z anima cj ą public class ViewAnimationActivity extends Activity { @Over ride public void onC reate ( Bundle savedinstanceState) { s u pe r . onCreate( savedlnstanceState ) ; setContentView ( R . layout . list_layou t ) ; setuplistView ( ) ; this . setupButton ( ) ; private void setuplistView ( ) { String [ ] listltems = new String [ ] { " E lement l " " Element 2 " , " E lement 3 " , " E lement 4 " , "Element 5 " , "Element 6 " , }; ArrayAdapter listltemAdapter new A r rayAdapt e r (this , android . R . layout . simple_list_item_l , listitems ) ; ListView lv = ( ListView) this . fin dViewByid ( R . id . list_view_id) ; l v . setAdapte r ( listitemAdapte r ) ; =
} private void setupButton ( ) { Button b = (Button ) this . findViewByld ( R . id . btn_animate ) ; b . setOnClicklistener( new Button . OnClicklistene r ( ) { public void onClick (View v ) {
animatelistView ( ) ;
}
}) ; } private void animatelistView ( ) {
ListView lv = ( listView) this . fi ndViewByld ( R . i d . list_view_id ) ; l v . startAnimation ( new ViewAnimation ( ) ) ;
}
Po uruchomieniu kodu z listingu 6 . 1 7 stanie się coś dziwnego. Widok ListVie1v, zamiast równomiernie powiększać się od środka ekranu, rozrasta się od lewego górnego rogu. Wy nika to z faktu, że operacje macierzy transformacji mają swój początek właśnie w lewym
Rozdział 6 • Prezentacja animacji dwuwymiarowej
251
górnym rogu ekranu. Żeby uzyskać zamierzony efekt, musimy najpierw przesunąć cały wi dok w taki sposób, żeby jego środek pokrywał się ze środkiem animacji (w lewym górnym rogu). Następnie 'vprowadzamy macierz i z powrotem przenosimy widok na właściwe miejsce. Poniżej przedstawiliśmy kod potrzebny do przeprowadzenia tej operacji: final Matrix matrix = t . getMatrix ( ) ; matrix.setScale(interpolatedTime , interpolatedTime ) ; matrix . p reTranslate ( - centerX, - centerY ) ; matrix. postTranslat e ( centerX, centerY) ;
Metody p reTranslate oraz postTranslate konfigurują macierz przed operacją skalowania oraz po tej operacji. Jest to proces równoważny utworzeniu zespołu trzech macierzy trans formacji. Kod matrix . setScale( interpolatedTime, inte rpolatedTime ) ; matrix. p reTranslate ( -centerX, -centerY) ; matrix . postTranslat e ( centerX, centerY) ;
jest równoważny instrukcjom przejdź do innego ś rodka skaluj p rzejdź do oryginalnego ś rodka
Poniżej umieściliśmy kod dla metody transformacji, dzięki której uzyskamy zamierzony efekt: p rotected void applyTransformation( float inte rpolatedTime, Transfo rmation t ) { final Matrix matrix = t . getMatrix ( ) ; matrix. setScale( interpolatedTime, interpolatedTime ) ; matrix . p reTranslate ( - centerX, -centerY ) ; matrix. postTranslate( centerX, centerY) ; }
Taki wzorzec metod p re i post jest stosowany bardzo często. Podobne wyniki można osią gnąć za pomocą innych metod klasy Matrix, ta technika jest jednak najpopularniejsza - a do tego jest zwięzła. Pozostałe techniki również zostaną omówione pod koniec rozdziału. Co ważniejsze, klasa Matrix umożliwia nie tylko skalowanie widoku, lecz również przeno szenie go za pomocą metod t ranslate oraz zmianę jego orientacji za pomocą metod rotate. Można sprawdzić te metody i przekonać się, jak wyglądają ich efekty. W rzeczywistości wszyst kie animacje omówione w podrozdziale „Animacja układu graficznego" są implementowane wewnętrznie za pomocą metod klasy Matrix.
Zastosowanie klasy Camera do wprowadzenia postrzegania przestrzeni w obrazie dwuwymiarowym Pakiet graficzny w Androidzie zawiera jeszcze jedną klasę związaną z animacją - a dokład niej z transformacją - klasę Camera. Można ją wykorzystać do vvprowadzenia postrzegania przestrzeni poprzez rzutowanie obrazu dwuwymiarowego, poruszającego się w przestrzeni trójwymiarowej po płaszczyźnie. Możemy na przykład wysłać nasz widok ListView o 1 0 pikseli w głąb ekranu p o osi z i obrócić j ą o 3 0 stopni wokół osi y. Poniżej podajemy przy kład modyfikowania macierzy za pomocą klasy Camera:
252
Android 2. Tworzenie aplikacji
Camera camera = new Camera ( ) ; protected void applyTransformatio n ( float interpolatedTime, Transformation t ) { final Matrix matrix = t . getMatrix ( ) ; camera. save ( ) ; camera . t ranslate ( O . Of , O . Of , ( 1300 - 1300 . 0f * interpolatedTime ) ) ; camera. rotateY (360 * inte rpolatedTime ) ; camera. getMat rix(mat rix ) ; mat rix . p reTranslate ( - centerX, -centerY ) ; matrix. postTranslat e ( centerX, centerY ) ; camera . restore ( ) ;
Animacja widoku ListView przebiega tu w następujący sposób: najpierw jest on umieszczony w odległości 1300 pikseli od ekranu po osi z, a następnie wraca do płaszczyzny, w której oś z przyjmuje wartość O. W międzyczasie zostaje on również obrócony od O do 360 stopni wokół osi y. Zobaczmy, w jaki sposób w kodzie jest zdefiniowane to zachowanie, opisane w poniższej metodzie: camera . t ranslat e ( O . O f , O . Of , ( 1300 - 1300 . 0f * inte rpolatedTime ) ) ;
Metoda ta każe przesunąć obiekt c a m e ra w taki sposób, że przy wartości O parametru i n t e rpolatedTime (początek animacji) wartość z będzie wynosiła 1300. Podczas trwania animacji wartość z będzie systematycznie malała aż do samego ko11ca, gdy wartość parame tru i n t e rpolatedTime wyniesie l, a tym samym wartość parametru z wyniesie O. Metoda came ra . rotateY ( 360 * i n t erpolatedTime) wykorzystuje możliwość obracania obiektu w trójwymiarze wokół wybranej osi przez obiekt carne ra. a początku animacji jej wartość wynosi O. Na końcu animacji przybierze wartość 360. Metoda ca rn e ra . getMat rix ( m a t r i x ) pobiera operacje dotychczas wykonane na obiekcie Carne ra i narzuca je przekazanej macierzy transformacji. W tym momencie klasa mat rix po siada wszystkie translacje potrzebne do uzyskania końcowego efektu, zapewnione przez klasę Camera. Teraz klasa Camera schodzi z widoku (niezamierzona gra słów), ponieważ w macie rzy zostały zaimplementowane wszystkie niezbędne operacje. Wykonujemy teraz operacje pre i p o s t w celu przesunięcia środka widoku i sprowadzenia go z powrotem. Na koniec przywracamy obiekt Carne ra do pierwotnego stanu, uprzednio zachowanego. Po wstawieniu kodu do naszego przykładu zobaczymy obiekt ListView zbliżający się ze środka widoku w stronę użytkownika, przy okazji wirujący, dokładnie tak jak zaplanowaliśmy. Część naszej analizy dotyczącej animacji widoku dotyczyła sposobu animowania dowolnego widoku poprzez rozszerzenie klasy Animation i zastosowanie jej wobec tegoż widoku. Poza modyfikowaniem matryc (bezpośrednio i za pomocą klasy Camera ) klasa Animation umożliwia też wykrywanie poszczególnych etapów animacji. Tym się teraz zajmiemy.
Analiza interfejsu Animationlistener Android wykorzystuje interfejs nasłuchujący A n imationlistener do monitorowania zda rzeń animacji (listing 6.18). Możemy nasłuchiwać tych zdarzeń poprzez zaimplementowanie interfejsu Animationlistene r i skonfigurowanie tej implementacji wobec klasy Animation.
Rozdział 6 • Prezentacja animacji dwuwymiarowej
253
Listing 6.18. Implementacja i nterfejs u A n i mat i on liste ner public class ViewAnimationlistener implements Animation . Animationlistener { private ViewAnimationlistene r ( ) {} public void onAnimationSta rt ( Animation animation)
{
Log . d ( " P rzykładowa animacj a " , "onAnimationSta rt " ) ; } public void onAnimationEnd ( Animation animation) { Log . d ( " P rzykładowa animac j a " , "onAnimationEnd " ) ; } public void onAnimationRepeat (Animation animatio n )
{
Log . d ( " P rzykładowa animac j a " , "onAnimationRepeat " ) ;
} }
Klasa ViewAnimationListener służy jedynie do tworzenia dzienników komunikatów. Możemy zaktualizować metodę animatelistView w naszym przykładzie animacji V>'idoku (listing 6.17), żeby dołączyć nasłuchiwacz animacji: private void animatelistView ( ) {
ListView lv = ( ListView ) this . findViewByid ( R . id . list_view_id ) ; ViewAnimation animation = new ViewAnimation ( ) ; animation . s etAnimationlistener( new ViewAnimationListener ( ) ) : lv . st a rtAnimation ( animation ) ;
}
Kilka uwag na temat macierzy transformacji Jak pokazaliśmy w tym rozdziale, macierze stanowią podstawę przekształcania widoków i przetwarzania animacji. Teraz omówimy w skrócie niektóre kluczowe metody klasy Mat rix. Poniżej zostały wymienione podstawowe operacje na macierzach: matrix. reset ( ) ; matrix . setScale ( ) ; matrix. setTranslate ( ) matrix . setRotat e ( ) ; matrix. setSkew ( ) ;
Pierwsza operacja przekształca macierz do postaci macierzy jednostkowej, która po zasto sowaniu nie wprowadza zmian w widoku. Operacja setScale odpowiedzialna jest za zmianę rozmiaru, setTranslate powoduje przesunięcie pozycji obiektu imitujące ruch, a setRotate służy do zmiany orientacji. Operacja setSkew pozwala na wykrzywienie widoku. Można powiązać ze sobą macierze lub je wspólnie powielać, aby utworzyć efekt złożony z wielu transformacji. Rozpatrzmy następujący przykład, w którym ml, m2 oraz m3 są macierzami jednostkowymi: ml . setScale ( ) ; m2 . setTranlate ( ) m3 . concat ( m l , m2 )
254
Android 2. Tworzenie aplikacji
Transformacja widoku przez macierz ml i następująca po niej transformacja widoku przez macierz m2 są tożsame transformacji tego samego widoku przez macierz m3. Zwróćmy uwa gę, że metody typu s e t zastępują poprzednie transformacje, natomiast m 3 . conca t ( m l , m2 ) nie jest tym samym, co m 3 . c o n c a t ( m 2 , ml ) . Pokazaliśmy już sposób postępowania podczas stosowania metod p reTranslate oraz post '+T r a n s late wobec zmiany macierzy transformacji. W rzeczywistości metody p r e i post nie są przeznaczone wyłącznie dla operacji t r a n s l a t e i tego typu odmiany są dostępne dla każdego rodzaju metod transformacji typu set. Ostatecznie metoda p reTranslate, taka jak m l . p re T r a n s la t e ( m 2 ) , jest równoważna operacji: m l . concat ( m 2 , m l l
W analogiczny sposób metoda m l . p o s tT r a n s l a t e ( m2 ) jest tożsama operacji: m l . concat ( m l , m 2 )
Po rozszerzeniu ekwiwalentem kodu matrix . setScale ( inte rpolatedTime, inte rpolatedTime ) ; mat rix . preTranslate( - centerX, - centerY) ; mat rix . postTranslate ( centerX, centerY ) ;
jest Matrix matrixPreTranslate = new Matrix ( ) ; mat rixPreTranslate . setTranslate ( - centerX, - centerY ) ; Matrix matrixPostTranslate = new Matrix ( ) ; mat rixPostTranslate . setTranslate(cetnerX, cent e rY ) ; mat rix . concat(mat rixPreTransla te , matrix ) ; matrix. postTranslate ( matrix , matrixpostTranslate ) ;
Podsumowanie W tym rozdziale zaprezentowaliśmy ciekawy sposób usprawnienia interfejsu użytkownika poprzez wstawienie do nich animacji. Omówiliśmy wszystkie podstawowe typy animacji obsługiwane w Androidzie: animację poklatkową, animację układu graficznego oraz anima cję widoku. Opisaliśmy także dodatkowe pojęcia dotyczące animacji, między innymi inter polatory i macierze transformacji. Skoro Czytelnik poznał już podstawy, proponujemy przejrzeć przykładowe interfejsy API, udostępnione w zestawie Android SDK, aby przeanalizować pliki XML definiujące różne typy animacji. Poruszymy jeszcze temat animacji w rozdziale 10., poświęconym rysowa niu i animowaniu za pomocą technologii OpenGL. Teraz jednak poświęcimy uwagę usługom systemu Android. W rozdziale 7. omówimy usługi zabezpieczeń oraz lokalizacyjne, natomiast rozdział 8. jest poświęcony usługom HTTP.
ROZDZIAŁ
7 Anal iza usług zabezpieczeń i usług opartych na położeniu geograficznym
W mmeJszym rozdziale zajmiemy się modelem zabezpieczeń aplikacji oraz
usługami opartymi na położeniu geograficznym w systemie Android. Chociaż obydwa tematy nie są ze sobą związane, przed wdrożeniem usług położenia geograficznego należy zrozumieć zabezpieczenia Androida. Pierwsza część rozdziału dotyczy zabezpieczeń, które stanowią podstawę plat formy Android. W systemie Android są one rozpostarte na wszystkich etapach cyklu życia aplikacji - począwszy od rozważań na temat polityki czasu pro jektowania aplikacji aż do sprawdzania krawędzi środowiska uruchomieniowego. Czytelnicy poznają architekturę zabezpieczeń i dowiedzą się, w jaki sposób tworzyć bezpieczne aplikacje. Druga część rozdziału została poświęcona usługom opartym na położeniu geo graficznym. Są to jedne z najciekawszych elementów zestawu Android SDK. Ta część środowiska SDK zawiera interfejsy API umożliwiające projektantom aplika cji wyświetlanie map oraz manipulowanie nimi, uzyskiwanie informacji o loka lizacji urządzenia w czasie rzeczywistym oraz posiada wiele innych fascynujących funkcji. Po przeczytaniu tego fragmentu książki Czytelnik ostatecznie stwier dzi, że Android jest zdumiewającym systemem. Rozpocznijmy od modelu zabezpieczeń w Androidzie.
Model zabezpieczeń w Androidzie Zabezpieczenia w Androidzie obejmują etapy wdrażania oraz uruchamiania aplikacji. Na etapie wdrażania aplikacje muszą zostać podpisane za pomocą cyfrowego certyfikatu, zanim zostaną zainstalowane na urządzeniu. Na etapie uruchamiania każda aplikacja działa wewnątrz oddzielnego procesu, który po siada unikatowy oraz permanentny identyfikator użytkownika (przydzielony podczas instalacji). Zostaje w ten sposób utworzona granica wokół procesu,
256
Android 2. Tworzenie aplikacji
uniemożliwiająca uzyskanie bezpośredniego dostępu przez jedną aplikację do danych innej aplikacji. Ponadto w Androidzie został zdefiniowany deklaracyjny model uprawnień, służą cy do ochrony wrażliwych funkcji (na przykład listy kontaktów). Omówimy te zagadnienia w kilku następnych paragrafach. Zanim jednak do nich przej dziemy, musimy nakreślić pewne pojęcia dotyczące zabezpieczeń, do których będziemy się w przyszłości odnosić.
Przegląd pojęć dotyczących zabezpieczeń Jak już zdążyliśmy wspomnieć, Android wymaga, żeby aplikacje były podpisane certrfik.1 tarni cyfrowymi. Jedna z zalet takiego wymogu polega na tym, że aplikacja nie może zo�ta, zaktualizowana do wersji nieopublikowanej przez oryginalnego autora. Na przykład _ki< I autorzy książki opublikują aplikację, Czytelnik nie będzie mógł jej zaktualizować swoją włN wersją (chyba że w jakiś sposób uzyska certyfikat autorów oraz związane z nim hasło). Zalt co to znaczy, że aplikacja musi zostać podpisana? I jak wygląda proces jej podpisy\,·ania' Dokonuje się tego za pomocą certyfikatu cyfrowego. Certyfikat c;frowy jest artefaktem / 1 wierającym informacje o producencie, takie jak nazwa firmy, adres, i tak dalej. Wśród nai ważniejszych atrybutów certyfikatu cyfrowego wyróżnić można podpis oraz klucz publiun i prywatny. Klucz publiczny i prywatny jest również nazywany parq kluczy. Zauważmy, i , chociaż użyvvamy tu kluczy do podpisywania plików .apk, są one również wykorzyst}>,"<11 1 < w innych celach (na przykład komunikacja szyfrowana). Można otrzymać certyfikat cyfro wy od zaufanego wydawcy certyfikatów (ang. certificate authority CA), a także istniej,· możliwość samodzielnego wygenerowania własnego certyfikatu za pomocą takiego narzędz ia. jak omówiona w dalszej części rozdziału aplikacja keytool. Cert)iikaty qfrowe są przechu wywane w magazynach kluczy. Magazyn kluczy zawiera listę cert)fikatów cyfrowych, z kto rych każdy posiada alias, służący jako odniesienie do tego cert)rfikatu. -
Podpisanie aplikacji w systemie Android wymaga trzech elementów: cert)iikatu qfrowego, pliku .apk oraz aplikacji potrafiącej wdrożyć podpis z cert)fikatu do pliku .apk. Jak się \\·krótct' okaże, taką aplikacją może być bezpłatne narzędzie jarsigner, dostępne \,. zesta\,·ie Ja\·a Development Kit. Jest to program wiersza poleceń, który potrafi podpisać plik .jar za pomoq certyfikatu cyfrowego. Przejdźmy teraz do tematu podpisywania pliku .apk za pomocą cert)rfikatu qfrowego.
Podpisywanie wdrażanych aplikacji Żeby zainstalować aplikację systemu Android w urządzeniu, musimy najpiern· podpisać plik pakietu Android (.apk) cyfrową sygnaturą certyfikatu. Jednak cert)fi.kat można podpisać sa modzielnie - nie ma konieczności zakupu certyfikatu od wydawcy pokroju firmy VeriSign. Podpisywanie wdrażanej aplikacji obejmuje trzy etapy. Pierwszym krokiem jest \\')'genero wanie certyfikatu za pomocą aplikacji keytool (lub podobnego narzędzia). W drugim etapie poprzez aplikację jarsigner (lub analogiczny program) podpisujemy plik .apk sygnaturą wygenerowanego certyfikatu. W ostatnim etapie zostają przypisane fragmenty aplikacji do granic pamięci, dzięki czemu podczas działania programu pamięć jest wykorzystywana efektywniej. Warto zwrócić uwagę, że podczas etapu projektowania wtyczka ADT automa tycznie zajmuje się podpisywaniem pliku .apk oraz przydzielaniem pamięci, zanim aplikacja zostanie wdrożona na emulatorze. Co więcej, domyślny certyfikat stosowany podczas etapu projektowania nie może być zastosowany do wdrożenia aplikacji na rzeczywiste urządzenie.
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
257
Wygenerowanie samoistnie podpisanego certyfikatu za pomocą narzędzia keytool Aplikacja keytool zarządza bazą danych kluczy prywatnych oraz powiązanych z nimi certy fikatów X.509 (standard certyfikatów cyfrowych). Jest dostępna w zestawie JDK, dokładniej w jego katalogu bin. Po wykonaniu omówionych w rozdziale 2. czynności modyfikowania zmiennej środowiskowej PATH katalog bin zestawu JDK powinien być częścią tej zmiennej. W niniejszym paragrafie pokażemy, w jaki sposób można wygenerować magazyn kluczy zawierający jeden wpis służący do podpisania pliku .apk. Żeby utworzyć taki wpis, należy wykonać następujące czynności: 1 . Utwórz folder, w którym będzie przechov.rywany magazyn kluczy, na przykład c: \android\release\. 2. Otwórz okno narzędzi i uruchom narzędzie keytool wraz z parametrami pokazanymi w listingu 7.1 (w rozdziale 2. jest wyjaśnione, co mamy na myśli, używając terminu „okno narzędzi"). Listing 7 .1. Generowanie wpisu magazynu kluczy za pomocą narzędzia keytool keytool -genkey - v - keystore " PEŁNA ŚCIEŻKA PLIKU release . keystore UTWORZONEGO PUNKCIE l" -alias and roidbook - storepass paxxword - keypass paxxword - keyalg '+RSA -validity 14000
-..w
Wszystkie argumenty wykorzystane w narzędziu keytool zostały opisane w tabeli 7.1. Tabela 7
.
1 . Argumenty przekazane aplikacji
keytool
Argument
Opis
gen key
Każe aplikacji keytool wygenerować parę kluczy publiczny i prywatny.
V
Każe aplikacji keytoo/ wyświetlić dane wyjściowe w formie opisowej podczas generowania klucza.
keystore
$cieżka do bazy danych magazynu kluczy (w naszym wypadku do pliku).
alias
N iepowtarzalna nazwa wpisu magazynu kluczy. Alias będzie zastosowany jako odniesienie do wpisu.
storepass
Hasło magazynu kluczy.
key p a s s
Hasło dostępowe do klucza prywatnego.
key alg
Algorytm.
validity
Okres ważności.
Aplikacja keytool poprosi o podanie haseł wymienionych w tabeli 7. 1 , jeżeli nie zostaną zdefiniowane w wierszu poleceń. Jeżeli komputer jest współużytkowany przez wiele osób, bezpieczniej będzie nie określać parametrów -storepas s i -keypas s w wierszu polecenia, lecz zdefiniować je, gdy aplikacja keytools o to poprosi. Polecenie przedstawione w listingu 7.1 \\')'generuje bazodanowy plik magazynu kluczy w utworzonym przez nas folderze. Baza danych będzie plikiem noszącym nazwę release.keystore. Okres ważności wpisu (parametr va lid i ty) v,rynosi 14 OOO dni (w przybliżeniu 38 lat) - co stanowi długi okres czasu. Ważne
258
Android 2. Tworzenie aplikacji
jest zrozumienie powodu ustanowienia tak długiego okresu ważności. Dokumentacja An droida zaleca, aby definiować okres ważności wystarczająco długi, żeby przewyższał całko wity okres istnienia aplikacji, wliczając w to jej wielokrotne aktualizacje. Zalecanym okre sem ważności jest 25 lat. Co więcej, jeżeli aplikacja ma zostać umieszczona w serwisie Android Market (http://www.android.com/market/), certyfikat musi być ważny przynajm niej do dnia 22 października 2033 roku. Serwis ten sprawdza każdą umieszczoną aplikację pod kątem powyższego okresu ważności. Wracając do aplikacji keytool, argument alias jest niepowtarzalną nazwą, przydzielaną każdemu wpisowi magazynu kluczy; można jej później używać jako odniesienia do tego wpisu. Po uruchomieniu pokazanego w listingu 7 . 1 polecenia keytool aplikacja zada kilka pytań (rysunek 7.1), a następnie wygeneruje bazę danych oraz jej wpis.
Rysunek 7
.
1 . Dodatkowe pytania
zadawane przez aplikację keytool
Właśnie utworzyliśmy certyfikat cyfrowy, który posłuży nam do podpisania pliku .apk. Żeby tego dokonać, użyjemy aplikacji jarsigner. Traktuje o tym następny paragraf.
Zastosowanie narzędzia jarsigner do podpisania pliku .apk Narzędzie keytool wygenerowało w poprzednim akapicie certyfikat cyfrowy, który stanowi jeden z parametrów aplikacji jarsigner. Jego drugim parametrem jest rzeczywisty pakiet Androida, który ma zostać podpisany. Żeby utworzyć pakiet Androida, musimy użyć na rzędzia Export Unsigned Application Package dostępnego we wtyczce ADT środowiska Ec lipse. Dost�p do niego uzyskuje się poprzez kliknięcie prawym przyciskiem myszy węzła projektu w aplikacji Eclipse, kliknięcie opcji Android Tools, a następnie ·wybranie Export Unsigned Application Package. W ten sposób zostanie wygenerowany plik .apk niepodpisa ny przez certyfikat testowy. Żeby sprawdzić działanie tego mechanizmu, zastosujmy narzę dzie Export Unsigned Application Package wobec jednego z projektów i zapiszmy gdzieś ·wy generowany plik .apk. W tym przykładzie użyjemy utworzonego uprzednio folderu magazynu kluczy i wygenerujemy plik apk c:\android\release\myapp.apk. Po utworzeniu pliku .apk oraz wpisu w magazynie kluczy uruchamiamy aplikację jarsigner w celu podpisania tego pliku (listing 7.2). Wpisujemy pełną ścieżkę do pliku magazynu kluczy oraz pliku .apk.
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
259
Listing 7.2. Zastosowanie aplikacji jarsig ne r do podpisania pliku .apk j a rsigner - keystore "ŚCIEŻKA DO PLIKU release . keystore" -storepass paxx1�ord 4keypass paxxword "ŚCIEŻKA DO PLIKU APK" a n d roidbook
Żeby p odp isać plik .apk, p odajemy lokalizację magazynu kluczy, hasło do tego magazynu, hasło do klucza prywatnego, ścieżkę do pliku .apk oraz alias wp i su magazynu kluc zy. Apli kacja jarsigner następ ni e podpisze plik .apk za pomocą sygnatury pochodzącej z wpisu ma gazynu klu czy Żeby uruchomić narzędzie jarsigner, należy otworzyć albo okno narzędzi (rozdział 2.), alb o wiersz poleceń lub okno Terminal. Następnie trzeba przejść do katalogu bin zestawu JDK l ub upe\rnić się, że katalog ten znajduje się w zmiennej systemowej PATH. .
Jak już wcześniej stwierdziliśmy, Android v.rymaga od apli ka cji cyfrowej sygnatury, żeby uniemożliwić złośliwemu programiście aktualizowanie aplikacji stworzonej przez Czytelni ka do własnej wersji. Wynika z tego wniosek, że aktualizacja aplikacji musi być po dp isana za pomocą tej samej sygnatury, jak j ej wersja pierwotna. Je żel i aktuali zacja zostanie podpi sana za pomocą innej sygnatury, zostanie potraktowana jak odrębna aplikacja.
Rozdysponowanie aplikacji za pomocą narzędzia zipalign Chcemy,
żeby ap likacja jak n ajwydaj n iej korzystała z pamięci podczas działania na urzą dzeniu. Jeżeli korzysta ona z ni eskom p resowan ych danych (na przykład niektórych rodza j ów obrazów lub plików danych) w trakcie działania, Android może je odwzorować bezpo średnio w pamięci za pomocą wywołania mmap ( ) . Jednak żeby to było możliwe, dane muszą zostać przydzielone do czterobajtowej granicy pamięci. Jednostki przetwarzające w urzą dzeniach obsługujących system Android stanowią procesory 32-bitowe, a 32 bity są równo ważn e -1 bajtom. \\'}'\rnlanie mmap ( ) sprawia, że dane umieszczone w pliku .apk wyglądają jak pamięć, jednak jeżeli nie są przydzielone do czterobajtowej krawędzi, nie da się prze p ro wadzić procesu odwzorowania i w czasie działania aplikacji będzie wykonywane dodat kowe kopiowanie danych. Dostępne w katalogu pakietu Android SDK n arzędzie zipalign analizuj e daną aplikację i niezauważalnie przenosi wszelkie nieskompresowane dane do czterobajtowej krawędzi pamięci. Rozmiar aplikacji może się nieznacznie zwiększyć, nie będą to jednak duże zmiany. Żeby przeprowadzić ten proces na naszym pliku apk należy użyć poniższego polecenia \\. oknie n arzędz i (rÓ\rnież rysunek 7.2): .
zipalign -v 4 infile . apk outfile . ap k
Rysunek 7.2. Zastosowanie aplikacji zipalign
,
260
Android 2. Tworzenie aplikacji
Zauważmy, że aplikacja zipalign weryfikuje rozmieszczenie plików po utworzeniu pliku przydziału. W przypadku potrzeby nadpisania istniejącego pliku wyjściowego outfile.apk można użyć opcji f Ponadto, żeby zweryfikować poprawność rozmieszczenia pliku, można użyć aplikacji zipalign w poniższy sposób: -
zipalign -c
-
.
v 4 filename . apk
Istotne jest, żeby przeprowadzić rozmieszczanie po procesie podpisywania, w przeciwnym razie po podpisaniu aplikacji rozmieszczenie plików może zostać anulowane. Nie oznacza to wcale, że aplikacja będzie się zawieszać, po prostu może zajmować więcej pamięci, niż potrzeba. Po podpisaniu i rozmieszczeniu zawartości pliku .apk można ręcznie zainstalować aplikację na emulatorze za pomocą narzędzia adb. W celach ćwiczeniowych można uruchomić emu lator. Jednym ze sposobów uruchomienia emulatora, o którym jeszcze nie wspomnieliśmy, jest kliknięcie menu Window w środowisku Eclipse i wybranie opcji Android SDK and AVD Manager. Zostanie wyświetlone okno z listą posiadanych urządzeń AVD. Wybierzmy jedno z nich i kliknijmy przycisk Start. . Emulator zostanie uruchomiony bez funkcji kopiowania wszelkich projektów ze środowiska Eclipse. Otwórzmy okno narzędzi i włączmy narzędzie a d b wraz z poleceniem install: . .
adb install "�CIEŻKA DO PLIKU APK"
Proces może zakończyć się niepowodzeniem z kilku powodów, jednak najczęściej wynika ono z faktu, że na emulatorze została wcześniej zainstalowana wersja testowa aplikacji, co powoduje konflikt certyfikatów i wyświetlenie błędu, lub gotowa wersja aplikacji została wcześniej zainstalowana i zostanie wyświetlony komunikat, że dana aplikacja jest już zain stalowana na urządzeniu. W pierwszym przypadku można odinstalować aplikację testową za pomocą polecenia: adb uninstall packagename
Zwróćmy uwagę, że argumentem jest tutaj nazwa pakietu, a nie nazwa pliku .apk. Nazwa pakietu jest zdefiniowana w pliku AndroidManifest.xml zainstalowanej aplikacji. W przy padku błędu drugiego rodzaju można wpisać poniższą komendę, w której parametr - r oznacza ponowne zainstalowanie aplikacji z zachowaniem danych n a urządzeniu ( w emu latorze): adb install
-r
"�CIEŻKA DO PLIKU APK"
Prześledźmy teraz, w jaki sposób podpisywanie wpływa na proces aktualizowania aplik�cji.
Instalowanie aktualizacji aplikacji a podpisywanie Wspomnieliśmy wcześniej, że certyfikat posiada okres ważności oraz że firma Google zaleca ustawienie bardzo długiego terminu wygaśnięcia, na wypadek dużej liczby aktualizacji. Co się zatem dzieje z aplikacją po wygaśnięciu certyfikatu? Czy będzie ona nadal działać? Na szczęście tak - Android sprawdza certyfikat jedynie w czasie instalacji programu. Po zainstalowaniu aplikacji będzie ona działała nawet po wygaśnięciu ważności certyfikatu. A co z aktualizacjami? Niestety, po wygaśnięciu ważności certyfikatu nie będzie możliwości aktualizowania aplikacji. Innymi słowy, zgodnie z radą firmy Google należy ustanowić wy starczająco długi termin ważności aplikacji, żeby objął jej cały cykl życia. Jeżeli ważność certyfikatu wygaśnie, Android nie będzie instalował aktualizacji aplikacji. Jedynym wyjściem
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
261
pozostanie wtedy utvvorzenie kolejnej aplikacji - posiadającej inną nazwę pakietu - i pod pisanie jej za pomocą nowego certyfikatu. Jak więc widać, podstawową kwestią jest dobranie terminu ważności certyfikatu podczas jego utworzenia. Skoro już posiadamy wiedzę na temat zabezpieczeń związanych z wdrażaniem oraz instala cją aplikacji, przejdźmy do zabezpieczeń środowiska wykonawczego w Androidzie.
Przeprowadzanie testów zabezpieczeń środowiska wykonawczego Zabezpieczenia środowiska wykonawczego w Androidzie zostały zaimplementowane na poziomie procesowym oraz na poziomie operacyjnym. Ka poziomie procesowym Android zapobiega uzyskaniu bezpośredniego dostępu do danych jednej aplikacji przez inną aplika cję. W Androidzie jest to osiągnięte poprzez działanie każdej aplikacji w innym procesie oraz poprzez przydzielenie unikatowego i permanentnego identyfikatora użytkownika dla każdej z nich. Na poziomie operacyjnym zostaje zdefiniowana lista chronionych funkcji i zaso bów. Żeby aplikacja uzyskała dostęp do tych informacji, należy dodać przynajmniej jedno żądanie uprawnień w pliku AndroidManifest.xml. Można także zdefiniować niestandardowe uprawnienie dla aplikacji.
W następnych paragrafach zajmiemy się tematyką zabezpieczeń granicy procesu oraz spo sobami deklarowania i stosowania predefiniowanych uprawnień. Omówimy także proces tworzenia niestandardowych uprawnień i narzucania ich \\·obec aplikacji. Zacznijmy od analizy zabezpieczeń na krawędziach procesu.
Zabezpieczenia na krawędziach procesu W przeciwieństwie do środowisk komputerów biurowych, w których większość aplikacji korzysta z tego samego identyfikatora użytkownika, niemal każda aplikacja w Androidzie posiada swój własny identyfikator. W ten sposób zostaje utworzona bariera izolacyjna wo kół każdego procesu. Żadna aplikacja nie może uzyskać bezpośredniego dostępu do danych innej aplikacji. Chociaż każdy proces jest otoczony barierą, współdzielenie danych pomiędzy aplikacjami jesr oczywiście możliwe, musi być jednak jawnie zadeklarowane. Innymi słowy, żeby otrzy mać dane z innej aplikacji, należy podjąć interakcję z jej składnikami. Można na przykład wysłać zapytanie do dostawcy treści innej aplikacji, wywołać aktywność w innej aplikacji lub - jak się przekonamy w rozdziale 8. - nawiązać łączność z usługą innej aplikacji. Każdy z wymienionych sposobów zawiera metody umożliwiające wymianę informacji pomiędzy aplikacjami, dokonują one tego jednak w jawny sposób, ponieważ nie uzyskujemy dostępu do właściwej bazy danych, plików i tak dalej.
W Androidzie zabezpieczenia na poziomie granicy procesu są proste i zrozumiałe. Robi się ciekawie, gdy zwrócimy uwagę na ochronę zasobów (na przykład danych kontaktowych), funkcji (na przykład aparatu fotograficznego) oraz naszych własnych składników. W celu zapewnienia ochrony Android definiuje schemat uprawnień. Zajmiemy się nim teraz.
262
Android
2. Tworzenie aplikacji
Deklarowanie oraz stosowanie uprawnień Android zawiera schemat uprawnień służący do ochrony zasobów i funkcji urządzenia. Na przykład domyślnie aplikacje nie mogą uzyskać dostępu do listy kontaktów, umożliwiać wykonywania połączeń telefonicznych i tak dalej. Aby chronić użytkownika przed złośliwym oprogramowaniem, Android wymusza na aplikacjach żądanie zasobów, gdy próbują uzy skać dostęp do chronionego zasobu lub funkcji. Jak niebawem wyjaśnimy, żądania upraw nień umieszczane są w pliku manifeście. W trakcie instalacji instalator plików APK przydziela lub odrzuca żądane uprawnienia w zależności od sygnatury pliku .apk oraz (lub) decyzji użytkownika. Jeżeli uprawnienie nie zostanie zapewnione, każda próba wykonania działania lub uzyskania dostępu do danej funkcji zakończy się niepowodzeniem. W tabeli 7.2 zaprezentowano niektóre powszechnie stosowane funkcje oraz wymagane do nich uprawnienia. Większość wymienionych funkcji nie została jeszcze omówiona, lecz zajmiemy się nimi w dalszej części książki (w dalszej części tego rozdziału oraz w następnych rozdziałach). Tabela 7.2. Funkcje zasoby oraz wymagane do nich uprawnienia ,
Funkcja/zasób
Wymagane uprawnienie
Opis
Aparat fotograficzny
and roid . permission . CAMERA
Udziela dostępu do aparatu fotograficznego urządzenia.
Internet
a n d roid . pe rmission . INTERNET
Umożliwia połączenie sieciowe.
Dane kontaktów użytkownika
android . permission . READ_CONTACTS
Pozwala na odczytywanie lub zapisywanie danych kontaktów użytkownika.
a n d roid . permis sion . WRITE_ CONTACTS
Dane kalendarza użytkownika
and roid . permi s sion . READ CALENDAR
Dyktafon
android . pe rmission . RECORD AUDIO
Umożliwia nagrywanie dźwięku.
Informacje położenia geograficznego GPS
and roid . permission . ACCESS FINE LOCATION
Pozwala na uzyskanie dokładnych danych dotyczących położenia geograficznego. Obejmuje informacje lokalizacyjne GPS.
Informacje położenia geografi cznego sieci Wi-Fi
android . pe rmission . ACCESS COARSE LOCATION
Pozwala na uzyskanie zgrubnych danych dotyczących położenia geograficznego. Obejmuje informacje lokalizacyjne sieci Wi-Fi.
Informacje o stanie baterii
android . pe rmission . BATTERY_STATS
Umożliwia uzyskanie informacje o stanie baterii.
Bluetooth
android . permissio n . BLUETOOTH
Pozwala na połączenie ze sparowanym urządzeniem Bluetooth.
and roid . permission . WRITE_CALENDAR
Pozwala na odczytywanie lub zapisywanie danych kalendarza użytkownika.
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
263
Kompletną listę uprawnień można znaleźć pod adresem:
http:l!developer.android.corn/reference/android!Manifest.permission.html Programiści mogą żądać uprawnień poprzez dodawanie wpisów w pliku AndroidManifest.xml. Na przykład w listingu 7.3 zostało utworzone żądanie uzyskania dostępu do aparatu foto graficznego, odczytania listy kontaktów oraz odczytania danych kalendarza. Listing 7.3. Uprawnienia w pliku AndroidManifest.xml Warto wiedzieć, że możemy wprowadzić żądanie uprawnienia ręcznie do pliku Android Manifest.xml albo mo7.emy skorzystać z edytora manifestu. Jest on gotowy do uruchomienia tuż po otwarciu (dwukrotnym kliknięciu) pliku manifestu. W edytorze tym jest dostępna rozwijana lista wszystki ch uprawnień, dzięki czemu można uniknąć popełnienia błędu. Jak widać na rysunku 7.3, można uzyskać dostęp do listy uprawnień poprzez ·wybranie zakładki Permissions w edytorze manifestu.
'Permb'\IOO<.
fl) �'"'lÓl'o�j,ro�rr„!-;$f"A• ;.. ·-:css_Fl�.E_l: :t.n:n ;L;s� Pe· W aridt<:>�� �
.Q. a.�ieroo.per�tł!Słln F:>E�.::·:·•..-1;.�rs ·�'!S Pt::�st:.·(
l\ł.tnl'lut,·� łut ,md1tnd.per:11i<>._10n.ArU-'>'t. t ro,,; Peth\l'S'-:Onj.
111( Allil"' (lh.r<;
The Yf"':id"�'r·r�.91,,.:-1 t-tQ rl'QUes.ts d "permis!i!Oll" r,h.,t th� Contdf'llflQ p.�:�-'JC l':)l.,;:;J toe ąr�ed111 (1tdę1 for 11; to "P"'fbtt" conectJ,-.
Rysunek 7.3. Narzędzie edytora manifestu w środowisku Eclipse 'Niemy już, w jaki sposób jest zdefiniowany w Androidzie zestaw uprawnień służących do ochrony funkcji i zasobów. W podobny sposób możemy definiować i wymuszać niestan dardowe uprawnienia dla danej aplikacji. Zobaczmy, jak to działa.
Android 2. Tworzenie aplikacji
264
Stosowanie uprawnień niestandardowych Android pozwala na zdefiniowanie własnych uprawnień wobec danej aplikacji. Na przy kład, jeżeli chcemy uniemożliwić określonym użytkownikom uruchamianie jednej z aktywno ści w naszej aplikacji, możemy tego dokonać za pomocą uprawnienia niestandardowego. Aby korzystać z własnych uprawnień, należy je najpierw zadeklarować w pliku AndroidManifest.xml. Po zdefiniowaniu uprawnienia można się do niego odnosić jak do pozostałych składO\vych definicji. Zademonstrujemy, jak to działa. Stwórzmy aplikację, której aktywności nie będzie mógł uruchomić dowolny użytkownik. Żeby tego dokonać, będzie potrzebował specjalnego uprawnienia. Po utworzeniu aplikacji zawierającej taką uprzywilejowaną aktywność będziemy mogli napisać klienta zdolnego do wywołania tej aktywności. Najpierw należy utworzyć projekt zawierający niestandardowe uprawnienie i aktywność. Dokonujemy tego poprzez uruchomienie środowiska Eclipse i kliknięcie opcji New/New Project/Android Project. Zostanie otwarte okno dialogowe New Android Project. Jako na zwę projektu wpisujemy CustomPermission, wybieramy opcję Create new project in works pace i zaznaczamy Use default Location. Nazwa aplikacji może brzmieć Niestandardowe uprawnienie, nazwa pakietu com . cu s t . perm, a nazwa aktywności CustPe rmMainActivity. Powinniśmy również wpisać dowolną wersję docelowej platformy w polu Build Target. Żeby utworzyć projekt, należy kliknąć przycisk Finish. Wygenerowany projekt będzie zawierał dopiero co utworzoną aktywność, pełniącą rolę domyślnej (głównej) akty\rności. Stwórzmy akty\vność, dla której wymagane jest spe także tak zwaną aktywność uprzywilejowanq cjalne uprawnienie. W aplikacji Eclipse należy przejść do pakietu com . c u s t . pe rm, utworzyć klasę PrivActi v i ty, dla której superklasą jest and ro id . a p p . Acti v i t y a następnie przepi sać kod umieszczony w listingu 7.4. -
,
Listing 7.4. Klasa PrivActivity package com . cust . perm; import import import import import
android . app . Activity; android . o s . Bundle; and roid . view. ViewGrou p . LayoutParams; android.widget. Linearlayout; android . widget . TextView;
public class PrivActivity extends Activity {
@Over ride public void onCreate( Bundle savedinstanceState) { s u p e r . onCreate( savedinstanceState ) ; Linearlayout view = new Linearlayout ( this ) ; view.setlayoutParams ( new LayoutPa rams ( LayoutParams . FILL_PARENT, LayoutParams. WRAP_CONTENT) ) ; view . setOrientation( LinearLayout . HORIZONTAL ) ; TextView namelbl = ne1-1 TextVie1-1 ( t his ) ; namelbl . setText ( " Pozd rowienia z aktywności PrivActivity " ) ;
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
265
view . addVie1·1( namelbl ) ; setContentView ( view) ; }
Jak widać, aktywność PrivActivi ty nie czyni cudów. Chcemy jedynie pokazać, w jaki spo sób można chronić ją za pomocą uprawnienia i v-rywołać ją za pomocą klienta. Jeżeli klient zostanie właściwie zaimplementowany, na ekranie pojawi się wiadomość Pozd ro�1ienia z aktywności PrivActivity. Po wygenerowaniu aktywności, która ma być chroniona, mo żemy stworzyć uprawnienie do niej . Żeby stworzyć niestandardowe uprawnienie, n al eży je zdefiniować w pliku AndroidMani fest.xml. Najłatwiej to zrobić za pomocą edytora manifestu Wystarczy dwukrotnie kliknąć plik AndroidManifest.xml i wybrać zakładkę Permissions. W oknie Permissions klikamy przycisk Add, wybieramy opcję Permission i wciskamy p rzycisk OK. Zos tanie wygenerowa .
ne puste uprawnienie. Należy wypełnić je atrybutam i, tak jak zil ustro wano na rysun ku 7.4. Wypełniamy pola po prawej po stronie, a jeżeli etyki eta po prawej stroni e wciąż bądzie no s iła nazwę Permission, klikamy ją i nazwa uprawnienia powinna zostać zaktualizowana . .:r,....__
,i..,.,, • • .
A.n1łr(lirl Maniłest Pcr1u\.;.i.inn�
� ·�c, � -,.....·-� U1 .t.lł·t: ) 1t("''°• .,..,„"""" �'o\ U� et ...•„lil\O CO„l
....... B 1t1m c „.
Rysunek 7.4. Deklarowanie niestandardowego uprawnienia za pomocą edytora manifestu
Jak widać na rysunku 7.4, up rawni enie posiada nazwę, etykietę, ikonę, grupę uprawnienia, opis i poziom ochrony. W tabeli 7.3 zostały zdefiniowane wymienione pa ra metry .
Właśnie utworzyliśmy niestandardowe uprawnienie. Teraz musimy poin fo r m ować system, że ak�-vność P rivActivity powinna być uruchamiana jedynie przez aplikacje posiadające uprawnienie syh . p e rmiss ion . STARTMYACTIVITY. Można dołączyć wymagane uprawnienie do ak�vności poprzez dodanie atrybutu and ro i d : pe rmission do definicji aktyvmości w pliku AndroidManifest.xml. Żeby uruchomić ak�vność, będzie konieczne również dodanie do niej filtru intencji. Zaktualizujmy plik AndroidManifest.xml kodem zawartym w listingu 7.5.
266
Android 2. Tworzenie aplikacji
Tabela 7.3. Atrybuty uprawnienia Atrybut
Wymagany? Opis
android: name
Tak
Nazwa uprawnienia. Zazwyczaj należy przestrzegać konwencji nazewnictwa Androida (* . pe rmi s s i o n . *) .
a n d roid : p rotectionlevel
Tak
Definiuje „potencjał zagrożenia" związany z uprawnieniem. Należy wybrać jedną spośród następujących wartości: normal dangerous signature signatu reOrSystem W zależności od poziomu ochrony system podejmuje inne działanie podczas procesu określania, czy należy przydzielić dostęp użytkownikowi, czy nie. Wartość no rma! oznacza, że uprawnienie stanowi niewielkie zagrożenie i nie stanowi niebezpieczeństwa dla systemu, użytkownika lub innych aplikacji. Poziom dangerous oznacza, że istnieje duże ryzyko oraz że system najprawdopodobniej będzie potrzebował zgody użytkownika przed akceptacją uprawnienia. Wartość signatu re przydziela uprawnienie jedynie aplikacjom posiadającym taką samą sygnaturę, jak aplikacja deklarująca to uprawnienie. Wartość signatu reO rSystem pozwala przydzielać uprawnienie aplikacjom posiadającym taką samą sygnaturę, jak aplikacja deklarująca to uprawnienie lub klasom pakietu Android. Ten poziom ochrony jest stosowany w szczególnych przypadkach, w których wielu producentów musi współdzielić funkcje poprzez obraz systemu.
android : permissionGroup
Nie
Uprawnienia można grupować, jednak w przypadku uprawnień niestandardowych powinno się pomijać tę właściwość. Jeżeli naprawdę trzeba stworzyć grupę uprawnień, lepiej skorzystać z tego atrybutu:
android : label
Nie
Chociaż ta własność nie jest wymagana, służy do stworzenia krótkiego opisu uprawnienia.
an d ro id : d e s c ripti o n
Nie
Chociaż i ta właściwość nie jest wymagana, można tu umieścić dokładniejsze informacje na temat przeznaczenia uprawnienia.
and roid : icon
Nie
Uprawnienia można powiązać z ikonami umieszczonymi w zasobach (na przykład @d rawable/mo j aikona).
and roid . permission- grou p . SYSTEM TOOLS
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
267
Listing 7.5. Plik AndroidManifest.xml projektu zawierającego niestandardowe uprawnienie capplication android : icon="@drawable/icon" and roid : label="@string/app name"> cactivity a n d roid : name=" . Cu stPermMainActivity" android : label="@string/app_name"> ccategory android : name="android . intent . catego ry . LAUNCHER" /> cactivity android: name= " P rivActivity" android : permission=" syh . permission . STARTMYACTIVITY"> cuses - sdk android : minSdkVe rsion="2" /> Jak widać z listingu 7.5, musimy dodać stałą w postaci ciągu znaków sta rtMyActivityDesc do zasobów typu string. Żeby kompilacja kodu przebiegła bezbłędnie, dodajmy następujący ciąg znaków do pliku res/values!strings.xml: Umożliwia uruchomienie mojej aktywności Włączmy teraz projekt na emulatorze. Chociaż główna aktywność nie wykonuje żadnej czynności, musimy zainstalować aplikację na emulatorze, zanim będzie można napisać klienta dla uprzywilejowanej aktywności. Napiszmy więc takiego klienta. W ś rodo wisku Eclipse należy kliknąć opcj ę New/Project/ Android Project. Jako nazwę projektu wpiszmy ClientOfCustomPermission, wybierzmy opcję Create new project in workspace i zaznaczmy opcję Use default location. Nazwa aplikacji może brzmieć Klient niestanda rdov1ego u p rawnienia, nazwa pakietu com . clien t . cust . pe rm, a nazwa aktywności ClientCu s t Pe rmMainActivity. W polu Build Target wpisujemy nazwę dowolnej wersji platformy. Aby utworzyć projekt, należy kliknąć przycisk OK. Teraz chcemy napisać aktywność zawierającą przycisk, którego kliknięcie spowoduje wy wołanie uprzywilejowanej aktywności. Przepiszmy układ graficzny z listingu 7.6 do pliku main.xml, znajdującego się w naszym nowym projekcie.
Android 2. Tworzenie aplikacji
268
Listing 7.6. Plik main.xml w projekcie klienta
Jak widać, plik XML układu graficznego definiuje pojedynczy przycisk nazwany Uruchom PrivActivity. Napiszmy teraz aktywność, która będzie generowała zdarzenie wywołane klik nięciem i uruchamiała uprzywilejowaną aktywność. Kod z listingu 7.7 należy umieścić w klasie ClientC u s t Pe rmMainActivity. Listing 7.7. Zmodyfikowana klasa ClientCustPermMainActivity package com . client . cust . perm; ClientCustPermMainActivity.java
li To jest plik
import import import import import import
android . app . Activity; android . content . Intent ; android . o s . Bundle; android . view.View; android . vie�1. View . OnClicklistener; android . widget . Button ;
public class ClientCustPe rmMainActivity extends Activity @Over ride public void onCreate ( Bundle savedinstanceState) { supe r . onCreate ( savedinstanceState ) ; setContentView( R . layout . main ) ; Button btn = ( Butto n ) findViewByid ( R . id . btn ) ; b t n . setOnClicklistene r ( new OnClicklistene r ( ) { @Override public void onClick( View a rg O ) { Intent intent = new Intent ( ) ;
}}) ;
}
inten t . setClassName ( " com. cust . pe rm " , " com. cust . pe rm . P rivActivity " ) ; sta rtActivity ( intent ) ;
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
269
Z listingu 7.7 widać, że wprowadzamy odniesienie do przycisku zdefiniowanego w pliku main.xml, a następnie dołączamy nasłuchiwacz reagujący na kliknięcie. Po wywołaniu przy cisku zostaje utworzona nowa intencja, a następnie ustanowiona nazwa klasy aktywności, która ma zostać uruchomiona. W naszym przypadku chcemy uruchomić aktywność com. c u s t . perm. -+PrivActivity z pakietu com. c u s t . perm. W tym momencie jedynym brakującym elementem jest wpis u s e s - pe rmis sion, który jest dodawany do pliku manifestu po to, aby poinformować Androida, że należy skorzystać z uprawnienia s y h . permi s s i o n . STARTMYACTIVITY. Zamieńmy kod manifestu z projektu klienta na zawarty w listingu 7.8. Listing 7.8. Plik manifest klien ta capplication android : icon="@d rawable/icon" android : label="@string/app_name"> cactivity and roid : name= " . ClientCustPermMainActivity" android : label="@st ring/app_name"> cuses- permission android : n ame="syh . permission. STARTMYACTIVITY"> cuses - sdk android : minSdkVe rsion="2" />
W listingu 7.8 dodaliśmy wpis u s es - pe rmis sion, żądający niestandardowego uprawnienia, które jest wymagane do uruchomienia aktywności PrivActivity zaimplementowanej w na szym projekcie. Teraz można wdrożyć p rojekt klienta na emulator i kliknąć przycisk Uruchom PrivActivity. Po przetworzeniu zdarzenia kliknięcia zostanie wyświetlony napis Pozd rowienia z aktywności P rivActivity. Po udanym wywołaniu uprzyw·i!ej owa nej aktywności można usunąć wpis uses - permission z pliku manifestu klienta i ponownie wdrożyć projekt. Po kliknięciu przycisku wywołania aktywności pojawi się komunikat o błędzie. Zauważmy, że aplikacja LogCat wyświetli wy jątek odmowy uprawnienia.
Wiemy już, w jaki sposób działają niestandardowe uprawnienia w Androidzie. Oczywiście nie są one ograniczone wyłącznie do aktywności. Istotnie, można stosować uprawnienia za równo predefiniowanie oraz niestandardowe również wobec innych rodzajów składników Androida. Zajmiemy się teraz kolejnym rodzajem uprawnień: uprawnieniami identyfi katorów URI.
270
Android 2. Tworzenie aplikacji
Stosowanie uprawnień identyfikatorów URI W przypadku dostawców treści (omówionych w rozdziale 3.) całkowite przydzielanie do stępu lub jego całkowite blokowanie często nie wchodzi w grę. Na szczęście w Androidzie została zaimplementowana odpowiednia technologia. Wyobraźmy sobie załączniki do wia domości e-mail. Żeby załącznik został wyświetlony, musi zostać odczytany przez inną ak tywność. Ale ta aktywność nie powinna mieć dostępu do wszystkich danych poczty e-mail, a nawet do pozostałych załączników. W tym momencie pojawiają się uprawnienia identyfi katorów URI. Po wywołaniu innej aktywności oraz przekazaniu jest identyfikatora URl aplikacja może sprecyzować, że daje uprawnienia jedynie przekazywanym identyfikatorom URl. Jest to doko nywane za pomocą metody grantU riPermission ( ) i przekazania flagi Intent . FLAG_GRANT_ -+READ_URI PERMISSION lub flagi Intent . FLAG_GRANT_WRITE_URI_ PERMISSION jako argumentu.
Praca z usługami opartymi na położeniu geograficznym Usługi oparte na położeniu geograficznym składają się z dwóch zasadniczych części: inter fejsów API map oraz interfejsów API lokalizacji. Każdy z tych interfejsó\\· jest umieszczom· w oddzielnym pakiecie. Na przykład pakiet map to com.google.android.maps, natomiast pa kiet lokalizacji nosi nazwę android.location. Interfejsy map zawierają narzędzia pozwalające na wyświetlanie map oraz manipulowanie nimi. Na przykład można przybliżać oraz prze suwać widok, zmieniać tryb wyświetlania mapy (z widoku satelitarnego na widok ulic), na kładać na mapę własne informacje i tak dalej. Z drugiej strony dostępne są dane systemu GPS (ang. Global System Positioning - globalny system ustalania położenia geograficznego) oraz informacje o położeniu geograficznym uzyskiwane w czasie rzeczywistym, które są do starczane poprzez pakiet lokalizacji. Interfejsy te łączą się poprzez internet z usługami umiejscowionymi na sern·erach firmy Google. Zatem jeżeli te usługi mają działać, wymagany będzie dostęp do internetu. Ponadto należy zaakceptować warunki korzystania z usług, zanim będzie można rozpocząć projek towanie aplikacji korzystających z interfejsów API tych usług. Należy uważnie przeczytać te warunki; firma Google ogranicza zastosowanie danych usług. Na przykład można udostęp nić informacje o położeniu dla użytku osobistego użytkowników, jednak pewne zastosowa nia komercyjne, jak również aplikacje wspomagające zautomatyzowaną kontrolę pojazdów są zabronione. Warunki korzystania z usług zostaną wyświetlone podczas procesu rejestracji w celu uzyskania klucza interfejsu API. W niniejszym podrozdziale omówimy obydwa rodzaje pakietów. Zaczniemy od interfejsów map i pokażemy, w jaki sposób można wykorzystać mapy w aplikacji. Jak się przekonamy, praca z mapami w Androidzie ogranicza się do stosowania kontrolki interfejsu CI MapVie�1 oraz klasy MapActivity, wraz z interfejsami map API zintegrowanymi z serwerem Google Maps. Zademonstrujemy także, w jaki sposób można umieszczać własne informacje na wyświe tlanej mapie. W następnej części zajmiemy się usługami lokalizacji, które rozwijają koncepcje usług map. Zaprezentujemy możliwości klasy Geocoder oraz usługi LocationManager. Poru szymy także tematykę wątkowania - bardzo istotną w przypadku tych interfejsów API.
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
271
Pakiet mapowania Jak już wspomnieliśmy, interfejsy API map są jednym ze składników usług Androida opar tych na położeniu geograficznym. Pakiet ten zawiera wszelkie dane niezbędne do wyświe tlenia mapy na ekranie, umożliwienia użytkownikowi poruszania się po niej (na przykład powiększania jej), wyświetlania własnych informacji na górze mapy i tak dalej. Pierwszym etapem pracy z tym pakietem jest wyświetlenie mapy. W tym celu wykorzystamy klasę wi doku MapView. Zanim jednak będziemy mogli z nią pracować, musimy przeprowadzić od powiednie przygotowania. W szczególności musimy uzyskać od firmy Google klucz inter fejsu API mapy. Klucz interfejsu API mapy pozwala Androidowi na nawiązanie łączności z usługą Google Maps w celu uzyskania danych mapy. Poniżej przedstawiamy sposób uzy skania takiego klucza.
Uzyskanie klucza interfejsu API mapy od firmy Google Pierwszą informacją ważną w przypadku klucza interfejsu API mapy jest to, że potrzebne będą dwa klucze: jeden do etapu projektowego na emulatorze, a drugi dla produktu końcowego (na urządzeniu fizycznym). Wynika to z faktu, iż certyfikat potrzebny do uzyskania klucw będzie inny dla wersji testowej i wersji końcowej (wyjaśniliśmy to w pierwszej części rozdziału). Na przykład na etapie projektowania wtyczka ADT generuje plik .adt i wdraża go na emu lator. Ponieważ plik ten musi być podpisany za pomocą certyfikatu, zostaje wykorzystany certyfikat testowy na tym etapie. W momencie przygotowywania aplikacji końcowej naj prawdopodobniej będziemy chcieli skorzystać z własnoręcznie utworzonego certyfikatu do podpisania pliku .apk. Dobra wieść jest taka, że otrzymuje się klucz interfejsu API mapy przeznaczony do wersji testowej oraz drugi klucz dla wersji końcowej, i można je zamienić przed eksportowaniem wersji ostatecznej. Aby uzyskać taki klucz, potrzebny jest certyfikat, który zostanie użyty do podpisania aplika cji (przypominamy, że w fazie projektowania wtyczka ADT wykorzystuje certyfikat testowy do podpisania aplikacji, zanim zostanie ona wdrożona na emulator). Zatem uzyskujemy od cisk palca MDS certyfikatu, a następnie wprowadzamy go w witrynie Google, co spowoduje wygenerowanie skoligaconego z nim klucza. Najpierw musimy znaleźć certyfikat testowy, wygenerowany i przechowywany przez śro dowisko Eclipse. Jego dokładną lokalizację możemy znaleźć w programie Eclipse. Z menu Preferences przechodzimy do opcji Android/Build. Położenie certyfikatu testowego jest wy świetlone w polu Default debug keystore, zaprezentowanym na rysunku 7.5 (w rozdziale 2. umieszczone zostały informacje dotyczące lokalizacji menu Preferences). Żeby wyekstrahować odcisk palca MDS, możemy uruchomić narzędzie keytool wraz z opcją -list, zgodnie z listingiem 7.9. Listing 7.9. Wykorzystanie aplikacji keytool do otrzymania odcisku palca MDS certyfikatu testowego keytool - list - alias and roiddebugkey -keystore "PEŁNA �CIEŻKA PLIKU 4debug . keystore" - storepass android - keypass a n d roid
Zauważmy, że parametr alias testowego magazynu kluczy posiada wartość and roidde 4bugkey. Hasło do magazynu kluczy brzmi android, a hasło klucza prywatnego - również android. Po uruchomieniu polecenia z listingu 7.9 aplikacja keytool wyświetli odcisk palca (rysunek 7.6).
Android 2. Tworzenie aplikacji
272
� Pr�fer�nces +
Build Generał
Budd Settrngs: P' Automatically relresh Resources and Assets folder on buiłd
- Android Burki
LogCat
Usoge Ant
stots
:!') Help
,+. l+1 .+
I ('
Build OLtput
DDMS ·· Launch
+)
,__ �.
lnstall/Update Java Run/Debu9
Silent
(. Normal
I r
Verbose
Default debug keystore:
IC:\Documents and Settings\Dave\.android\debug.keystore f!rov..is:e . . .
eustom debug ke)'·store;
L•1 Team
I+
usage Data Colector Vafidation
"+ XML
I
Restore Qefaułts OK
&>PIY Canceł
Rysunek 7.5. Lokalizacja certyfikatu testowego
Rysunek 7.6. Dane wyjściowe opcji list w narzędziu keytools (odcisk palca został celowo zamazany) Teraz wklejamy nasz odcisk palca MDS cer tyfi katu testowego w odpowiednie pole na stronie Google:
http://code.google.com/android!maps-api-signup.html Należy teraz przeczytać warunki korzystania z usług. Jeżeli są do zaakceptowania, należy wcisnąć przycisk Generate API Key, aby uzyskać spowin owacony z odciskiem palca klucz interfejsu API map dla usługi Google Maps. Klucz ten jest od razu aktywny, zatem można już teraz za jego pomocą uzyskać dostęp do danych map z serwisu Google. Pamiętajmy, że do uzyskania klucza wymagane jest posiadanie konta Google - podczas próby wygenero wania klucza zostanie wyświetlona prośba o zalogowanie się w serwisie Google. Pobawmy Si<( teraz m apam i .
Obiekty MapView i MapActivity Wiele elementów technologii wykorzystującej mapy opiera się na kontrolce i nterfejsu użytkow nika Map View oraz na rozszerzeniu klasy and ro i d . app . Acti vi ty nazwanym MapActi vi ty. Obydwie klasy wykonują ciężką pracę podczas wyświetlania mapy w Androidzie oraz poruszania się po niej. Podczas pracy z mapami należy pamiętać, że obydwie klasy muszą ze sobą
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
273
współpracować. Dokładniej mówiąc, żeby móc korzystać z klasy MapView, należy utworzyć jej egzemplarz w klasie MapActivity. Aby z kolei ten proces się powiódł, potrzebny będzie klucz interfejsu API mapy. W przypadku tworzenia obiektu MapView za pomocą układu graficznego XML należy skonfigurować właściwość and ro id : apiKey. Z kolei w celu ulwo rzenia tej klasy w kodzie Java musimy przekazać klucz API mapy do konstruktora widoku MapVie;1. Ponieważ dane mapy pochodzą z serwera Google Maps, aplikacja będzie wyma gała dostępu do Internetu. Oznacza to, że będzie potrzebne przynajmniej następujące żąda nie uprawnienia w pliku AndroidManifest.xml:
W rzeczywistości za każdym razem, gdy będziemy używać usług opartych na położeniu geogra ficznym (mapy, GPS itd.), będziemy musieli prawdopodobnie umieścić trzy uprawnienia w pliku AndroidManifest.xml. Dwoma pozostałymi są and ro id . pe rmissio n . ACCESS_COARSE '+LOCATION i a n d roid . permission . ACCESS_ FINE_ LOCATION. W listingu 7. 1 0 zostały wy tłuszczone wpisy pliku AndroidManifest.xml umożliwiające działanie aplikacji obsługującej mapy.
Listing 7 .1 O. Znaczniki pliku AndroidManifest.xml wymagane dla aplikacji obsługującej mapy
Zgodnie z tabelą 7.2 przypominamy, że uprawnienie and ro id . permission . ACCESS_ FINE_ '+LOCATION pozwala uzyskiwać dokładne informacje o położeniu geograficznym, na przykład za pomocą technologii GPS. Dzięki uprawnieniu and ro id. permission . ACCESS_ COARSE_ LOCATION dostępne stają się „zgrubne" informacje dotyczące położenia, w tym dane uzyskiwane po przez wieże operatora sieci komórkowej i technologię Wi-Fi. Powinniśmy wprowadzić jeszcze jedną modyfikację do pliku AndroidManifest.xml. Defini cja aplikacji obsługującej mapy wymaga odniesienia do biblioteki map (odpowiedni wiersz został również wytłuszczony w listin gu 7.10). Przyjrzyjmy się rysunkowi 7.7. Na rysunku 7.7 została zaprezentowana aplikacja wyświetlająca mapę w trybie widoku ulic. Widać także, w jaki sposób można przybliżać i oddalać mapę oraz jak zmieniać jej tryb wy świetlania. Układ graficzny XML tej aplikacji jest ukazany w listingu 7. 1 1 .
Rysunek 7.7. Kontrolka Map View w trybie widoku ulic Listing 7.1 1 . Układ graficzny XML aplikacji demonstracyj nej MapViewDemo
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
275
Jak widać z listingu 7.1 1 , nadrzędny menedżer Linearlayout zawiera podrzędny menedżer Linea rlayout oraz widok MapVie�1. W podrzędnym menedżerze Linea rlayout umieszczo ne zostały przyciski, widoczne u góry ekranu na rysunku 7.7. Należy również pamiętać, żeby zaktualizować wartość parametru a n d ro i d : apiKey wartością swojego klucza interfejsu API mapy.
Kod naszej przykładowej aplikacji obsługującej mapy został umieszczony w listingu 7.12. Listing 7 1 2 Klasa rozszerzająca MapActivity, wczytująca układ graficzny XML .
.
li Jest to plik MapViewDemoActivity.java import and roid . o s . Bundle; impo rt and roid . view . Vie�1;
import com . google . an d roid . maps . MapActivity; import com . google . an d roid . maps . MapView; public class MapViewDemoActivity extends MapActivity { p rivate MapView mapView; @Over ride protected void o n C reate ( Bundle savedinstanceState) { supe r . o n C reate( savedinstanceStat e ) ; setContentView( R . layou t . mapview) ; mapView = (MapView ) findViewByid ( R . id . mapview) ; public void myClickHandler(View t a rget ) { switch ( t a rget. getid ( ) ) { case R . i d . zoomin: mapView . getController( ) . zoomin ( ) ; b reak; case R . id . zoomout : mapView . getControlle r ( ) . zoomOut ( ) ; brea k ; case R . i d . s a t : mapView . setSatellite ( t rue ) ; b reak; case R . id . st reet : mapView. setSt reetView ( t rue ) ; b re a k ;
276
Android 2. Tworzenie aplikacji
case R . i d . t raffic : mapView . setTraffic ( t rue ) ; b reak; case R . id . norma l : mapView . setSatellit e ( false ) ; mapView . setSt reetView ( false ) ; mapView . setTraffi c ( false ) ; b reak; }
@Over ride p rotected boolean isLocationDisplayed ( ) { return false; } @Ove r ride protected boolean isRouteDisplayed ( ) { return false; }
} Z listingu 7.12 widać, że wyświetlanie widoku MapV i ew za pomocą metody one reate ( ) nie różni się od vvyświetlania innych typów kontrolek. To znaczy konfigurujemy widok treści interfejsu użytkownika w pliku układu graficznego zawierającego widok MapVie1·1, a resztą zajmuje się ten widok. O dziwo, obsługa funkcji przybliżania jest również bardzo prosta. Aby przybliżyć lub oddalić widok, używa się klasy MapController widoku MapView. Naj pierw wywołujemy metodę mapView . getCont r o l l e r ( ) , a następnie stosujemy metodę zoomln ( ) dla procesu przybliżania i metodę zoom out ( ) dla procesu oddalania. Taki spo sób przybliżania jest jednopoziomowy; użytkownik musi wielokrotnie powtarzać czynność, aby dalej powiększać lub zmniejszać widok.
Równie prosta jest możliwość zmiany trybu wyświetlania mapy. Widok MapView obsługuje kilka trybów: standardowy, widoku ulic, satelitarny lub wyświetlania ruchu ulicznego. Do myślny jest widok standardowy mapy. W trybie widoku ulic zostaje nałożona na mapę do datkowa warstwa, dzięki której można oglądać zdjęcia ulic zaznaczonych niebieskim obrysem. Zdjęcia te zostały wykonane za pomocą kamer umieszczonych na pojazdach poruszających się po tych ulicach. Należy jednak pamiętać, że sama kontrolka MapView nie wyświetla zdjęć w widoku ulic. Do tego jest potrzebna odrębna kontrolka widoku. Temat ten zostanie do kładniej omówiony w rozdziale 16. W trybie satelitarnym są wyświetlane rzeczywiste zdjęcia lotnicze mapy, dzięki którym można obserwować dachy budynków, korony drzew, drogi i tak dalej. Tryb ruchu ulicznego zaznacza na mapie kolorowymi liniami, które ulice są łatwo przejezdne, a które są zakorkowane. Tryb ten jest obsługiwany jedynie wobec największych arterii 1 • Aby zmieniać tryby, należy przypisać właściwej metodzie nastawiającej wartość t rue. W pewnych przypadkach ustanowienie jednego trybu wyłączy inny tryb. Na przykład nie można jednocześnie posiadać trybu widoku ulic oraz trybu ruchu ulicznego, zatem po wybraniu trybu ruchu ulicznego zostanie automatycznie wyłączony tryb v.ridoku ulic. Żeby wyłączyć dany tryb, należy przydzielić mu wartość f a l se.
1
Tryb widoku ulicznego nie obejmuje Polski � przyp.
tłum.
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
277
llllllllfolhl·*I Może się okazać, że wybran e trybu widoku ulic lub trybu ruchu ulicznego nie i
powoduje zmian. Jeżeli mapa zostanie delikatnie przesunięta tuż po wybraniu któreg oś z powyższych trybów, zostanie zaktualizowana właściwymi informacjami.
Aby przesuwać mapę, należy dla widoku MapView w pliku XML skonfigurować at rybut a n d roid : clickable= " t rue" - w przeciwnym wypadku możliwe będzie wyłącznie przybli żanie i oddalanie mapy. W kodzie Java można tego dokonać za pomocą wywołania metody setClickable ( t rue) w widoku MapView. Należy jeszcze wspomnieć o dwóch metodach: isLocationDisplayed ( ) i isRouteDisplayed ( ) . Są one wymagane przez warunki korzystania z usług firmy Google. Aplikacja musi zwracać wartości true lub false serwerowi map w celu określenia, czy położenie geograficzne urzą dzenia jest wyświetlane lub czy są wyświetla ne informacje o trasie, na przykład wyznaczanie trasy samoch odowej . Przyznajemy że w An droidzie ilość kodu potrzebnego do wyświetlenia mapy oraz zaim plementowania funkcji p rzybliżania i zmieniania trybów jest mini malna (listing 7.12). Jed nak istnieje jeszcze prostszy sposób wstawienia kontrolek służących do zmieniania rozmia ru mapy Przyjrzyjmy się plikowi Xi\1L układu graficznego o raz kodowi w listingu 7.13. ,
.
Listing 7 . 1 3 . Prostsze zmienianie rozmiarów c?xml version=" l . O" encoding="utf - 8 " ?> ccom.google. android . maps . MapView androi d : id="@+id/mapview" a n d roid : layout_width="fill parent" and roid : layout_height="wrap_content " a n d roid : clickable="true" android : apiKey="TUTAJ UMIESZCZAMY KLUCZ INTERFEJSU API MAPY" /> public class MapViewDemoActivity extends MapActivity { private MapVie1·1 mapVie1·1 ; @Over ride p rotected void onCreate(Bundle savedinstanceState) s u pe r . onCreate( savedinstanceStat e ) ; setContentView { R . layout . mapview) ; mapView = (MapView) findViewByid { R . id . mapview) ; mapView . setBuiltinZoomCo n t rols ( t rue ) ; }
@Over ride p rotected boolean islocationDisplayed ( ) { return false;
278
Android 2. Tworzenie aplikacji
@Over ride protected boolean isRouteDisplayed ( ) { return false;
} }
Różnica pomiędzy listingiem 7.12 a 7 . 1 3 polega na przekształceniu układu graficznego na szego widoku w taki sposób, żeby wykorzystywał menedżer Relativelayout. Usunęliśmy wszystkie kontrolki odpowiedzialne za zmianę rozmiaru oraz trybu wyświetlanie mapy. Cała magia znajduje się w kodzie Java, a nie w układzie graficznym. Kontrolka MapView po siada już kontrolki umożliwiające przybliżanie i oddalanie. Wys tarczy je włączyć za pomocą metody set Bu il tinZoomCont rols ( ) . Na rysunku 7.8 zostały pokazane domyślne kontrolki zmiany rozmiaru mapy w widoku MapView.
�MlJ@ 1 : 52 PM
Ca nada United States
N o Al b 1 Oet _
\'t':nei.uel.:s Cołombl.l
Peru
Brasil i. 1 , r BOił'JI,:,
Rysunek 7.8. Wbudowane kontrolki widoku MapView Teraz dowiedzmy się, w jaki sposób dodawać własne dane do mapy.
Stosowanie nakładek Usługa Google Maps posiada funkcję pozwalającą na umieszczanie własnych informacji na mapie. Można ją przetestować, wyszukując pizzerie w swoim mieście: serwis Google Maps umieszcza pinezki lub dymki informacyjne nad miejscami spełniającymi kryteria wyszuki wania. Jest to możliwe, ponieważ Google Maps dopuszcza dodawanie własnej warstwy na mapę. W Androidzie j est dostępnych kilka klas usprawniających nakładanie takich warstw·. Najważniejszą klasą tego typu jest Overlay, można jednak równie dobrze korzystać z rozszerze nia tej klasy, nazwanego I t emizedOverlay. W listingu 7.14 został umieszczony przykład.
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
Listing 7.14. Zaznaczanie punktów na mapie za pomocą klasy ltemizedOverlay import j ava . ut i l . A r raylist ; import j ava . util. Lis t ; import import import import
and roid . g raphics . Canva s ; android . g raphics . d rawable. Drawable; android . o s . Bundle; android . widget . Linearlayout ;
import import import import import
com. google . an d roid . maps . GeoPoint; com. google . android . maps . ItemizedOverlay; com. google . android .map s . MapActivity; com . google . and ro i d . map s . MapVie111 ; com. google . an d roid . maps . Overlay!tem;
public class MappingOverlayActivity extends MapActivity { p rivate MapView mapView; @Ove rride p rotected void onCreate(Bundle savedinstanceState) { supe r . onCreate( savedinstanceState ) ; setContentView( R . layout . mapview ) ; mapView = ( MapVie1·1) findViewByid ( R . id . mapview) ; mapView . setBuiltinZoomCont rols ( t rue ) ; mapView . setClickable ( t rue ) ; Drawable marke r=getResou rces ( ) . getD rawable ( R . d rawable . mapmarke r ) ; marker. setBound s ( O , O , marke r . getintrinsicWidt h ( ) , marke r . getintrinsicHeigh t ( ) ) ; Interestinglocations funPlaces = ne111 Interestinglocation s ( ma r ke r ) ; mapView . getOverlays ( ) . add ( funPlaces ) ; GeoPoint pt f unPlaces . getCente r ( ) ; li wskazuje najtrafniejszy punkt mapView. getController( ) . setCent e r ( pt ) ; mapView . getControlle r ( ) . setZoom ( lS ) ; =
}
@Override protected boolean isLocationDisplayed ( ) { return false; } @Over ride p rotected boolean isRouteDisplayed ( ) { return fal s e ; } class Interestinglocations extends ItemizedOverlay { private List locations = new A rraylist ( ) ; private Drawable ma rke r ;
279
280
Android 2. Tworzenie aplikacji
public Interestinglocation s ( D rawable marker) { super( marke r ) ; this . ma rker=ma rke r ; li tworzy miejsca godne uwagi GeoPoint disneyMagicKingdom new GeoPoin t ( ( i nt ) ( 2 8 . 418971*1000000 ) , ( int ) ( - 8 1 . 58 1436*1000000) ) ; GeoPoint dis neySevenLagoon new GeoPoint ( ( i n t ) ( 2 8 . 410067*1000000) , ( in t ) ( - 8 1 . 583699*1000000) ) ; =
=
location s . add ( new Overlayitem(disneyMagicKingdom , "Magie Kingdom" , "Magie Kingdom " ) ) ; locations . ad d ( new Overlayitem (disneySevenlagoon "Seven Lagoon " , "Seven Lagoon " ) ) ; }
populat e ( ) ;
@Ove r ride public void d raw( Canvas canvas, MapVie1·1 mapVie�1. boolean shadow) { supe r . d raw( c anva s , mapView, s h adow) ; boundCenterBottom ( ma rke r ) ; @Ove r ride protected Overlayitem c reateitem ( int i ) { return location s . get ( i ) ; @Over ride public int size ( ) { return locations . size ( ) ;
}
}
W listingu 7.14 pokazujemy, w jaki sposób można nakładać znaczniki na mapę. \V przykła dzie tym zaznaczamy dwa miejsca: Magiczne Królestwo Disneya (ang. Magie Kingdom) oraz Lagunę Siedmiu Mórz (ang. Seven Seas Lagoon). Obydwa miejsca znajdują się "" pobliżu miasta Orlando na Florydzie (rysunek 7.9).
"''Ifff"
Aby nasza aplikacja demonstracyjna zadziałała, musimy wprowadzi ć obiekt rysowany, który posłuży nam za znacznik. Obraz ten musi zostać zachowany w fo ld erze /res/drawable w taki sposób, żeby identyfikator tego zasobu w metod zie getD rawable ( ) odpowiadał nazwie pliku tego obrazu. Nal eży również zdefiniować w nakładce położenie punktu zakotwiczenia znacznika - to znaczy, którym dokładnie miejscem znacznik ma wskazywać punkt na mapie. W naszym przykładzie wywołuj emy metodę boundCente rBottom ( ) wewnątrz metody d raw ( ) . W ten sposób punkt zakotwiczenia znajduje się na środku dolnej krawędzi znacznika. Inną metodą definiującą punkt zakotwiczenia jest boun dCe nt e r ( ) umieszczająca ów punkt dokładnie pośrodku obiektu rysowanego.
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
�\11. Gi� f'oocJd,:.t'\ R�
Sc
281
. Se .�
-.,
Rysunek 7.9. Widok MapView wraz ze znacznikami
Aby dod awać znaczniki na ma pę, n ależy dla niej stworzyć i dodać do ni ej rozszerzenie klasy com . google . a n d roid . maps . Overlay. Nie można utwo rzyć samej klasy Ove r l ay, zatem na leży ją rozszerzyć lub skorzystać z j ednego z rozszerzeń. W naszym przykładzie zaim ple mentowaliśmy klasę I n t e re s t ingLocations rozszerzającą klasę ItemizedOverlay, która z kolei rozszerza klasę Overlay. Klasa Overlay definiuje kontrakt dla nakładki, a kl asa ItemizedOverlay stanowi jej przydatną imp lem entacj ę, pozwalaj ącą na łatwe utworzen ie l isty lokacji, które n astęp nie mogą zostać zaznaczone na map ie. Standardo wym algo rytm em działania jest rozszerzenie klasy ItemizedOverlay oraz doda nie „elementów" - m i ejsc wartych odwiedzenia - do ko n st ruktora. Po utworzeniu takich miejsc można wywołać metodę populate ( } w klasie ItemizedOverlay. Metoda populat e ( } zapisuje w pamięci podręczn ej element(y) Overlayitem. Klasa ItemizedOverlay wywołuje wewnętrznie metodę size ( } służąc ą do określenia ilości nakładanych elementów, a następ nie wchodzi w pętlę, wywołując metodę c reateltem ( i } wobec każdego elementu. W metodzie c reateitem zwrac any jest już utworzony element w oparciu o indeks tablicy. Jak widać w listingu 7.14, tworzymy po prostu punkty i wywołujemy metodę popula t e ( } , aby wyświetli ć znaczniki n a map ie. Ko n t rakt klasy Overlay wykonuje pozostałą część pracy. Aby to za działało , metoda onCreate ( } aktywno ści tworzy wystąpienie klasy Interesting -+Locations i przekazuje obiekt rysowany, któ ry b ędzie wyświetlany jako znaczniki. Na stępnie metoda onCrea te ( } dodaje >vystąp ienie I n t e re s t i ngLocations do zbioru nakła dek (map View . getOve rlays ( ) . add ( ) ) .
Gdy nakładka jest już powiązana z naszą mapą, nadal musimy zdefiniować właściwą pozy cję na mapie, żeby znaczniki były widoczne na ekranie. W tym celu musimy wyznaczyć in teres uj ący nas punkt j ako środek ekranu . Wyb ieram y pierwszy punkt z nakładki j ako novvy śro dek. Metoda getCente r ( ) nakładki zwraca pierwszy punkt ( nie pun kt środkowy, jak można by się spodziewać). Metoda setCent e r ( ) kontrolera widoku mapy ustanawia.środek wyświetlanego elementu. D:lięk.i metodzi e setZoom ( ) decydujemy, jakie jest oddalenie mapy.
282
Android 2. Tworzenie aplikacji
W celach demonstracyjnych wybraliśmy poziom przybliżenia równy wa rtości 15. Mogliby śmy przetworzyć wszystkie elementy znajdujące się w nakładce, żeby określić granice mapy, a następnie dobrać taki poziom przybliżenia, żeby wszystkie znaczniki pojawiły się na jednym ekranie. Kolejnym interesującym aspektem, widocznym w listingu 7.14, jest two rzenie elementu (elementów) Overlayitem. Aby utworzyć element Overlayrtem, wymagany jest obiekt typu GeoPoint. Klasa GeoPoint reprezentuje położenie geografi cz ne poprzez wyznaczenie dłu gości i szerokości geograficznej w mikrostopniach. W naszym przykładzie uzyskaliśmy współ rzędne geograficzne Magicznego Królestwa i Laguny Siedmiu Mórz za pomocą internetowych witryn geokodujących (jak się wkrótce okaże, geokodowanie może posłużyć do przekształcania adresu na parę współrzędnych długość - szerokość geograficzna). Następnie przekonwer towaliśmy współrzędne geograficzne na mikrostopnie - interfejsy API akceptują te jed nostki - mnożąc wynik przez 1 OOO OOO i otrzymaną liczbę przekształcając na liczbę całkowitą. Do tej pory pokazaliśmy, w jaki sposób umieszczać znaczniki na mapie. Nakładki nie są jednak ograniczone wyłącznie do pinezek i chmurek informacyjnych. Można za ich pomocą przeprowadzać inne czynności. Możemy na przykład wyświetlać animacje wędrujące po mapie lub pokaZ)"'\laĆ symbole, na przykład frontów atmosferycznych lub burz. Podsumowując, umieszczanie znaczników na mapie nie może być prostsze. A może jednak? Nie posiadamy bazy danych współrzędnych geograficznych, domyślamy się jednak, że możemy stworzyć co najmniej jeden obiekt GeoPoint, kor zystaj ąc z adresu geograficznego. W tym momencie pojawia się klasa Geocoder, którą zajmiemy się w następnych par agrafach .
Pakiet położenia geograficznego Pakiet android.location zawiera funkcje usług opartych na położeniu geograficznym. W niniej szym paragrafie zajmiemy się dwoma istotnymi elementami tego pakietu: klasą Geocoder oraz usługą LocationManager. Rozpoczniemy od klasy Geocoder.
Geokodowanie w Androidzie Jeżeli mapy mają być wykorzystywane w jakiś praktyczny sposób, prawdopodobnie należy przekonwertować adres (lub lokację) na współrzędne geograficzne. Pojęcie to jest znane jako geolokalizacja, a klasa a n d roid . location . Geocode r posiada tę fu nkcję. W rzeczywistości klasa Geocoder umożliwia konwersję w obydwie strony - może przekształcić adres na współrzędne geograficzne oraz przetłumaczyć parę szerokość - długość geograficzna na listę adresów. Klasa ta posiada następujące metody: • List getF romLocation ( double latitude, double long i t u d e , int maxResults ) , •
List getF romLocationName (St ring locationName, int maxResult s , double lowe rleft Lati t u d e , double 10�1e rleft longitude , double upperRightlatitude, double upperRightlongitude ) ,
• List getF romLocationName ( S t ring locationName , int maxResult s ) . Okazuje się, że przetwarzanie adresu nie jest nauką ścisłą z powodu różnorodnych sposo bów opisywania lokacji. Na przykład metody getF romLocationName ( ) akceptują nazwę miejsca, adres fizyczny, kod lotniska lub zwyczajową nazwę. Zatem metody zwracają nie jeden adres, a całą ich listę. Z tego powodu istnieje możliwość ograniczenia listy wyników, usta nawiając wartość maxResul t s w zakresie od 1 do 5. Przyjrzyjmy się przykładowi.
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
283
Listing 7.15 przedstawia układ graficzny XML oraz odpowiadający mu kod Java interfejsu użytkownika z rysunku 7.10. Żeby ten przykład zadziałał, należy umieścić we właściv.rym miejscu własny klucz interfejsu API mapy.
Listing 7.15. P raca z klasą Geocoder c?xml version= " l . O " encoding="utf - 8 " ?> import import import import
com . google . and roid . maps . MapActivity ; com . google . an d roid . maps . MapControlle r ; com . google . an d roid . maps . MapView; com . google . an d roid . maps . MylocationOverlay;
import a n d roid . o s . Bundle; public class MylocationDemoActivity extends MapActivity { MapView mapView = null ; MapController mapController = null; MyLocationOverlay whe reAmI = null; @Ove r ride p rotected boolean isLocationDisplayed ( ) { return whereAmI . isMylocationEnabled ( ) ; } @Over ride
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
295
p rotected boolean isRouteDisplayed ( ) { return fal se; }
I"' Wywoływane podczas pierwszego utworzenia aktywności. •; @Over ride public void onCreate ( Bu ndle savedinstanceState) { supe r . on C reate( savedinstanceStat e ) ; setContentView ( R . layout . main ) ; mapView = (MapView ) findViewByid ( R . i d . geoMa p ) ; mapView . setBuiltinZoomControls ( t rue ) ; mapController mapView . getControlle r ( ) ; mapCont roller . setZoom ( lS ) ; =
whereAmI = new MyLocationOverlay ( t h i s , mapView) ; mapView . getOverlays ( ) . ad d ( whe reAmI ) ; mapView . postinvalidate ( ) ; }
@Over ride public void onResume( ) { supe r . onResume ( ) ; whereAmI . enableMyLocation ( ) ; whereAmI . runOnFi rstFix( new Runnable ( ) { public void run ( ) { mapControlle r . setCente r ( whe reAmI . getMyLocation ( ) ) ; } }) ; } @Ove r ride public void onPause ( ) {
super. onPause ( ) ; whe reAmI . disableMyLocation ( ) ;
} Upewnijmy się, że zmieniliśmy superklasę z Activity na MapActivity oraz ją importo waliśmy. M us imy także zaktualizować plik AndroidMan ifest.xml w taki sposób, żeby zo stały w nim umieszczone odpowiednie uprawnienia oraz znaczniki korzystania z właści wych bibliotek, omówione na początku rozdziału. Zwróćmy uwagę, że w tym przykładzie metoda islocationDisplayed ( ) będzie zwracała wartość t rue w przypadku, gdy będzie wyświetlane na mapie położenie geograficzne urządzenia.
Po uruchomieniu aplikacji na emulatorze musimy zacząć wysyłać aktualizacje położenia geograficznego, zanim zrobi się naprawdę interesująco. W tym celu przechodzimy do wi doku Emulator Control interfejsu DDMS, omówionego kilka stron wcześniej Powinniśmy jeszc ze znaleźć w internecie przykładowy plik GPX. Wymienione wcześniej witryny zawie rają ich olbrzymią ilość. Wybierzmy jeden plik i skopiujmy go na st ację roboczą. Wczytajmy pob ran y plik w interfejsie DDMS, korzystając z przycisku Load GPX . w zakładce GPX .
..
296
Android 2. Tworzenie aplikacji
interfejsu DDMS. Zaznaczamy ścieżkę z listy na dole i klikamy przycisk odtwarzania (zielony trójkąt). Warto również pamiętać o przycisku Speed. Powinien rozpocząć się proces prze syłania do emulatora strumienia aktualizacji położenia geograficznego, które zostaną prze kazane aplikacji. Wciśnięcie przycisku Speed spowoduje częstszy przebieg aktualizacji. Powyższy kod jest bardzo prosty. Po skonfigurowaniu podstawowych parametrów widoku MapView, ustawieniu kontrolek zmiany rozmiaru mapy i przybliżeniu mapy w odpowied nim stopniu tworzymy nakładkę MyLocationOverlay. Dodajemy nową nakładkę do wido ku MapView, następnie wywołujemy metodę pos tinvalidate ( ) w tym widoku po to, aby ta warstwa została naniesiona na mapę. Gdyby ta ostatnia metoda nie została umieszczona, nakładka zostałaby utworzona, ale nie byłaby wyświetlana. Pamiętajmy, że nasza aplikacja będzie ,.vywoływała metodę onResume ( ) nawet wtedy, gdy będzie uruchamiana, jak również po wyjściu ze stanu wstrzymania. Zatem chcemy powiązać namierzanie lokacji z metodą onRes ume ( ) , a wyłączyć je po wywołaniu metody onPause ( ) . Nie ma sensu marnowanie zapasu baterii na żądania ustalenia położenia geograficznego, je żeli nie będziemy ich potrzebować. Jednak po włączeniu śledzenia położenia geograficznego podczas wywołania metody onResume ( ) chcemy od razu przenieść się do naszego bieżące go położenia geograficznego. Klasa myLocationOverlay posiada w tym przypadku pomoc ną klasę: runOnFi rstFix ( ) . Dzięki tej metodzie możemy skonfigurować kod, który zostanie uruchomiony tuż po otrzymaniu współrzędnych geograficznych położenia. Może to nastą pić natychmiast, ponieważ posiadamy współrzędne ostatniego znanego położenia, albo nieco później, po otrzymaniu informacji od dostawcy GPS_ PROVIDER lub NETWORK_PROVIDER. Za tem, gdy uzyskamy odpowiednie dane, mapa zostanie zgodnie z nimi wyśrodkowana. Nie musimy robić nic więcej, ponieważ klasa MylocationOverlay będzie uzyskiwała uaktual nienia położenia geograficznego i będzie w tych miejscach umieszczała migoczącą, niebieską kropkę. Warto zauważyć, że podczas aktualizowania położenia geograficznego możemy przybliżać i oddalać widok, a nawet·przesuwać się w inne rejony mapy. W zależności od punktu ,,idzenia może to być zaleta lub wada. Jeżeli przesuniemy mapę i nie będziemy pamiętali naszego umiejscowienia na niej, może być ciężko odnaleźć niebieską kropkę, chyba że oddalimy mapę w stopniu umożliwiającym wyświetlenie tego punktu. Aby bieżąca lokacja była zawsze wyświetlana w pobliżu środka ekranu, musi być ona cały czas animowana. Dokonujemy tego poprzez dodanie aktualizacji lokacji do naszej aktywno ści. W następnej wersji naszego ćwiczenia •vykorzystamy uprzednio utworzone elementy, oprócz pliku MyLocationDemoActivity.java. Zaktualizowana wersja tego pliku została przedsta wiona w listingu 7.20.
Listing 7.20. Zastosowanie klasy MyLocationOverlay oraz centrowanie bieżącej lokalizacji import import import import
com . google . an d roid . maps . GeoPoint ; com . google . an d roid . maps . MapActivity; com . google . and roid . maps . MapView; com . google . an d roid . maps . MyLocationOverlay;
import import import import import import
androi d . conten t . Context ; android . location . Location; and roid . location . Locationlistene r ; android . location. LocationManage r ; and roid . o s . Bundle; android . widget . Toast;
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
public class MyLocationDemoActivity extends MapActivity { MapView mapView nul l ; MyLocationOverlay whe reAmI null; LocationManager locMgr = null; Locationlistener loclistener null ; =
=
=
@Over ride p rotected boolean is LocationDisplayed ( ) { return whereAmI . isMylocationEnabled ( ) ; } @Over ride protected boolean isRouteDisplayed ( ) { return false; } /H Wywoływane podczas pierwszego utworzenia aktywności. */ @Over ride public void onCreate(Bundle savedinstanceState) { supe r . onCreat e ( savedinstanceState ) ; setContentView ( R . layout .main ) ;
mapView (MapView) findViewByid ( R . id . geoMa p ) ; mapView . setBuilt inZoomCont rols ( t rue ) ; mapView . getControlle r ( ) . setZoom ( l S ) ; =
whereAmI new MylocationOverlay ( this , mapView ) ; mapView . g etOverlays ( ) . add (whereAmI ) ; mapView . postinvalidate ( ) ; =
locMgr = ( LocationManage r ) getSystemServic e ( Context . LOCATION_SERVICE) ; loclistener = new Locationlisten er ( ) { public void onlocationChanged ( Location location) {
showlocation ( location ) ;
} public void onProviderDisabled ( St ring provider) { } public void onProviderEnabled ( St ring provider) { } public void onStatusChanged ( St ring p rovid e r , int status , Bundle ext ras) { }
}
};
@Over ride
297
298
Android 2. Tworzenie aplikacji
public void onResume ( ) {
supe r . onResume ( ) ; Location lastloc = locMg r . getlastKnownlocation( LocationManager.GPS_PROVIDERJ ; showlocation ( lastloc ) ; locMg r . requestLocationUpdates ( LocationManage r . GPS_PROVIDER, O , li mirz'rime w ms O , li minDistance w metrach loclistenerl ; whereAm I . enableMylocatio n ( ) ; whereAmI . runOnFirstFix( new Runnable ( ) { public void run ( ) { mapView . getCont roller( ) . setCenter (whe reAmI . getMylocation( ) ) ; }) ;
}
}
@Ove r ride public void onPause ( ) { supe r . onPause ( J ; locMg r . removeUpdate s ( loclistener) ; whereAm I . disableMyLocation ( ) ; } private void showlocation( Location location) { i f ( location ! = null) { double lat = location . getlatitude ( ) ; double lng = location . getlongitude ( ) ; GeoPoint mylocation new GeoPoin t ( ( in t ) ( lat*lOOOOOO ) , ( int ) ( lng*lOOOOOO ) ) ; Toast . makeText ( getBaseContext ( ) , "Nowa lokacj a : szerokość [ " + lat + ] długość [ " + lng +" ] " , Toast . LENGTH_SHORT) . show( ) ; mapView . getCont rolle r ( ) . animateTo ( mylocation ) ; } } =
"
Tym razem dodaliśmy nasz własny nasłuchiwacz Locationlistener, zatem podczas gdy klasa MylocationOverlay uzyskuje uaktualnienia pozycji geograficznej od dostawców loka cji, otrzymujemy jednocześnie aktualizacje od dostawcy GPS_PROVIDER. Dopóki będziemy otrzymy>vać te same aktualizacje, obydwaj dostawcy będą zsynchronizowani. W vv-ywołaniu zwrotnym wywołujemy metodę showlocation ( ) , która przesuwa mapę w ten sposób, że bieżąca lokacja jest cały czas widoczna. Wewnątrz metody onResume ( ) zamieściliśmy więcej elementów, niż jest wymagane: dodat kowe wywołania mają wyłącznie charakter poglądo·wy. Na przykład wywołanie metody getLastKnownLocat i on ( ) zwróci albo 1Nartość null w przypadku braku ostatniej znanej lokacji, albo obiekt Location. Za pomocą wartości Location wy;vołujemy również metodę
Rozdział 7 • Analiza usług zabezpieczeń i usług opartych na położeniu geograficznym
299
showLocation ( ) . Jest to przydatne w przypadku posiadania poprawnych współrzędnych lokacji, w przeciwnym wypadku nie przyda się do niczego. Wywołujemy tu także metodę runOnFi rstFix ( ) , która pełni niemal identyczną funkcję. Jeżeli posiadamy współrzędne znanej lokacji, natychmiast do niej przechodzimy. Różnica polega na tym, że jeżeli nie ma my informacji na temat położenia, zostaje skonfigurowany obiekt Run nable, dzięki któremu mapa zostanie wycentrowana na bieżącym położeniu tuż po uzyskaniu właściwych danych. Włączmy tę aplikację na emulatorze, a następnie prześlijmy dane nowej lokalizacji poprzez okno Emulator Control. Zwróćmy uwagę, że w tej demonstracyjnej aplikacji korzystamy również z widgetu Toast, w którym są wyświetlane punkty, do których przechodzimy. Przypominamy również, że wartości m i n Time oraz minDistance (obydwie wynoszą O) nie są zalecane dla końcowej aplikacji. Nie chcemy aktualizować położenia z maksymalną prędko ścią, gdyż w ten sposób bateria zostanie bardzo szybko wyczerpana.
Podsumowanie W niniejszym rozdziale omówiliśmy dwa istotne elementy środowiska Android SDK: mo del zabezpieczeń aplikacji oraz usługi oparte na położeniu geograficznym. W kwestii zabezpieczeó wyjaśniliśmy, że Android v.rymaga cyfrowych podpisów od wszyst kich aplikacji. Opisaliśmy sposoby zapewnienia bezpieczeństwa podczas projektowania aplikacji na emulatorze oraz w środowisku Eclipse, a także metody podpisywania końco wych wersji pakietu Android. Przedstawiliśmy także kwestię zabezpieczeń środowiska wy konawczego - można się było dowiedzieć, że instalator w systemie Android wymaga od aplikacji uprawnień podczas jej instalowania. Pokazaliśmy również, w jaki sposób definiować uprawnienia wymagane przez aplikację, a także w jaki sposób podpisywać wdrażany plik .apk. W kwestii usług opartych na położeniu geograficznym szczegótowo omówiliśmy zastoso wanie kontrolki MapView oraz klasy MapActivity. Najpierw zajęliśmy się podstawovvymi funkcjami mapy, a następnie pokazaliśmy, w jaki sposób można wykorz1stać nakładki do umieszczania znaczników na mapie. Przeanalizowaliśmy nawet proces geokodowania i przetwa rzania tego procesu w wątkach przeprowadzanych w tle. Podaliśmy informacje na temat klasy LocationManager, która uzyskuje szczegółowe informacje o położeniu geograficznym za pomocą dostawców. Na końcu zademonstrowaliśmy, w jaki sposób można wyświetlić bie żące położenie urządzenia na mapie. W następnym rozdziale zajmiemy się tworzeniem oraz użytkowaniem usług w Androidzie.
ROZDZIAŁ
8 Tworzenie i użytkowanie usług
Platforma Android zawiera kompletny stos programowy. Oznacza to, że otrzymu jemy system operacyjny oraz oprogramowanie integracyjne (ang. middleware), a także działające aplikacje (na przykład telefon). Poza tymi składnikami otrzymujemy dostęp do środowiska SDK, dzięki któremu możemy pisać apli kacje na tę platformę. Dotychczas zajmowaliśmy się aplikacjami, które w bez pośredni sposób oddziałują z użytkownikiem poprzez interfejs Ul. Nie omawiali śmy jednak usług działających w tle oraz możliwości tworzenia składników przetwarzanych w tle.
W niniejszym rozdziale skupimy się na tworzeniu i użytkowaniu usług w An droidzie. Na początku omówimy użytkowanie usług HTTP, a w dalszej kolej ności zajmiemy się komunikacją międzyprocesową - czyli komunikacją po między aplikacjami znajdującymi się w obrębie jednego urządzenia.
Użytkowanie usług HTTP Aplikacje w Androidzie, a ogólnie aplikacje na urządzenia mobilne, są małymi programami zaopatrzonymi w dużą ilość funkcji. Jednym ze sposobów zapew nienia tak dużej funkcjonalności aplikacji w tak małym urządzeniu jest uzyski wanie przez nie informacji z różnych źródeł. Na przykład urządzenie T-Mobile G 1 zawiera aplikację Maps, która jest v.ryposażona w pozornie rozbudowane funkcje przetwarzania map. Jednak już wiemy, że program ten jest zintegrowany z serwerem Google Maps oraz innymi usługami, dzięki którym uzyskuje on ową złożoność. Skoro o tym mowa, jest całkiem prawdopodobne, że tworzone przez nas apli kacje będą również wykorzystywały informacje z innych aplikacji. Standardową strategią integracji jest stosowanie protokołu HTTP. Na przykład możemy chcieć udostępnić w internecie serwlet Java, który będzie dostarczał usługi uzyskiwane poprzez jedną z aplikacji Androida. Jak tego dokonać w Androidzie? Interesu jący jest fakt, że zestaw Android SDK jest zaopatrzony w powszechnie stosowany w środowisku J2EE moduł H t t p C l i e n t (http://hc.apache.org!httpclient-3.xl).
Android 2. Tworzenie aplikacji
302
W środowisku Android SDK wersja tego modułu została dopasowana do Androida, ale in terfejsy API zostały niemal niezmienione w stosunku do wersji dla środowiska J2EE. Moduł HttpClient jest rozbudowanym klientem HTTP. Chociaż posiada pełną obsługę protokołu HTTP, najczęściej będziemy wykorzystywać wywołania metod GET i POST. W ni niejszym podrozdziale zajmiemy się wywołaniami GET i POST modułu HttpClient.
Wykorzystanie modułu HttpClient do żądań wywołania GET Poniżej przedstawiamy ogólny algorytm stosowania modułu HttpClient: 1 . Utwórz moduł H t t p C lient (lub skorzystaj z istniejącego odniesienia). 2. Utwórz nową metodę HTTP, na przykład PostMethod lub GetMethod.
3. Skonfiguruj nazwy/wartości parametru protokołu HTTP. 4. Wykonaj wywołanie HTTP za pomocą modułu HttpClient. 5. Przetwórz odpowiedź protokołu HTTP.
W listingu 8 . 1 został pokazany sposób przeprowadzenia wywołania GET za pomocą mo dułu HttpClient. Ponieważ kod będzie próbował uzyskać dostęp d o internetu, musimy podczas
l"•'NlllliTirFlii·"• wywoływania protokołu http za pomocą modułu HttpClient dodać w pliku manifeście upoważnienie a n d ro i d . pe rmi s sion . INTERNET.
Listing 8.1. Żądanie modułu HttpCli e nt wywoła nia GET import import import import
j ava . io . BufferedReade r ; j ava . io . IOException; j ava . io . InputSt reamRead e r ; j ava . net . UR I ;
import import import import
org . apach e . htt p . HttpRespon s e ; org . apache . http . client . HttpClient; org . apache . http . client . method s . HttpGet ; org . apache . ht t p . impl . client . DefaultHttpClien t ;
public class TestHttpGet { public void executeHttpGet ( ) th rows Exception { BufferedReader i n null; try { HttpClient client = new DefaultHttpClient ( ) ; HttpGet request = new HttpGet ( ) ; request . setURI ( new URI ( " http: //code . google. com/android/ " ) ) ; HttpResponse response = client . execute( request ) ; in = new BufferedReader ( new InputSt reamReade r ( respon s e . getEntity ( ) . getContent ( ) ) ) ; =
St ringBuffer s b = new S t ringBuffer( " " ) ; String line = " " ; String NL = System . getProperty ( " line . separato r" ) ; while ( (line = in . readline ( ) ) 1 = null) { s b . append(line + NL) ;
Rozdział 8 • Tworzenie i użytkowanie usług
303
} i n . close ( ) ; String page = s b . toString ( ) ; System . out . println ( page ) ; } finally { if ( i n ! = null) { t ry { i n . close ( ) ; } catch (IOException e ) { e . p rintStackTrace ( ) ; }
}
}
}
Moduł HttpClient zawiera abstrakcje różnych typów żądania protokołu HTTP, na przy kład HttpGet, HttpPost i tak dalej. W listingu 8.1 moduł HttpClient używany jest do po brania zawartości z adresu http://code.google. com/android!. Właściwe żądanie protokołu HTTP jest przeprowadzane poprzez wywołanie metody client . execute ( ) . Po wykonaniu żądania kod przekształca całą odpowiedź w pojedynczy obiekt typu string. Zauważmy, że obiekt BufferedReader jest zamknięty w bloku finally, który jednocześnie zamyka p od stawowe połączenie HTTP. Należy sobie uświadomić, że klasa widoczna w listingu 8.1 nie rozszerza klasy a n d roid . <+app . Activity. Innymi słowy, nie musimy przebywać w kontekście aktywności, aby za stosować moduł HttpClient - możemy go używać wewnątrz kontekstu składnika An droida (na przykład aktywności) lub stosować go jako część samodzielnej klasy, ponieważ moduł ten jest częścią pakietu Android. Kod widoczny w listingu 8 . 1 wykonuje żądanie protokołu HTTP bez przekazywania jakich kolwiek parametrów serwerowi. Możemy przekazywać parametry nazw i wartości jako część żądania poprzez dodanie par nazwa i wartość do adresu URL, podobnie jak to ma miejsce w listingu 8.2. Listing 8.2. Dodawanie parametrów do żądania metody GET protokołu HTIP HttpGet method = new HttpGet ( "h tt p : //somehost/WS2/Upload . aspx?one=valueGoesHe re" J ; clien t . execute ( method ) ;
Podczas wykonywania żądania GET parametry (nazwy i wartości) tego żądania są przeka zywane w postaci części adresu URL. Przekazywanie parametrów w ten sposób ma pewne ograniczenia. Gwoli ścisłości, długość adresu URL n ie powinna przekraczać 2048 znaków. Zamiast z żądania HTTP GET możemy skorzystać z żądania metody HTTP POST. Metoda POST jest elastyczniejsza i przekazuje parametry w formie części treści żądania.
Wykorzystanie modułu HttpClient do żądań wywołania POST Wykonanie wywołania POST jest bardzo podobne do wywołania GET (listing 8.3).
Android 2. Tworzenie aplikacji
304
Listing 8.3. Żądanie metody HTIP POST za pomocą modułu HttpClient import j ava . util . ArrayList; import j ava . ut il . List ; import import import import import import import
o rg . apache . ht t p . HttpResponse; o r g . apache . ht t p . NameValuePai r ; org . apache . http . client . HttpClien t ; org . apache . http . client . entity . U rlEncodedFormEntit y ; org . apache . http . client . method s . HttpPost ; org . apache . http . imp l . client . DefaultHttpClient ; org . apache . http . message . BasicNameValuePai r ;
public class TestHttpPost { public String executeHttpPo st ( ) t h rows Exception { Buffe redReader in = n ul l ; t ry { HttpClient client = new DefaultHttpClien t ( ) ; HttpPost request = new HttpPost{ "http : //j akasst rona/WS2/Upload . aspx" ) ; List postParameters = new A rrayList ( ) ; postParameters . ad d { new BasicNameValuePa i r ( " one" , "valueGoesHere" ) ) ; UrlEncodedFormEntity formEntity = new U rlEncodedFo rmEntity( postPa ramete rs ) ; request . setEntity(formEntity ) ; HttpResponse response = client . execute( reques t ) ; in = new BufferedRead e r ( new InputSt reamReade r ( response. getEntity ( J . getContent ( ) ) ) ; StringBuffer s b = new StringBuffe r ( " " ) ; String line " ; String NL = System . getProperty ( " line . separato r " ) ; while ( ( line = i n . readline ( ) ) != null) { s b . append ( line + NL) ; =
"
in . close ( ) ; String return } finally if (in t ry
}
}
result s b . toSt ring ( ) ; result; { 1= null) { i n . cl o se ( ) ; } catch ( IOException e) { e . p rintStackTrace ( ) ; }
Rozdział 8 • Tworzenie i użytkowanie usług
305
Aby żądać wywołania metody POST za pomocą modułu HttpClient, musimy wywołać jego metodę execute ( ) wobec instancji HttpPost. Podczas przeprowadzania vrywołań metody POST zazwyczaj przekazujemy parametry nazwa - wartość zakodowane w postaci adresu URL jako część żądania protokołu HTTP. W celu wykonania tego za pomocą modułu HttpClient musimy utworzyć listę zawierającą wystąpienia obiektów NameValuePair i umie ścić ją w klasie U rlEncodedFormEnti ty. Obiekt NameValuePai r zawiera kombinację para metrów nazwa - wartość, natomiast klasa U rl E n codedFormEntity potrafi przetłumaczyć li stę tych obiektów na język zrozumiały dla wywołań protokołu HTTP (ogółem wywołań metody POST). Po utworzeniu klasy U rlEncodedFo rmE n t i t y można ustanowić typ obiektu funkcji HttpPost, a następnie odpowiedzieć na żądanie. W listingu 8.3 stworzyliśmy moduł HttpClient, a następnie utworzyliśmy funkcję HttpPost posiadającą adres URL punktu końcowego protokołu HTTP. Teraz utworzymy listę obiek tów NameValuePair i umieścimy w niej pojedynczy parametr nazwa - wartość. Nadajemy temu parametrowi nazwę one oraz wartość valueGoesHere. Następnie tworzymy wystąpie nie obiektu U rl E n coded Fo rm E n t i t y i przekazujemy jego konstruktorowi listę obiektów NameValuePai robj ects. Na koniec wywołujemy metodę setEnti ty ( ) żądania metody POST i wykonujemy to żądanie za pomocą instancji HttpClient. W rzeczywistości metoda HTTP POST jest o wiele potężniejszym narzędziem. Dzięki niej możemy równie łatwo przekazywać pojedyncze parametry nazwa - wartość, co zostało przedstawione w listingu 8.3, jak również złożone parametry, takie jak pliki. Metoda HTTP POST obsługuje inny format treści żądania, znany jako wieloczęściowa metoda POST (ang. multipart POST). Dzięki temu typowi metody POST możemy wraz z parametrami nazwa wartość wysyłać własne pliki. Niestety, wersja modułu HttpClient dostępna w Androidzie nie obsługuje wieloczęściowych metod POST w sposób bezpośredni. Aby można było przepro wadzać wieloczęściowe wywołania metody POST, wymagane są tizy dodatkowe, posiadające jawny kod źródłowy projekty firmy Apache: Apache Commons IO, Mime4J i HttpMime. Projekty te można pobrać z następujących witryn: • Commons IO: http://commons.apache.org/io/ • Mime4j: http://james.apache.org/mime4j/ • HttpMime: http:l!hc.apache.org/httpcomponents-client!httpmime/index.html
Ewentualnie można odwiedzić poniższą stronę, aby pobrać z jednego miejsca wszystkie pliki
.jar wymagane do korzystania z wieloczęściowej metody POST: http://www.apress.com/book/view/ 1430226595 W listingu 8.4 zostało przedstawione zastosowanie wieloczęściowej metody POST w An droidzie. Listing 8.4. Tworzenie wywołania wieloczęściowej metod y POST import j ava . io . ByteA rrayinputStream; import j ava . io . InputStream; import import import import import import import
o rg . apache. commons . io . IOUtils ; o rg . apache . http . HttpRespon s e ; o rg . apache . http . client . HttpClien t ; org . apache . http . client . methods . HttpPost ; o rg . apache . http . entity . mime . MultipartEntity; o rg . apache . ht t p . entit y . mime . content . InputSt reamBody ; o rg . apache . ht t p . entity . mime . content . St ringBody;
306
Android 2. Tworzenie aplikacji
import org . apache . http . impl . client . DefaultHttpClient ; import android . ap p . Activity; public class TestMultipartPost extends Activity { public void executeMultipartPost ( J t h rows Exception { t ry { InputSt ream is = this . getAssets ( ) . open ( " data . xml " ) ; HttpClient httpClient = new DefaultHttpClien t ( ) ; HttpPost postRequest = new HttpPos t ( " h t t p : //192 . 17 8 . 10 . 131/WS2/Upload . aspx" ) ; byte [ l data = IOUtils . toByteArray ( is ) ; InputSt reamBody i s b = new InputSt reamBod y ( new ByteArrayinputStream ( d at a ) , " uploadedFil e " ) ; StringBody s b l new St ringBody ( "Wpisuj emyJakiśTek s t " ) ; St ringBody sb2 = ne1-1 St ring Body ( " R61-mież Wpisuj emyJakiśTekst " ) ; MultipartEntity multipa rtContent = new MultipartEntity ( ) ; multipartContent . addPart ( " uploadedFile " , isb ) ; multipartContent . addPart ( "one" , s bl ) ; multipa rtConten t . addPa r t ( "two " , sb2 ) ;
}
"•1'ł'ITiłii!01i1?"•
postRequest . setEntity ( multipa rtContent ) ; HttpResponse res =httpClient . execute( postRequest ) ; res . getEntity ( ) . getConten t ( ) . close ( ) ; } catch (Th rowable e ) { li zajmuje się tutaj wyjątkiem }
W przykładzie korzystania z wieloczęściowej metody je st wykorzystywanych kilka plików jar, które nie są dostępne w środowisku Android. Aby pliki te zostały dołączone do pakietu .apk, musimy dodać je jako zewnętrzne pliki .jar w środowisku Eclipse. W tym ce lu na l eży kliknąć prawym przyciskiem nazwę projektu w programie Eclipse, wybrać opcję Properties, następnie Java Class Path, przej ść do zakładki Libraries i zaznaczyć opcję Add External JARs. Wy ko n a n i e tych czyn no ści sprawi, że pliki te będą do s tę p n e zarówno w trakcie kompilacji, jak i działania aplikacji.
Żeby aktywować wieloczęściową metodę POST, musimy wywołać moduł HttpPost i uru chomić jego metodę setEntit y ( ) wraz z instancją MultipartEntity (zamiast obiektu U rlEncodedFo rrnEntity, który tworzyliśmy dla pojedynczego parametru nazwa - wartość). Element MultiPa rtE n t i t y reprezentuje treść żądania wywołania wieloczęściowej metody POST. Jak widać, najpierw tworzymy wystąpienie Mul t i Pa rtEnti t y , a następnie wywołu jemy metodę addPa rt ( ) dla każdej części. W listingu 8.4 zostają dodane do żądania trzy części: dwa ciągi znaków oraz plik XML.
Rozdział 8 • Tworzenie i użytkowanie usług
307
Jeżeli tworzymy aplikację 'vymagającą przekazania wieloczęściowej metody POST do zaso bu sieciowego, prawdopodobnie będziemy chcieli sprawdzić nasze rozwiązanie pod kątem błędów, korzystając z pozorowanej implementacji usługi na lokalnej stacji roboczej. Pod czas pracy z aplikacjami na stacji roboczej uzyskujemy dostęp do komputera lokalnego za pomocą polecenia localhost lub adresu IP 127 . O . O . 1. Jednakże w przypadku aplikacji na Androida nie będziemy korzystać z polecenia localhost (ani z adresu 127 . O . O 1 ) ponie waż emulator będzie swoim własnym lokalnym hostem. Żeby uzyskać dostęp do swojej projektowej stacji roboczej z poziomu emulatora, musimy użyć jej adresu IP (w rozdziale 2. został omówiony sposób określenia adresu IP stacji roboczej). W listingu 8.4 należy pod stawić w miejsce widocznego adresu IP adres IP stacji roboczej Czytelnika. .
,
A co z protokołem SOAP? W internecie jest obecnych wiele usług siecimvych bazujących na protokole SOAP, ale do tej pory firma Google nie wprowadziła w Androidzie bezpośredniej obsługi wywoływania tych usług. Preferowane są raczej usługi oparte na architekturze REST, co na pierwszy rzut oka powoduje zredukowanie ilości obliczeń w urządzeniu klienckim. Jednak z drugiej strony projektant musi poświęcić więcej pracy na wysyłanie danych oraz analizo wanie zwracanych informacji. Idealnym rozwiązaniem byłoby posiadanie opcji umożliwiających wybór sposobu interakcji z usługami sieciowymi. Niektórzy twórcy korzystają z zestawu projektowego kSOAP2 do tworzenia klientów opartych na protokole SOAP dla systemu An droid. Nie będziemy się zajmować tą technologią, jednak jest ona dostępna dla zaintereso wanych osób (http://ksoap2.sourceforge.net!).
Zajmowanie się wyjątkami Radzenie sobie z \'ł)'jątkami jest częścią każdego programu, jednak oprogramowanie 'vyko rzystujące usługi zewnętrzne (na przykład usługi HTTP) musi zwracać szczególną uwagę na wyjątki, ponieważ prawdopodobieństwo występowania błędów zostaje zwielokrotnione. Istnieje kilka rodzajów wyjątków, jakich można spodziewać się podczas korzystania z usług HTTP. Do nich zalicza się wyjątki transportowe, wyjątki protokołowe oraz przekroczenia limitu czasu. Należy wiedzieć, kiedy te wyjątki mogą się pojawić. Wyjątki transportowe mogą wystąpić z wielu różnych powodów, jednak w przypadku urzą dzeń mobilnych najczęstszym scenariuszem jest niska jakość połączenia sieciowego. Wyjąt ki protokołowe występują na poziomie warstwy protokołu HTTP. Należą do nich błędy uwierzytelniania, niewłaściwe pliki cookie i tak dalej. Możemy spodziewać się W)�ątku pro tokołowego na przykład wtedy, gdy mamy dostarczyć poświadczenie tożsamości za pomocą loginu, a tego nie uczynimy. Pod kątem wywołań protokołu HTTP przekroczenia limitu czasu dzielimy na dwie kategorie: przekroczenie czasu połączenia oraz przekroczenie czasu gniazda. Przekroczenie czasu połączenia następuje wtedy, gdy moduł HttpClient nie może ustanowić połączenia z serwerem - na przykład jeśli adres URL jest niepoprawny lub ser wer nie odpowiada. Przekroczenie czasu gniazda ''ł)'stępuje, gdy moduł HttpClient nie otrzyma odpowiedzi w określonym czasie. Innymi słowy, moduł ten zdołał się połączyć z serwerem, lecz serwer nie zwrócił odpowiedzi w wyznaczonym limicie czasowym. Skoro już znamy rodzaje pojawiających się ''ł)'jątków, jak sobie z nimi radzimy? Na szczę ście moduł HttpClient jest solidną strukturą, zdejmującą większość obowiązków z barków projektanta. Tak naprawdę jedynymi wyjątkami, jakimi powinniśmy się przejmować, są te, które są najłatwiej zarządzane. Moduł HttpClient zajmuje się \'ł)'jątkami transportowymi poprzez wykrywanie problemów transportowych i ponawianie żądań (w przypadku tego typu wyjątków opisany sposób jest bardzo skuteczny). Wyjątki protokołowe są zaZ\vyczaj
Android 2. Tworzenie aplikacji
308
usuwane w procesie projektowania. Będziemy się zajmować jedynie przekroczeniami limitu czasu. Prostym i skutecznym sposobem radzenia sobie z obydwoma rodzajami przekroczeń limitu czas - przekroczeniami czasu połączenia i przekroczeniami czasu gniazda - jest umieszczenie metody execute ( ) żądania HTTP wewnątrz instrukcji t ry/catch i spraw dzenie, czy znowu wystąpi niepowodzenie. Zostało to zaprezentowane w listingu 8.5. Listing 8.5. Implementacja prostej techniki ponawiania prób w przypadku prze krocze ń limitu cza su import import import import
j ava . io . BufferedReade r ; j a va . io . IOExceptio n ; j ava . io . InputSt reamReade r ; j av a . net . URI ;
import import import import
org . apache . ht t p . HttpResponse; org . apache . ht t p . client . HttpClient; o rg . apache . ht t p . client. methods . HttpGet; org . apache . http . impl . client . DefaultHttpClient ;
public class TestHttpGet { public String executeHttpGetWithRetry ( ) th rows Exception { int ret ry = 3 ; int count O; while ( count < retry) { count += 1 · t ry { String response executeHttpGet ( ) ; /** •jeśli tutaj trafimy, to znaczy, że próba przebiegła pomyślnie • i możemy zakończyć. */ return response; } catch ( Exception e) { /** •jeśli wyczerpaliśmy limit powtórzeń */ if ( count < retry) { =
=
/**
• mamy jeszcze powtórzenia, zatem wyświetlamy wiadomość • i ponawiamy próbę. */
System . out . p rintl n ( e . getMessage ( ) ) ; } else { System . out . println ( " p róba zakończona niepowodzeniem . . . " ) ; th row e ;
}
}
return null;
}
public String executeHttpGet ( ) th rows Exception { BufferedReader in null; =
Rozdział 8 • Tworzenie i użytkowanie usług
309
t ry { HttpClient client = new DefaultHttpClient ( ) ; HttpGet request = new HttpGet ( ) ; reques t . setURI( new URI ( "htt p : //code . google. com/and roid/ " ) ) ; HttpResponse response = client. execute( request ) ; in = new BufferedReader( new InputSt reamReade r ( response. getEntity ( ) . getContent ( ) ) ) ; StringBuffer s b = new StringBuffe r ( " " ) ; S t ring line = " " ; String NL System . getProperty( " line. separato r " ) ; while ( ( line = i n . readline ( ) ) ! null) { s b . append(line + NL) ; } in . close ( ) ; =
=
}
s b . toString ( ) ; result result; { ! = null) { i n . close ( ) ; } catch ( IOException e ) e . p rintStackTrace ( ) ; }
St ring return finally if ( i n try
}
Kod w listingu 8.5 jest ilustracją możliwości zaimplementowania prostej techniki ponawia nia prób w celu wywołania protokołu HTTP po przekroczeniu limitu czasu. W listingu zo stały ukazane dwie metody: pierwsza wykonuje funkcję HTTP GET (executeHttpGet ( ) ), druga natomiast umieszcza pierwszą metodę w algorytmie ponawiania prób (e x ecu t eH t t pGetWi thRet ry ( ) ). Algorytm ten jest niezmiernie prosty. Ilości powtórzeń prób przypisu jemy wartość 3, a następnie wprowadzamy pętlę while. We wnętrzu tej pętli egzekwujemy żądanie. Zauważmy, że jest ono umieszczone w bloku t ry/catch, a w bloku catch spraw dzamy, czy nie wyczerpaliśmy limitu po"vtórzeń.
Podczas korzystania z modułu HttpClient jako części zwykłej aplikacji, musimy poświęcić baczniejszą uwagę problemom wielowątkowości, które mogą się pojawić. Zajmiemy się ni mi teraz.
Kwestia problemów z wielowątkowością W dotychczas ukazanych przykładach dla każdego żądania tworzyliśmy nowy moduł HttpClient. W praktyce jednak powinniśmy tworzyć j ede n moduł HttpClient dla całej aplikacji i używać go podczas wszelkiej komunikacji z protokołem HTTP. Jeżeli jeden mo duł HttpClient przetwarza wszystkie żądania protokołu HTTP, należy również uważać na problemy zwi ązane z wielowątkowością, które mogą się pojawić w trakcie jednoczesnego przetwarzania wielu żądań przez ten moduł. Na szczęście posiada on funkcje ułatwiające to zadanie - wystarczy utworzyć obiekt D e f a u l t H t t p Client za pomocą klasy ThreadSafe '+ClientConnManager, tak jak zostało t o pokazane w listingu 8.6.
� 2. Tworzenie aplikacji
Listing 8.6. Utworzenie wielowątkowego modułu HttpClient li ApplicationEx.java
import import import import import import import import import import import import import
o rg . apache . http . HttpVe rsion ; o rg . apache . http . client . HttpClient; org . apache . ht t p . conn . Clie�tConnectionManag e r ; o rg . apache . http . con n . scheme. PlainSocketFacto ry ; o rg . apache . http . co n n . scheme. Scheme; org . apache . http . co n n . scheme . SchemeRegistry; org . apache . http . conn . ss l . SSLSocketFactory; o rg . apache . http . imp l . clien t . DefaultHttpClien t ; org . apache . http . impl . conn . t sccm.Th readSafeClientConnManage r ; o rg . apache . http . param s . BasicHttpParams; o rg . apache . ht t p . pa rams . HttpParams ; o rg . apache . http . params . HttpProtocolParams; o rg . apache . http . p rotocol . HTTP;
import and roid . a p p . Application; import android . util . Log; public class ApplicationEx extends Application { private static final String TAG "ApplicationEx " ; p rivate HttpClient httpClient; =
@Over ride public void onCreate ( ) { s u pe r . onCreate { ) ; httpClient
=
c reateHttpClient ( ) ;
@Over ride public void o nlo�1Memory ( ) { s u pe r . on lowMemory ( ) ; s hutdownHttpClient ( ) ; @Over ride public void onTerminate ( ) { s u p e r . onTerminate ( ) ; shutdownHttpClient ( ) ; } private HttpClient createHttpClient ( ) {
Log . d (TAG , " c reateHttpClient ( ) . . . " ) ; HttpParams params = new BasicHttpParam s ( ) ; HttpProtocolPa rams . setVersio n ( params , HttpVersion . HTTP_l_ l ) ; HttpProtocolPa rams . setContentCharset ( p a rams , HTTP . DEFAULT_CONTENT CHARSET) ; HttpProtocolPa rams . setUseExpectContinue ( pa rams, true) ;
Rozdział 8 • Tworzenie i użytkowanie usług SchemeRegistry schReg = new SchemeRegistry ( ) ; schReg . register( new Scheme ( " http " , PlainSocketFactory. getSocketFact o ry ( ) , 8 0 ) ) ; schReg . regist e r ( new Scheme ( " https " , SSLSocketFactory. getSocketFactory ( ) , 443 ) ) ; ClientConnectionManager conMgr new ThreadSafeClientConnManage r ( params , schReg ) ; =
return new DefaultHttpClien t ( conMg r , params ) ; }
public HttpClient getHttpClient ( ) { return httpClient; private void s hutdo1·mHt tpClient ( ) {
i f ( httpClient ! =null && httpClien t . getConnectionManage r ( ) ! =null) { httpClient . getConnectionManage r ( ) . shutdown ( ) ; }
} } li HttpActivity.java
import j ava . net . URI ; import import import import
o rg . apache . ht t p . HttpResponse; o rg . apache . http . clien t . HttpClient; o rg . apache . ht t p . client . methods . HttpGet ; o rg . apache . http . ut i l . EntityUtil s ;
import android . ap p . Activity; import android . o s . Bundle; import android . util . Log ; public class HttpActivity extends Activity {
/** Wywoływane podczas pierwszego utworzenia aktywności. */ @Ove r ride public void onC reate(Bundle savedinstanceState)
{
supe r . onCreate( savedinstanceState ) ; Log . d ( " S e rvicesDemoActivity" , "instrukcja wyszukiwania blędów" ) ; getHttpContent ( ) ;
}
public void getHttpContent ( ) {
t ry ApplicationEx app = (ApplicationEx ) this . getApplication ( ) ; HttpClient client = app . getHttpClient ( ) ; HttpGet request = new HttpGet ( ) ; request . set URI ( new URI ( " http : //�/\·M. google . com/" ) ) ; HttpResponse response = client . execute( reques t ) ;
311
312
Android 2. Tworzenie aplikacji
St ring page=EntityUtil s . toString( respons e . getEntity ( ) ) ; System . out . p rintln( page ) ;
} catch ( Exception e ) {
e . p rintStackTrace ( ) ;
} }
Zwróćmy uwagę, że po przesłonięciu lub rozszerzeniu domyślnego obiektu aplikacji musi my również zmodyfikować węzeł aplikacji w pliku AndroidManifest.xml poprzez skonfigu rowanie atrybutu a n d ro i d : name w następujący sposób:
Jeżeli aplikacja musi wykonywać więcej wywołań protokołu HTTP, należy stworzyć moduł HttpClient obsługujący wszystkie te żądania. Jednym ze sposobów wprowadzenia takiej możliwości jest wykorzystanie faktu, że każda aplikacja w Androidzie posiada powiązany z nią obiekt aplikacji. Domyślnie, jeśli nie zostanie skonfigurowany niestandardowy obiekt aplikacji, wykorzystywany jest a n d ro i d . a p p . Application. Obiekt aplikacji ma jedną intere sującą właściwość: istnieje zawsze jeden egzemplarz takiego obiektu dla danej aplikacji i wszyst kie jej składniki posiadają do niego dostęp (poprzez obiekt globalnego kontekstu). Na przykład z klasy aktywności możemy wywołać metodę getApplication ( ) , aby uzyskać obiekt danej aplikacji. Pomysł polega na tym, że aplikacja jest samotna i zawsze dostępna, zatem możemy rozszerzyć tę klasę i utworzyć tu moduł HttpClient. Dostarczamy następ nie metodę akcesorową, umożliwiającą wszystkim aplikacjom uzyskanie dostępu do tego modułu. Właśnie to zjawisko zostało ukazane w listingu 8.6. Najpierw zauważmy, że posiadamy zdefiniowane w nim dwie klasy (każda powinna być umieszczona w oddzielnym pliku Java). Jedna jest naszym niestandardowym obiektem aplikacji, a druga jest typowym składnikiem klasą activi ty. W klasie A p p l i c a t i o n E x rozszerzamy klasę a n d ro id . a p p . A p p l i c a t i o n , a następnie tworzymy nasz moduł H t t p C l i e n t w metodzie o n e r e a t e ( ) . W dalszej ko lejności dla wszystkich składników zostaje dostarczona metoda akcesorowa, pozwalająca uzyskać odniesienie do klienta. W klasie H t t p A c t i v i t y uzyskujemy odniesienie do glo balnego obiektu aplikacji i kierujemy je do klasy ApplicationEx. Na końcu wywołujemy metodę getHttpClient ( ) i używamy jej do wywołania protokołu HTTP. Przyjrzyjmy się teraz metodzie c reateHttpClient ( ) klasy ApplicationEx. Jest ona odpo wiedzialna za tworzenie naszego pojedynczego modułu HttpClient. Odnotujmy fakt, że pod czas tworzenia metody DefaultHttpClient ( ) "''Puszczamy klasę ClientConnectionManager. Jest ona odpowiedzialna za zarządzanie połączeniami HTTP modułu HttpClient. Ponie waż chcemy, żeby wszystkie połączenia HTTP przetwarzał pojedynczy moduł HttpClient, tworzymy klasę Th readSafeClientConnManager. -'
- Po zakończeniu pracy z menedżerem połączeń należy wywołać metodę shutdown ( ) , tak jak pokazano w listi n gu 8.6.
1• fl ""i 'T lil '·
Rozdział 8 • Tworzenie i użytkowanie usług
313
N a tym zakończymy dyskusję dotyczącą przetwarzania usług HTTP z a pomocą modułu HttpClient. W kolejnych podrozdziałach skupimy się na następnym interesującym ele mencie platformy Android: tworzeniu usług przetwarzanych w tle i nastawionych na długie działanie. Chociaż początkowo nie jest to oczywiste, procesy tworzenia wywołań protokołu HTTP i tworzenia usług w Androidzie są ze sobą powiązane w taki sposób, że często bę dziemy musieli integrować wiele elementów za pomocą usług Androida. Weźmy na przy kład prostą aplikację pocztową. W urządzeniu obsługującym system Android taka aplikacja będzie składała się prawdopodobnie z dwóch części: jedna będzie dostarczać interfejs użyt kownika, druga natomiast będzie sprawdzała, czy są dostępne nowe wiadomości. Proces sprawdzania będzie najprawdopodobniej przeprowadzany w tle. Składnik odpowiedzialny za sprawdzanie będzie usługą Androida, czego skutkiem z kolei będzie wykorzystywanie w tym celu modułu H t tpClient. Zabierzmy się teraz za pisanie usług.
Nawiązywanie komunikacji międzyprocesowej W Androidzie jest zdefiniowane pojęcie usług. Przez usługi mamy na myśli składniki działające w tle, nieposiadające interfejsu użytkownika. Przypominają one usługi systemu Windows albo systemu Unix. Podobnie jak jest w przypadku wymienionych usług, usługi w Andro idzie są zawsze dostępne, lecz nie muszą być bez przerwy aktywne. Istnieją dwa rodzaje usług w Androidzie: usługi lokalne i usługi zdalne. Usługa lokalna nie jest dostępna z poziomu innych aplikacji uruchomionych w urządzeniu. Zasadniczo usługa tego typu jest obsługiwana jedynie przez aplikację, która ją wywołała. W przypadku usług zdalnych dostęp do nich jest uzyskiwany z poziomu aplikacji je wywołujących oraz innych pro gramów. Usługi zdalne są definiowane wobec klientów za pomocą języka AIDL (ang. Android Interface Definition Language język definicji interfejsu systemu Android). -
Naszą analizę usług zacznijmy od napisania prostego przykładu.
Utworzenie prostej usługi W celu zbudowania usługi rozszerzamy abstrakcyjną klasę and r o id . a p p . Service i umiesz czamy wpis konfiguracji usługi w pliku manifeście aplikacji. W listingu 8.7 umieściliśmy przykład. Listing 8.7. Definicja prostej usługi systemu Android import android . app . Service; public class TestServicel extends Service {
private static final String TAG @Over ride public void onCreate ( ) { Log . d (TAG, " o n C reate" ) ; supe r . onCreate ( ) ; } @Over ride
=
"TestServicel " ;
314
Android 2 . Tworzenie aplikacji
public IBinder on �ind ( I ntent intent) { Log . d (TAG, "onBind " ) ; return null;
} wpis definicji usługi: musi znaleźć się w pliku AndroidManifest.xml jako podrzędny węzła .
Jl
li element
Usługa z listingu
8.7 nie ma praktycznego zastosowania, lecz służy do zademonstrowania,
w jaki sposób są definiowane usługi. Aby utworzyć usługę, należy napisać klasę rozszerzają
cą klasę and roid . a p p . Service oraz implementującą metodę on Bind ( ) . Następnie trzeba umieścić wpis definicji usługi w pliku AndroidManifest.xml. W taki sposób jest implemen
towana usługa. Rodzi się teraz następujące, oczywiste pytanie: jak V.')'Wołujemy usługę? Od powiedź zależy od klienta usługi i wymaga nieco większej wiedzy na temat samych usług.
Usługi w Androidzie Łatwiej będzie zrozumieć pojęcie usługi, jeżeli przyjrzymy się metodom publicznym klasy a n d ro id . a p p . Service (listing 8.8).
Listing 8.8. Metody pu bli czne usługi Application getApplication ( ) ; abst ract !Binder onBin d ( I ntent intent ) ; void onConfigurationChange d ( Configuration newConfig ) ; void onCreate ( ) ; void onDestroy ( ) ; void on LowMemo ry( ) ; void onRebind ( Intent intent ) ; void onStart ( I ntent intent , int startid ) ; boolean onUnbind ( I ntent intent ) ; final void setforeground( boolean isforeground ) ; final void stopSelf ( ) ; fina! void stopSelf(int sta rtld ) ; fina! boolean stopSelfResult( int startid ) ; Metoda getApplication ( ) zwraca aplikację implementującą daną usługę. Dzięki metodzie
onBind ( ) zewnętrzne aplikacje działające na tym samym urządzeniu otrzymują interfejs umożliwiający ich komunikację z usługą. Metoda onConfigu rationChanged ( ) pozwala
usłudze na zmianę własnej konfiguracji w przypadku, gdy zmianie ulega konfiguracja urzą dzenia.
System wywołuje metodę onCrea te ( ) podczas pierwszego utworzenia usługi, lecz przed wywołaniem metody onSta rt ( ) Proces ten, podobny do zjawiska hvorzenia aktywności, po .
zwala na przeprowadzenie jednorazowej inicjacji usługi podczas etapu uruchamiania (więcej informacji dotyczących procesu tworzenia akt)"vności można znaleźć w rozdziale 2„ w pod rozdziale „Badanie cyklu życia aplikacji"). Na przykład: jeżeli tworzymy wątek działający w tle, zrobimy to za pomocą metody onCreate( ) i upewnimy się, że zostanie zatrzymany dzięki
Rozdział 8 • Tworzenie i użytkowanie usług
315
metodzie onDestroy ( ) . System najpierw wywołuje metodę onCreate( ) , następnie onStart( ) a w przypadku zamknięcia usługi onDest roy ( ) Poprzez metodę onDest roy ( ) dostar czany jest mechanizm przygotowujący usługę do zamknięcia. ,
-
.
Zauważmy, że metody onSta rt ( ) , one reate ( ) i onDest roy ( ) są wywoływane przez system; nie powinniśmy 'vych miejsc. Zamiast umieszczać odpowiedzialny za to kod w każdej aplikacji, możemy napisać zdalną usługę trasującą i sprawić, żeby komunikowały się z nią aplikacje. Istnieją pewne istotne różnice pomiędzy usługami lokalnymi a zdalnymi. Ściślej mówiąc, jeżeli usługa jest używana wyłącznie przez składniki w obrębie jednego procesu (do uruchamia nia zadań przebiegających w tle), klient musi ją uruchomić za pomocą wywołania me tody Context . sta rtSe rvice ( ) . Jest to usługa lokalna, ponieważ jej celem jest, zasadniczo, uruchamianie zadań przetwarzanych w tle dla aplikacji, która uruchomiła t� usługę. Jeżeli usługa obsługuje metodę onBind ( ) , mamy do czynienia z jej wersją zdalną, którą można \.vywołać za pomocą komunikacji międzyprocesowej (Context . bindService ( ) ). Usługi zdalne są również nazywane usługami obsługujqcymi język AIDL, ponieważ klienty porozumiewają się z nimi za pomocą języka AIDL. Chociaż interfejs klasy and ro id . app . Service obsługuje zarówno usługi lokalne, jak i zdal ne, nie jest dobrym pomysłem wdrożenie jednej implementacji usługi, która obsługiwałaby obydwa rodzaje usług. Wynika to z faktu, że każdy typ usługi posiada predefiniowany cykl życia; połączenie obydwu rodzajów, chociaż jest dopuszczalne, może powodować błędy. Możemy teraz przeprowadzić szczegółową analizę obydwu kategorii usług. Zaczniemy od omówienia usług lokalnych, a następnie przejdziemy do usług zdalnych (usług obsługują cych język AIDL). Jak już wspomnieliśmy, usługa lokalna jest wywoływana jedynie przez za rządzającą nią aplikację. Usługi zdalne obsługują mechanizm \\rywołania RPC (ang. Remote Pro cedure Call zdalne \\rywołanie procedury). Usługa tego typu umożliwia zewnętrznym klientom, obecnym na tym samym urządzeniu, podłączenie do niej i korzystanie z jej funkcj i. -
"'Wr?" 111111
Drugi rodzaj usługi nosi w Androidzie różne nazwy: usługa zdalna, usługa obsługująca język AIDL, usługa AIDL, usługa zewnętrzna oraz usługa RPC. Nazwy te dotyczą tego samego typu usługi - pozwalającej na uzyskanie do niej dostępu zdalnego przez aplikacje uruchomione na urządzeniu.
Android 2 . Tworzenie aplikacji
316
Usługi lokalne Usługi lokalne są usługami uruchamianymi za pomocą metody Context . s t a rtService( ) . Ten typ usług będzie działał do momentu wywołania przez klienta metody Context . stopSe rvice ( ) wobec usługi lub dopóki usługa sama nie wywoła metody stopSel f ( ) . Zwróćmy uwagę, że podczas \"ł)IWOłania metody Context. sta rtSe rvice ( ) system stworzy usługę i wywoła jej me todę onStart ( ) . Musimy pamiętać, że ponowne wywołanie metody Context . sta rtServic e ( ) nie spowoduje utworzenia nowej instancji usługi, lecz przywoła metodę onSta r t ( ) usługi już istniejącej. Poniżej przedstawiamy przykłady usług lokalnych: • Usługa odczytująca dane sieciowe (na przykład z internetu) w oparciu o regulator czasowy (do wysyłania lub pobierania informacji). • Usługa wykonująca zadania, umożliwiająca aktywnościom aplikacji zgłaszanie czynności do wykonania oraz kolejkująca je.
Listing 8.9 przedstawia przykład usługi lokalnej, stanowiącej implementację usługi wyko nującej zadania w tle. W listingu tym zostały zawarte wszystkie artefakty niezbędne do utworzenia oraz użytkowania usługi: plik BackgroundService.java - tworzący samą usługę; plik MainActivity.java - tworzący klasę aktywności wywołującej usługę; oraz plik main.xml - tworzący układ graficzny aktywności. Listing 8.9. Implementowanie usługi lokalnej li BackgroundService.java
import import import import import import
android . ap p . Notificatio n ; android . ap p . NotificationManage r ; android . a p p . Pendinginte n t ; android . a p p . Service; android . conten t . Intent ; and roid . os . IBinder;
public class BackgroundService extends Service {
private NotificationManager notificationM g r ; @Over ride public void onCreate ( ) { supe r . onCreate ( ) ; notificationMgr = ( NotificationManage r ) getSystemService( NOTIFICATION_SERVICE ) ; displayNotificationMessage ( " u ruchamianie usługi w tle" ) ; Thread t h r = new Thread ( null, new ServiceWorke r ( ) , " BackgroundService " ) ; t h r . start( ) ;
} class ServiceWorker implements Runnable { public void run ( ) { li wykonuje przetwarzanie w tle...
Rozdział 8 • Tworzenie i użytkowanie usług
317
li zatrzymuje usługę po zakończeniu. . . li BackgroundService.this.stopSelj();
}
}
@Over ride public void onDes t roy ( ) { displayNotificationMessage ( "zat rzymywanie uslugi przebiegajacej w tle" ) ; supe r . onDestroy ( ) ; } @Over ride public void onStart ( Intent intent, int startid) { supe r . onSta rt ( inten t , s t a rtid ) ; } @Over ride public IBinder onBind ( Intent intent) { return null ; } p rivate void displayNotificationMessage(St ring message) {
Notification notification = new Notificatio n ( R . d rawable . note , message , System . cu rrentTimeMillis ( ) ) ; Pendinglntent contentintent = Pendingintent . getActivity (this, O , new Intent ( t h i s , MainActivity . class ) , O ) ; notification . setLatestEventinfo ( th i s , "Background Service " , message, contentlntent ) ;
}
}
notificationMg r . notify( R . id . app_notification_id , notificatio n ) ;
li MainActivity.java
import import import import import import import
android . a p p . Activity; android . content . Inten t ; and roid . o s . Bundle ; and roid . ut i l . Log ; android . view.View; android . view . View . OnClicklistener; android . widget . Button ;
public class MainActivity extends Activity { private static final St ring TAG = "MainActivity" ; @Over ride public void onCreate(Bundle savedinstanceState)
318
{
Android 2 . Tworzenie aplikacji
supe r . onC reat e ( savedinstanceStat e ) ; setContentView ( R . layout . main ) ; Log . d (TAG, " u ruchamianie usługi " ) ; Button bindBtn = (Button) findViewByid ( R . id . bindBtn ) ; bindBt n . setOnClickListener( new OnClickListene r ( ) { @Over ride public void onCli c k ( View a rg O ) { startService( new Intent(MainActivity . t h i s , BackgroundService . class ) ) ; }} ) ; Button unbindBtn = (Button ) fi ndViewByid ( R . id . unbindBt n ) ; unbindBt n . setOnClickListener( new OnClicklistene r ( ) {
}
@Over ride public void onClick( View a rgO) { stopService( new Intent ( MainActivity . this , BackgroundService . clas s ) ) ; }}) ;
}
and roid : o rientation="vertical" android : layout_width="fill_parent" and roid : layout_height="fill_parent" >
/>
Android 2. Tworzenie aplikacji
326
I! fest to plik MainActivity.java
import com . an d roidboo k . stockquoteservice . IStockQuoteService; import import import import import import import import import import import import import
android . ap p . Activity; android . conten t . ComponentName; android . content . Context; and roid . content . Intent; a n d roid . conten t . ServiceConnection; and roid . o s . Bundle; android . o s . IBinde r ; and roid . o s . RemoteException ; android . ut i l . Log ; and roid . view . View; and roid . view. View . OnClicklistener; and roid . widget . Butto n ; and roid . widget .Toast ;
public class MainActivity extends Activity { protected static final String TAG = "StockQuoteClient " ; private IStockQuoteService stockService null; =
p rivate Button bindBt n ; p rivate Button callBt n ; p rivate Button unbindB t n ; /** Wywoływane podczas pierwszego utworzenia aktywności. �; @Over ride public void onCreate ( Bundle savedinstanceState) supe r . onCreate( savedinstanceState) ; setContentView ( R . layout . main ) ;
bindBtn = (Button) findViewBy! d ( R . id . bindBt n ) ; bindBt n . setOnClicklistene r ( new OnClicklistener ( ) { @Ove r ride public void onClick (View view) { bindService(new Intent ( IStockQuoteServic e . class . getName ( ) ) serCon n , Context . BIND_AUTO_CREATE ) ; bindBt n . setEnabled ( false ) ; callBtn . setEnabled ( t rue ) ; unbindBtn . setEnabled ( t rue ) ; }} ) ; ,
callBtn = ( Button ) f i ndViewByid ( R . id . callBt n ) ; callBt n . setOnClicklisten e r ( new OnClicklistene r ( ) { @Ove r ride public void onClick(Vie1·1 view) { callService ( ) ; }} ) ; callBt n . setEnabled ( false ) ; unbindBtn = (Button ) findViewByid ( R . id . unbindBtn ) ; unbindBt n . setOnClickListen e r ( new OnClicklistener( ) {
Rozdział 8 • Tworzenie i użytkowanie usług
327
@Ove r ride public void onClick(View view) { unbindService ( serConn ) ; bindBtn. setEnabled ( t rue ) ; callBtn . setEnabled ( false) ; unbindBtn . setEnabled ( false ) ; }}) ; unbindBt n . setEnabled (false ) ;
} private void callService ( ) { t ry { double val stockService. getQuot e ( "SYH" ) ; Toa s t . makeText ( MainActivit y . this , "Usługa zwraca wartość "+va l , Toast . LENGTH_SHORT) . sh ow ( ) ; } catch ( RemoteException ee) { Log . e ( "MainActivity " , ee . getMessag e ( ) , ee ) ; } } =
private ServiceConnection serConn
new ServiceConnection ( ) {
@Over ride public void onServiceConnected( ComponentName name, !Binder service) { Log . v ( TAG, "Wywołana metoda onServiceConnected ( ) " ) ; stockService IStockQuoteService . St u b . a s lnterface( service ) ; call Service ( ) ; =
}
@Over ride public void onServiceDisconnected( ComponentName name) { Log . v (TAG, "Wywołana metoda onServiceDisconnected ( ) " ) ; stockService null; } =
};
Aktywność dołącza nasłuchiwacze OnClicklistener dla trzech przycisków: Przyłącz, Wywołaj ponownie i Odłącz. Po kliknięciu przycisku Przyłącz aktywność wywoła metodę bind Service ( ) . W analogiczny sposób po wybraniu przycisku Odłącz zostanie wywołana metoda u n b i n d S e r v i c e ( ) . Zwróćmy uwagę, że metodzie bindService( ) są przekazywane trzy parametry: nazwa usługi AIDL, instancja obiektu ServiceConnection oraz flaga automatycznego utworze nia usługi.
W przypadku usługi AIDL musimy wprowadzić implementację interfejsu ServiceConnection. Interfejs ten definiuje dwie metody: jedna jest wywoływana przez system po ustanowieniu połączenia z usługą, a druga jest wywoływana po przerwaniu takiego połączenia. W naszej implementacji aktywności definiujemy prywatnego, anonimowego członka, implementującego interfejs ServiceConnection dla usługi IStockQuoteService. Podczas wywoływania metody bindSe rvice ( ) przekazujemy jej odniesienie do tego członka. Po ustanowieniu połączenia z usługą uzyskujemy odniesienie do obiektu I S t o c k Q u o t e S e r v i c e za pomocą klasy Stub, a następnie wywołujemy metodę getQuote ( ) z metody callService ( ) .
328
Android 2. Tworzenie aplikacji
Zauważmy, że wywołanie bindService ( ) jest asynchroniczne. Ta asynchroniczność wynika z faktu, że proces lub usługa mogą nie być uruchomione, zatem należy je utworzyć lub uru chomić. Wobec tego platforma posiada wywołanie zwrotne Se rviceConnection, dzięki któremu wiemy, kiedy usługa jest uruchomiona lub kiedy przestaje być dostępna. Teraz wiemy, w jaki sposób tworzyć i użytkować usługi AIDL. Zanim jeszcze bardziej skomplikujemy temat, powtórzmy, co należy zrobić, aby stworzyć prostą usługę lokalną oraz usługę AIDL. Usługa lokalna nie obsługuje metody on Bind ( ) - zwraca wobec niej wartość null. Ten rodzaj usługi jest dostępny wyłącznie dla składników aplikacji, która sprawuje pieczę nad tą usługą. Usługi lokalne są wywoływane za pomocą metody sta rtSe rvice ( ) . Z drugiej strony, istnieje usługa AIDL, która może być użytkowana zarówno przez składniki będące częścią tego samego obiektu, jak i składniki innych aplikacji. Ten typ usługi definiuje kontrakt pomiędzy sobą a klientem za pomocą pliku AIDL. Usługa implementuje kontrakt AIDL, a klient łączy się z definicją języka AIDL. Usługa dokonuje implementacji kontraktu poprzez zwrócenie implementacji interfejsu AIDL z metody onBind ( ) . Klienty łączą się z usługą AIDL poprzez wywołanie metody bind Service ( ) , a rozłączają się z nią dzięki wy wołaniu metody unbindService ( ) . W naszych dotychczasowych przykładach usług przekazywaliśmy wyłącznie proste typy danych języka Java. Usługi w Androidzie w rzeczywistości obsługują również przekazywanie złożonych typów danych. Jest to bardzo przydatne, zwłaszcza w przypadku usług AIDL, ponieważ możemy posiadać dowolną ilość parametrów, które chcemy przekazać usłudze, a nie jest rozsądne prze kazywanie ich wszystkich w formie prostych typów danych. Bardziej sensowne jest upakowanie ich w formie złożonych typów danych i przekazanie ich w tej postaci usłudze. Zobaczmy, w jaki sposób można przekazywać złożone typy danych usługom.
Przekazywanie złożonych typów danych usługom Przekazywanie złożonych typów danych do usług i z usług wymaga więcej pracy, niż prze kazywanie prostych typów danych. Zanim zagłębimy się w ten temat, powinniśmy przed stawić podstawowe koncepcje obsługi złożonych typów danych w języku AIDL:
• Język AIDL obsługuje typy St ring i Cha rSequence. • Istnieje możliwość przekazywania innych interfejsów AIDL, jednak dla każdego interfejsu, do którego tworzone jest odniesienie, wymagana jest instrukcja import (nawet jeśli ten interfejs znajduje się w tym samym pakiecie).
• Istnieje możliwość przekazywania złożonych typów danych, implementujących interfejs and roid . os . Parcelable. Wymagana jest instrukcja import w pliku AIDL dla tych typów danych. •
Język AIDL obsługuje w ograniczonym stopniu interfejsy j ava . ut i l . L i s t i j ava . util . M a p . Dopuszczalne typy danych dla elementów w zbiorze obejmują proste typy danych Java, St ring, Cha rSequence lub android . o s . Pa rcelable. Instrukcje import nie są wymagane dla interfejsów List lub Map, ale są potrzebne dla interfejsu Pa rcelable.
•
Złożone typy danych - poza typem St ring - wymagają zdefiniowania wskaźnika kierunkowego. Zaliczane są do nich parametry in, out oraz inout. Parametr in oznacza, że wartość jest definiowana przez klienta; dzięki parametrowi out wartość zostaje określona przez usługę; w przypadku parametru ino ut wartość określają zarówno klient, jak i usługa.
Rozdział 8 • Tworzenie i użytkowanie usług
329
Interfejs Pa rcelable tłumaczy Androidowi, w jaki sposób obiekty mają być serializowane oraz deserializowane w procesie szeregowania lub rozszeregowania. W listingu 8.15 została pokazana klasa Person implementująca interfejs Parcelable. Listing 8.15. Implementowanie interfejsu Parcelable li fest to plik Person.java package com . sy h ; import and roid . o s . Pa rcel ; import android . o s . Parcelable; public class Person implements Pa rcelable { private int age ; private String name; public static fina! Parcelable . C reator CREATOR new Parcelable . C reator ( ) { public Person createFromPa rcel ( Parcel in) { return new Person ( in ) ; }
};
public Person [ ] newArray( int size) { return new Person [ size ] ; }
public Person ( ) { } p rivate Person( Parcel i n ) readFromParcel ( in ) ; @Over ride public int desc ribeContents ( ) { return O ; } @Over ride public void writeToParcel( Parcel out , int flags ) { out . writelnt ( a ge ) ; out . writeString ( name ) ; public void readFromPa rcel( Parcel i n ) { age = in . readlnt ( ) ; name = i n . readString ( ) ; } public int getAge ( ) { return age ; } public void setAge (int age) { thi s . age = age;
330
Android 2. Tworzenie aplikacji
public String getName ( ) return name;
} public void setName (String name) { thi s . n am e = name;
}
}
Aby zaimplementować ten kod, utwórzmy nowy projekt Androida nazwany S t ockQuo t e Service2. Przypiszmy polu Create Activity aktywność MainActivity i skorzystajmy z pa kietu com.syh. Następnie dodajmy powyższy plik Person.Java do pakietu com.syh w naszym nowym projekcie. Interfejs Pa rcelable definiuje kontrakt odpowiedzialny za hydratację lub dehydratację obiek tów w trakcie procesu szeregowania lub rozszeregowania. Podstawą interfejsu Par ce l a ble jest pojemnik Parcel. Klasa Parcel jest szybkim mechanizmem serializowania i deseriali zowania, zaprojektowanym specjalnie dla komunikacji międzyprocesowej w Androidzie. W klasie tej są zawarte metody pozwalające na rozmieszczanie członków w pojemniku oraz wyciąganie ich z niego. Żeby zaimplementować obiekt komunikacji międzyprocesowej we właściwy sposób, należy postępować zgodnie z następującym algorytmem: 1 . Zaimplementuj interfejs Parcelable. Oznacza to konieczność implementacji metod
wri teToPa rcel ( ) i readF romPa rcel ( ) . Pierwsza metoda służy do zapisania obiektu w parceli, druga natomiast pozwala odczytywać obiekty umieszczone w parceli. Pamiętajmy, że kolejność odczytywania właściwości musi być taka sama, jak kolejność ich zapisywania.
2. Dodaj właściwość static final o nazwie CREATOR do klasy. Właściwość ta wymaga implementacji interfejsu and ro id . o s . Pa rcelable . C rea to r.
3. Przygotuj dla interfejsu Pa rcelable konstruktor potrafiący tworzyć obiekty klasy Parcel.
4. Zdefiniuj klasę Pa r ce l a bl e w pliku .aidl odpowiadającym plikowi .Java, w którym zawarty jest złożony typ danych. Kompilator AIDL będzie szukał tego pliku podczas kompilowania plików AIDL. W listingu 8.16 umieściliśmy przykładowy plik Person.aidl. Plik ten powinien znajdować się w tym samym miejscu, co plik Person.Java. Na widok interfejsu Pa rcelable może narodzić się pytanie, dlaczego w Androidzie nie
•• r•r1• został wykorzystany wbudowany w środowisku Java mechanizm serializacji? Okazuje ' ·• " ·1 m 1 • • • •
się, że twórcy Androida uznali proces serializacji w środowisku Java za zbyt powolny, aby spełnić wymogi komunikacji międzyprocesowej na platformie Android. Zostało zatem utworzone rozwiązanie w postaci interfejsu Parcela ble. Należy w nim jawnie serializować członków klasy, jednak w zamian cały proces przebiega o wiele szybciej.
Należy również uświadomić sobie, że istnieją w Androidzie dwa procesy umożliwiające przekazywanie danych do innego procesu. Pierwszy z nich polega na przekazaniu danych do aktywności za pomocą intencji, a drugi na przesłaniu interfejsu Pa rcelable do usługi. Te dwa mechanizmy nie są stosowane wymiennie i nie należy ich ze sobą mylić. To znaczy interfejs Pa rcelable nie jest przekazywany do aktywności. Jeżeli chcemy uruchomić aktywność i przekazać jej dane, powinniśmy wykorzystać w tym celu intencję. Interfejs Pa rcelable jest przeznaczony wyłącznie do użytku jako część definicji AIDL.
Rozdział 8 • Tworzenie i użytkowanie usług
331
Listing 8.16. Przykładowy plik P erson .a idl package com . sy h ; parcelable com. syh . Person Będziemy potrzebowali pliku .aidl dla każdego interfejsu Parcelable w projekcie. W tym wypadku posiadamy tylko jeden interfejs Pa rce l a b l e - Person. Zastosujmy teraz klasę Person w usłudze zdalnej. Żeby nie komplikować sprawy, zmodyfi kujemy nasz obiekt IStockQuoteService w taki sposób, żeby pobierał parametr wejściov,ry typu danych klasy Person. Pomysł jest taki, aby klienty przekazywały klasę Person do usługi w celu powiadomienia, kto żąda wyniku notowania. Nowy plik IStockQuoteService.aidl został zaprezentowany w listingu 8.17.
Listing 8.17. Prze kazywa nie usługom plików parcelowanych package com . sy h ; import c om. syh . Person; interface IStockQuoteService { String getQuote ( in String ticke r , in Person requester ) ; }
Metoda getQuote ( ) przyjmuje obecnie dwa parametry: symbol notowanej firmy i obiekt Person określający, kto wysyła żądanie. Zwróćmy uwagę, że umieściliśmy wskaźniki kie runkowe dla tych parametrów, ponieważ typy danych tych parametrów nie są proste, oraz że wprowadziliśmy instrukcję import wobec klasy Person. Klasa Person znajduje się w tym samym pakiecie, co definicja usługi (com.syh). Implementacja usługi wygląda teraz tak, j a k w listingu 8 . 1 8 .
Listing 8.18. Implementacja usługi StockQuoteService2 c?xml version= " l . O" encoding="utf - 8 " ?> package com . sy h ; li Jest to plik StockQuoteService2.java import android . ap p . Notification;
Android 2. Tworzenie aplikacji
332
import import import import import import
android . app . NotificationManag e r ; android . ap p . Pending!ntent; android . ap p . Service; android . content . Intent; android . o s . IBinder; android . o s . RemoteException;
public class StockQuoteService2 extends Service { private NotificationManager notificationMg r ; public class StockQuoteServiceimpl extends IStockQuoteServic e . Stub { @Ove r ride public String getQuote (St ring ticke r , Person requester) th rows RemoteException { return "Witaj "+requester . getName ( ) + " ! Notowanie dla "+ticker+" wynosi 20 . O " ; }
@Over ride public void onCreate ( ) { supe r . onCreate ( ) ; notificationMgr (NotificationManager ) g etSystemService ( NOTIFICATION_SERVICE) ; =
displayNotif icationMessage ( "Metoda one reate ( ) wywoiana w StockQuoteSe rvice2 " ) ; } @Over ride public void onDest roy ( ) { displayNoti ficationMessage( "Metoda onoestroy ( ) wywoiana w StockQuoteService2 " ) ; supe r . onDestroy ( ) ; } @Over ride public void onStart ( I ntent intent, int startl d ) { s u pe r . onSta rt ( intent, startld ) ; }
,,...
@Over ride public !Binder onBind ( Intent intent) { displayNoti ficationMessage( "Metoda onBind ( ) wywoiana w StockQuoteService2 " ) ; return new StockQuoteServiceimpl ( ) ; } private void displayNotificationMessag e ( St ring message) { Notification notification = new Notification ( R . d rawable . note , message, System . cu rrentTimeMil l i s ( ) ) ; Pendinglntent contentlntent = Pendingintent . getActivit y ( this , O , new Intent ( th i s , MainActivity . class ) , O ) ;
Rozdział 8 • Tworzenie i użytkowanie usług
333
notificatio n . setlatestEventlnfo ( this , "StockQuoteService2 " , message , contentlnten t ) ;
}
}
notificat ionMgr . notify ( R . id . app_ notification_id , notificatio n ) ;
Różnica pomiędzy obecną a poprzednią implementacją polega na tym, że teraz ponownie wprowadzamy powiadomienia oraz przypisujemy notowaniu wartość string, a nie double. Zwracany użytkownikowi ciąg znaków zawiera uzyskane z obiektu Person informacje o osobie żądającej wyniku notowania, co ma na celu zademonstrowanie poprawnego odczytania wartości wysłanej przez klienta oraz poprawnego przekazania usłudze obiektu Person. Musimy wykonać jeszcze kilka czynności, aby nasz przykład działał:
1 . Umieść plik obrazu note w katalogu /res!drawable. 2. Dodaj nowy znacznik - w pliku
/res/values/strings.xml. 3. Musimy zmodyfikować kod aplikacji w pliku AndroidManifest.xml, tak jak pokazano w listingu 8.19.
Listing 8.19. Zmodyfikowany węzeł w pliku AndroidManifest.xml usługi StockQuoteService2
Na koniec wykorzystamy domyślny plik MainActivity.java, który wyświetla prosty układ graficzny z nieskomplikowaną wiadomością. Gdy już posiadamy implementację naszej usługi, stwórzmy nowy projekt Androida, nazwany StockQuoteClient2. Pakiet niech nosi nazwę com. sayed, a aktywność MainActivity. Aby zaimplementować klienta zdolnego do przekazywania obiektu Person usłudze, musimy skopiować do jego projektu wszystkie elementy projektu usługi wymagane przez klienta. W poprzednim przykładzie potrzebowaliśmy jedynie pliku IStockQuoteService.aidl. Obecnie musimy skopiować również pliki Person.java i Person.aidl, ponieważ obiekt Person jest teraz częścią interfejsu. Po skopiowaniu tych trzech plików do projektu klienta musimy zmodyfikować pliki main.xml i MainActivity.java zgodnie z listingiem 8.20. -
334
Android 2. Tworzenie aplikacji
Listing 8.20. Wywołanie usługi za pomocą obiektu Parcelable
352
Android 2. Tworzenie aplikacji
li RecorderActivity.java import j ava . io . File; import and roid . ap p . Activity; import and roid . media . MediaPlaye r ; import and roid . media . MediaRecorder; import and roid . os . Bundle; import android . view.View; import android. view.View . OnClicklistene r ; import and roid . widget . Butto n ; public class Recorde rActivity extends Activity { private MediaPlayer mediaPlaye r ; private MediaRecorder recorder; p rivate static fin a l String OUTPUT_FILE= " / sdca rdl re co rdoutput . 3gpp" ; @Over ride protected void onCreate(Bundle savedlnstanceState) { supe r . on C reate ( savedlnstancestat e ) ; setContentView ( R . layout . record ) ; Button s t a rtBtn = ( Button) findViewByld ( R . id . bgnBtn ) ; Button endBtn
=
(Button) findViewByld ( R . id . stpBt n ) ;
Button playRecordingBtn = ( Butto n ) findViewByld ( R . id . playRecordingBtn ) ; Button stpPlayingRecordingBtn (Button) findViewByld ( R . id . stpPlayingRecordingBtn ) ; =
startBt n . setOnClickListene r ( new OnClicklistene r ( ) { @Over ride public void onCli c k ( View view) { t ry { beginRecordin g ( ) ; } catch ( Exception e ) { e . printStackTrace ( ) ; }) ;
}
endBtn . setOnClicklistener ( new OnClickListener ( ) {
Rozdział 9 • Używanie szkieletu multimedialnego i interfejsów API telefonii
@Over ride public void onClick(Vie•1 view) { t ry { stopRecording ( ) ; catch ( Exception e ) { e . p rintStackTrace { ) ; }) ;
}
playRecordingBtn . setOnClicklistener { new OnClicklistener { ) { @Over ride public void onClick{ View view) { t ry { playRecording { ) ; catch ( Exception e ) { e . p rintStackTrace { ) ; }) ;
}
stpPlayingRecordingBt n . setOnClicklistene r { new OnClickListene r { ) {
});
@Override public void onClick{Vie1-1 view) t ry { stopPlayingRecording ( ) ; } catch {Exception e ) { e . p rintStackTrace ( ) ; } }
} p rivate void beginReco rding ( ) th rows Exception { killMediaReco rder { ) ; File outFile = new File ( OUTPUT_FILE ) ; i f ( outFile. exists ( ) ) { outFile. delete ( ) ; } recorder = new MediaRecorder { ) ; reco rde r . setAudioSource { MediaReco rde r . AudioSource . MI C ) ; recorde r . setOutputFormat{MediaRecorde r . OutputFormat . THREE_GPP) ; reco rde r . setAudioEncoder (MediaReco rde r . AudioEncode r . AMR_NB ) ; recorder . setOut put File ( OUTPUT_ FILE) ; re co rde r . p repa re { ) ; recorde r . st a rt ( ) ; } private void stopRecording ( ) th rows Exception { i f ( recorder ! = null) { recorder . stop { ) ;
353
354
Android 2. Tworzenie aplikacji
} private void killMediaRecorder ( ) { if ( recorder ! = null) { record e r . releas e ( ) ; } } p rivate void killMediaPlaye r ( ) if (mediaPlayer ! = null) { t ry { mediaPlaye r . release ( ) ; } catch (Exception e ) { e . p rintStackTrace ( ) ; } } } private void playRecording ( ) th rows Exception { killMediaPlaye r ( ) ; mediaPlayer = new MediaPlaye r ( ) ; mediaPlay e r . setDataSou rce( OUTPUT_FILE ) ; mediaPlaye r . prepa re ( ) ; mediaPlaye r . sta rt ( ) ; } private void stopPlayingRecording ( ) th rows Exception { if(mediaPlaye r ! =null ) { mediaPlaye r . stop ( ) ; } } @Over ride p rotected void onDestroy ( ) { supe r . onDestroy( ) ;
}
killMediaRecorde r ( ) ; killMediaPlaye r ( ) ;
}
Zanim zajmiemy się omawianiem listingu 9.5, musimy umieścić w pliku manifeście nastę pujące uprawnienie, aby móc rejestrować dźwięk: cuses - permission android : name="and roid . permission. RECORD_AUDIO" / >
Wspomnieliśmy również w paragrafie dotyczącym kart SD, że w przypadku wartości parametru minSdkVe rsion równej lub większej od 4 musimy dodać także znacznik u s e s - p e rmi s s i on dla klasy "and ro id . pe rmission . WRITE_ EXTERNAL_ STORAG E " . Oczywiście, jeżeli chcemy wypró bować funkcję rejestrowania dźwięku na emulatorze, musimy wyposażyć stację roboczą w mikrofon.
Rozdział 9 • Używanie szkieletu multimedialnego i interfejsów API telefonii
355
Jeśli przyjrzymy się metodzie onCreate ( ) z listingu 9.5, zauważymy, że procedury obsługi kliknięć są podłączone do czterech przycisków. Metoda beginReco rding ( ) zajmuje się pro cesem rejestracji dźwięku. Aby nagrywać dźwięk, musimy utworzyć wystąpienie klasy MediaRecorder i skonfigurować źródło dźwięku, format pliku wynikowego, koder audio oraz ścieżkę pliku wyjściowego. W wersjach środowiska Android SOK starszych od 1.6 je dynym obsługiwanym źródłem dźwięku był mikrofon. Od czasu wydania wersji Android SDK 1 .6 dostępne są trzy dodatkowe źródła dźwięku, wszystkie związane z rozmowami telefo nicznymi. Możemy rejestrować całą rozmowę (MediaReco rde r . AudioSou rce . VOICE_ CALL) , transmisję wyłącznie w górę sieci (MediaReco rde r . AudioSou rce . VOICE UP L I NK) lub transmisję i,,vyłącznie w dół sieci (Media Re co rde r . AudioSou rce . VOICE DOWN LINK). Trans misją prowadzoną w górę sieci może być głos użytkownika telefonu. Transmisją prowadzo ną w dół określa się zazwyczaj dźwięki dochodzące z drugiego końca połączenia. Jedynym obsługiwanym formatem "vyjściowym dźwięku jest format 3GPP (ang. 3rd Generation Partnership Project partnerski projekt trzeciej generacji). Wartością kodowania musi być AMR_NB, czyli wąskopasmmvy koder audio AMR (ang. Adaptive Multi-Rate), ponieważ jest to jedyny obsługiwany format kodera dźwięku. W naszym przykładzie zarejestrowany dźwięk jest zapisany na karcie SD w pliku !sdcard!recordoutput.3gpp. W listingu 9.5 założy liśmy, że utworzyliśmy obraz karty SD i powiązaliśmy go z emulatorem. Informacje potrzebne do przeprowadzenia tej czynności można znaleźć w podrozdziale „Używanie kart SD". -
Dla klasy MediaRecorder dostępne są dodatkowe metody, które mogą okazać się przydatne. Metody setMaxDuration ( int lenght_in_ms) oraz setMaxFileSize (long lenght in_ bytes) stosowane są do ograniczania długości i rozmiaru nagrań dźwiękowych. Po osiągnięciu li mitu maksymalnej długości nagrania w milisekundach lub maksymalnego rozmiaru w baj tach rejestracja dźwięku zostanie zakończona. Obydwie metody zostały zaimplementowane w wersji 1.5 środowiska SDK, zatem są obsługiwane przez niektóre starsze typy telefonów. Zwróćmy uwagę, że aktualna wersja interfejsów API multimediów nie obsługuje strumie niowego przesyłania danych. Na przykład: jeśli rejestrujemy nagranie dźwiękowe, nie mo żemy uzyskać dostępu do strumienia danych w trakcie procesu nagrywania (choćby w ce lach analitycznych). Aby móc pracować na strumieniu danych, musimy go najpierw zapisać do pliku. Przyszłe wersje środowiska Android SDK prawdopodobnie będą posiadały możli wość obsługi strumieni danych. Jednym ze sposobów uniknięcia takiej niedogodności jest zapisywanie strumienia danych do pliku i jednoczesne jego odczytywanie za pomocą innego wątku lub aplikacji.
Analiza procesu rejestracji wideo Od wersji 1 .5 środowiska Android SDK wprowadzono możliwość rejestrowania obrazu wi deo za pomocą struktury multimedialnej. Zasada działania jest podobna do procesu reje stracji dźwięku, i rzeczywiście, nagrany obraz wideo przeważnie posiada również ścieżkę dźwiękową. W przypadku rejestracji wideo istnieje jednak jedna różnica. Począwszy od śro dowiska Android SDK 1.6, wymagane jest, aby na obiekcie Su rface był tworzony podgląd rejestrowanego obrazu. W prostych aplikacjach nie stanowi to problemu, ponieważ użyt kownik pewnie i tak będzie chciał widzieć podgląd tego, co nagrywa. W bardziej złożonych programach może pojawić się problem. Jeżeli aplikacja nie musi pokazywać użytkownikowi podglądu wideo w trakcie nagrywania, obiekt Su rface nadal musi zostać zastosowany, gdyż klasa carne ra 'vymaga podglądu obrazów. Spodziewamy się, że wymóg ten zostanie w przy szłych wersjach środowiska SDK złagodzony, tak aby aplikacje mogły pracować bezpośrednio na buforach wideo bez konieczności kopiowania ich do interfejsu UL Na razie jednak musimy korzystać z obiektu Su rface i w listingu 9.6 pokazujemy, jak to należy robić.
356
Android 2. Tworzenie aplikacji
Listing 9.6. Za s tosowa n i e klasy MediaRecorder do rejestrowania obrazu wideo c?xml version= " l . O " encoding="utf - 8 " ?>
import
import import import import import import
and roid . a pp . A c tivi t y ; android . content . Intent ; android . ne t . Uri; android . o s . Bundle; android . view.View; android . view . View.OnClicklistener; android . widget . Button ;
p u bl ic
cla s s U s ingMediaSto reActivity extends Activity @Over ride prote c ted void onCreat e ( Bundle savedinstanceState) { supe r . on C reate( savedinstanceStat e ) ; setContent V iew ( R . layo u t . ma i n ) ; Button btn = ( Butto n ) findViewByid ( R . i d . recordBt n ) ; btn. setOnClickListener( new OnCli c kListene r ( ) { @Ove r ride
public void onClick( View view) { sta rtRecording ( ) ;
}} ) ;
}
public void st a rtRe c o rd in g ( ) { Intent intt = new In tent ( "and roid . p rovider . MediaSto re . RECORO_ SOUND" ) ; sta rtActivityFo rResult ( intt, O ) ; @Over ride protected void onActivityResult ( int requestCode, int resultCode, Intent data) switch ( req uestCode) { case O : if ( resultCode == RESULT OK) { Uri recordedAudioPath = dat a . getDat a ( ) ;
}
}
}
362
Android 2. Tworzenie aplikacji
Kod z listingu 9.7 tworzy intencję żądającą od systemu przeprowadzenia procesu rejestracji dźwięku. Intencja zostaje uruchomiona wobec akty>vności poprzez wy>vołanie metody sta rtActivi tyForResult ( ) oraz przekazanie tej intencji wraz z obiektem requestCode. Kiedy żądana aktywność wykona swoją pracę, zostaje wy>vołana metoda onActivityResult ( ) wraz z tymże kodem requestCode. Jak widać w metodzie onActivityRe s u l t ( ) , szukamy obiektu requestCode odpowiadającego kodowi przekazanemu klasie sta rtActivityFor -.Res ul t ( ) , a następnie uzyskujemy identyfikator URI zapisanego pliku poprzez '"Y"'ołanie metody data . getData ( ) . Możemy następnie przekazać otrzymany identyfikator URI do intencji, aby odsłuchać nagranie. Utworzony w listingu 9.7 interfejs Ul został zilustrowany na rysunku 9.6.
Rysunek 9.6. Wbudowany rejestrator dźwięku przed nagraniem (po lewej) i po nagraniu (po prawej)
Na rysunku 9.6 zostały umieszczone dwa zrzuty ekranu. Obraz po lewej stronie przedstawia rejestrator dźwięku w trakcie procesu nagrywania, a na zrzucie ekranu po prawej stronie widoczny jest interfejs Ul akty>vności po zatrzymaniu procesu rejestracji. W podobny sposób klasa MediaSto re dostarcza intencję umożliwiającą wykonanie zdjęcia. Przykładem jest kod z listingu 9.8. Listing 9.8. Uruchamianie intencji odpowiedzialnej za robienie zdjęć c?xml
version=" l . O " encoding="ut f - 8 " ?>
Rozdział 9 • Używanie szkieletu multimedialnego i interfejsów API telefonii
import import import import import import import import import import
363
android . ap p . Activity; android . content . ContentValues; android . conte n t . Intent; android . net . U r i ; android . o s . Bundle; android . p rovid e r . MediaStore; android . p rovider. MediaStore . Images . Media ; android . view . View; and roid . view.View . OnClickListener; android .widget . Button;
public class MainActivity extends Activity { Uri myPicture = n u l l ; @Over ride public void onCreate ( Bundle savedinstanceState) { supe r . onCreat e ( s avedinstanceStat e ) ; setContentView ( R . layout . main ) ; setRequestedOrientation (Activityi nfo . SCREEN_ORIENTATION_LANDSCAPE ) ; }
public void captu reimage(View vie11) { ContentValues values = new ContentValues ( ) ; values . pu t ( Media . TITLE, "Moj e p róbne zdjęcie " ) ; values . p u t (Media. DESCRIPTION, "Zdj ęcie �1ykonane apa ratem 4fotograficznym za pomocą intencj i " ) ; myPicture = getContentResolve r( ) . insert(Media. EXTERNAL_CONTENT_URI, values ) ; Intent i = new Intent ( MediaStore . ACTION IMAGE_CAPTURE) ; i . putExtra(MediaStore. EXTRA_OUTPUT , myPict u re ) ; sta rtActivityForResult ( i , O ) ; }
@Over ride protected void onActivityResult ( int requestCode, int resultCode, Intent data ) { i f ( requestCode==O && resultCode==Activity. RESULT_OK) { li Teraz wierny, że identyfikator URI rnyPicture odnosi się do wykonanego przed li chwilq zdjęcia. } }
Klasa aktywności pokazana w listingu 9.8 definiuje metodę c a p t u re!mag e ( ) . Dzięki niej zo staje utworzona intencja, której działanie nosi nazwę MediaSto re . ACTION_ IMAGE_(APTURE. Po uruchomieniu tej intencji zostaje na pierwszym planie wyświetlona aplikacja aparatu fotograficznego i użytkownik może wykonać zdjęcie. Ponieważ z góry utworzyliśmy identyfika tor URI, możemy umieścić dodatkowe informacje na temat zdjęcia, zanim zostanie zrobione.
364
Android 2. Tworzenie aplikacji
Do tego celu służy nam klasa ContentValues. Poza atrybutami TITLE i DECSRIPTION można dodać do obiektu v a l u e s również inne parametry. Pełną listę atrybutów można znaleźć, przeglądając klasę MediaSto re . Imag e s . I mageC o l umn s. Po wykonaniu zdjęcia zostaje wy wołana metoda zwrotna onActivi tyResul t ( ) . W naszym przykładzie użyliśmy dostawcy treści multimediów do utworzenia nowego pliku. Moglibyśmy również utworzyć nowy iden tyfikator URI z nowego pliku na karcie SD, tak jak poniżej: myPicture = U ri . f romFile( new File ( " /sdca rd/DCIM/lOOANDRO/imageCaptureintent . j pg " ) ) ;
Jednakże utworzenie identyfikatora URI w ten sposób utrudnia nam zaimplementowanie atrybutów zdjęcia, na przykład TITLE i DESCRIPTION. Istnieje inny sposób wywołania inten cji aparalu w celu zrobienia zdjęcia. Jeżeli w ogóle nie przekażemy żadnego identyfikatora URI wraz z intencją, otrzymamy obiekt mapy bitowej, zwrócony w argumencie intencji dla metody onA c t i vityResult ( ) . W tym przypadku problemem jest domyślne zmniejszenie rozmiaru otrzymanej mapy bitowej, prawdopodobnie dlatego, że twórcy systemu Android nie chcą, aby były przesyłane duże ilości danych z aktywności aparatu do aktywności naszej aplikacji. Mapa bitowa będzie miała rozmiar 50 KB. Żeby uzyskać obiekt typu Bitmap, wpro wadzamy następującą linijkę do metody onActivi tyResul t ( ) : Bitmap myBitmap = ( Bitmap) data . g etExt ras ( ) . get ( " data " ) ;
W podobny sposób zachowuje się intencja klasy MediaStore odpowiedzialna za rejestrowa nie wideo. W tym celu stosowany jest obiekt MediaStore. AC TIO N_ VIOEO_ CAPTURE.
Dodawanie plików do magazynu multimediów Kolejną funkcją dostępną w szkielecie multimediów Androida jest możliwość dodawania infor macji o plikach multimedialnych do magazynu za pomocą klasy MediaScannerConnection. Innymi słowy, jeśli w magazynie multimediów nie znalazły się informacje o nowych pli kach, klasa MediaScannerConnection służy do umieszczenia w nim danych tego typu. In formacje te mogą być później wykorzystywane przez inne aplikacje. Zobaczmy, jak to działa (listing 9.9). Listing 9.9. Doda wa n ie pliku do magazynu MediaStore c?xml version=" l . O " encoding= " u tf - 8 " 7>
Rozdział 9 • Używanie szkieletu multimedialnego i interfejsów API telefonii
import import import import import import import import import import import
j ava . io . File; and roid . a p p . Activity; and roid . content . Inten t ; and roid . media . MediaScannerConnection; androi d . media . MediaScannerConnection . MediaScannerConnectionClien t ; android . net . U r i ; android . o s . Bundle; android . util . Log ; and roid . view . View; android . widget . EditText; android . widget .Toast ;
public class MediaScannerActivity extends Activity implements MediaScannerConnectionClient { private EditText editText = null; private String filename = null; private MediaScannerConnection conn; @Over ride protected void onC reate (Bundle savedlnstanceState) super . on C reate( savedinstanceState ) ; setContentView ( R . layout . main ) ; editText
=
( EditText) findViewByid ( R . i d . fileName ) ;
}
public void startScan(View view) {
if ( conn ! =null) { conn. disconnect ( ) ; } filename = editText . g etText ( ) . toString ( ) ; File fileCheck = new File( filename ) ; if { fileChec k . isFile { ) ) { conn = new MediaScannerConnection { th i s , this ) ; con n . connect { ) ; } else { Toast . makeTex t ( t h i s , "Taki plik nie istniej e " , Toast . LENGTH_SHORT) . show( ) ; }
@Over ride public void onMediaScannerConnected ( ) conn . s canFile ( filename, null ) ;
{
@Over ride public void onScanCompleted (St ring path, Uri u r i ) { try {
365
366
Android 2. Tworzenie aplikacji
i f ( u ri ! = null) { Intent intent = new I ntent ( In ten t . ACTION_VIEW) ; intent . setDat a ( u ri ) ; sta rtActivity ( intent ) ; } else { Log . e ( "MediaScannerDemo " , " Plik tego typu nie j est obslugiwany " ) ;
}
}
finally { con n . disconnect ( ) ; con n = null;
W listingu 9.9 została ukazana klasa aktywności umożliwiająca dodawanie pliku do maga zynu MediaStore. Jeśli proces dodawania przebiegnie pomyślnie, dany plik zostanie v.ry świetlony użytkownikowi poprzez intencję. Poza wzrokiem użytkownika klasa MediaStore sprawdza typ pliku i inne dotyczące go informacje. Oczywiście w przypadku klasy MediaStore możemy jako drugi argument metody s c a nFile ( ) podać typ MIME. Jeżeli klasa MediaStore nie potrafi rozpoznać typu pliku po jego rozszerzeniu, nie zostanie on dodany do magazynu. Jeżeli natomiast plik jest akceptowany przez klasę MediaSto re, zostaje umieszczony wpis wewnątrz bazy danych dostawcy multimediów. Sam plik nie zostaje przeniesiony. Teraz jednak dostawca multimediów wie wszystko na temat tego pliku. Jeżeli dodaliśmy plik obra zu, możemy uruchomić aplikację Gallery i go obejrzeć. W przypadku pliku muzycznego zostanie on wyświetlony w aplikacji Music. Jeżeli chcemy przejrzeć zawartość bazy danych dostawcy multimediów, otwieramy okno narzę dzi, uruchamiamy aplikację adb shell i przechodzimy do pliku !data/data/com.android. providers.media!databases znajdującego się na urządzeniu. Powinny się tu również znajdo wać zewnętrzne pliki bazodanowe, każdy reprezentujący kartę SD. Ponieważ w telefonie obsługującym system Android można umieścić kilka kart SD, w katalogu tym może znajdować się wiele plików odpowiadających tym kartom. W celu przeglądania tablic bazodanowych umieszczonych w tych plikach możemy posłużyć się aplikacją sqlite3. Istnieją oddzielne ta blice dla plików audio, wideo i obrazów. W rozdziale 3. można znaleźć dodatkowe informa cje na temat korzystania z aplikacji sqlite3. Na tym zakończymy omawianie interfejsów API multimediów. Mamy nadzieję, że dla Czytelnika odtwarzanie i rejestrowanie multimediów nie będzie skomplikowanym procesem. Przechodzi my teraz do interfejsów API telefonii.
Stosowanie interfejsów API telefonii W niniejszym podrozdziale zajmiemy się interfejsami API telefonii w Androidzie. Poświęcimy uwagę zwłaszcza procesowi wysyłania i otrzymywania wiadomości SMS, po którym omó wimy metody odbierania i wykonywania połączeń telefonicznych. Zacznijmy od wiado mości SMS.
Rozdział 9 • Używanie szkieletu multimedialnego i interfejsów API telefonii
367
Praca z wiadomościami SMS Jak już wcześniej wspomnieliśmy, rozwinięciem skrótu SMS jest Short Messaging Service, czyli usługa wysyłania krótkich wiadomości tekstowych, powszechnie stosuje się jednak termin wiadomości tekstowe. Środowisko Android SDK pozwala na wysyłanie i odbieranie takich wiadomości. Przyjrzymy się najpierw różnym sposobom wysyłania wiadomości SMS za pomocą środowiska SDK.
Wysyłanie wiadomości SMS Aby wysłać wiadomość tekstową z poziomu aplikacji, dodamy uprawnienie w pliku manifeście, a następnie sko rzystamy z klasy and ro i d . telephony . SmsManage r {listing 9.10). Listing 9.1 O. Wysyłanie wiadomości SMS (tekstowych) c?xml version="l . O" encoding=" u tf - 8 " ?>
372
import import import import import import
Android 2. Tworzenie aplikacji
and roid . ap p . ListActivity; android . databas e . Cu rs o r ; android . net . U r i ; android . os . Bundle; android . widget . ListAdapter ; android . widget . SimpleCu rsorAdapter;
public class SMSinboxDemo extends ListActivity { private ListAdapter adapter; private static final Uri SMS_INBOX = U ri . pa rse ( " content : //sms/inbox " ) ;
}
@Over ride public void onCreate( Bundle bundle) supe r . on C reate ( bundle ) ; Cursor c = getContentResolve r ( ) . query(SMS_INBOX, n u l l , null, n u l l , null ) ; sta rtManagingCu rso r ( c ) ; String [ ) columns = new String [ ) { " body" } ; i nt [ ) names = new int [ ] { R . id . row } ; adapte r = new SimpleCu rsorAdapt e r ( this , R . layout . sms_inbox, c , column s , names ) ; setListAdapter( adapter) ; }
Kod z listingu
9.12 otwiera skrzynkę nadawczą wiadomości SMS i tworzy listę elementów,
z których każdy zawiera część treści wiadomości tekstowej . Fragment kodu dotyczący układu graficznego z listingu
9.12 zawiera prostą kontrolkę TextVie1-1, w której będzie przechowy
wana treść wiadomości każdego elementu listy. Aby uzyskać listę wiadomości SMS, tworzymy identyfikator
URI wskazujący na skrzynkę wiadomości przychodzących (conten t://sms/inbox),
a następnie wykonujemy prostą kwerendę. \V kolejnym kroku filtrujemy treść wiadomo ści SMS i wyznaczamy adapter listy klasy ListAct ivity. Po wykonaniu kodu z listingu 9.12 ujrzymy listę wiadomości tekstowych, dostępnych
w
skrzynce odbiorczej. Zanim urucho
mimy kod na emulatorze, utwórzmy najpierw kilka wiadomości SMS za pomocą narzędzia Emulator Control. Skoro potrafuny uzyskać dostęp do skrzynki odbiorczej wiadomości SMS, spodziewamy się, że będziemy mogli korzystać również z innych, powiązanych folderów, na przykład ze skrzynki nadawczej lub z folderu przechowującego wersje robocze wiadomości. Jedyną różnicą pomiędzy skrzynką odbiorczą a innymi folderami są ich identyfikatory
URI.
Ta
przykład możemy uzyskać dostęp do skrzynki nadawczej poprzez wykonanie kwerendy wobec adresu
content:!/sms/sent.
oraz definiujące j e identyfikatory
Poniżej znajduje się pełna lista folderów wiadomości SMS
URI:
• Wszystkie: content://sms/All • Odebrane: content://sms/inbox • Wysłane: content://sms!sent • Wersje robocze: content://sms!draft • Nieodebrane: content:!!sms/outbox • Niewysłane: content://sms/failed
Rozdział 9 • Używanie szkieletu multimedialnego i interfejsów API telefonii
373
• Zakolejkowane: content:l/sms/queued • Niedostarczone: content://smslundelivered • Konwersacje: content://sms/conversations
W Androidzie koncepcje wiadomości MMS i SMS są ze sobą połączone i można uzyskać dostęp jednocześnie do dostawców treści obydwu rodzajów za pomocą upoważnienia mms - sms. Zatem możemy korzystać z następującego identyfikatora URI: content : //mm s - sms/conversations
Wysyłanie wiadomości e-mail Skoro już wiemy, w jaki sposób można wysyłać w Androidzie wiadomości SMS, możemy założyć, że za pomocą podobnych interfejsów API są wysyłane wiadomości e-mail. Niestety, Android nie został zaopatrzony w interfejsy API obsługujące wiadomości e-maiL Panuje powszechna opinia, że użytkownicy nie chcą aplikacji, która wysyłałaby wiadomości e-mail w ich imieniu. Zamiast tego należy skorzystać z zarejestrowanej aplikacji e-mailowej w celu wysłania wiadomości. Na przykład możemy wykorzystać obiekt ACTION_SEND do urucho mienia takiej aplikacji. Intent emailintent=new Intent ( Intent . ACTION SEND ) ; String subject = "Cześ ć ! " ; String body = "pozd rowienia
z
Androida . . . . " ;
St ring [ ] ext ra new St ring [ ] { " aaa@bbb . com " } ; emailintent . putExtra ( Inten t . EXTRA_EMAIL, ext ra ) ; =
emailin tent . putExtra ( Intent . EXTRA_SUBJ ECT , s u b j e ct ) ; emailinten t . putExt r a ( Intent . EXTRA_TEXT, body) ; emailinten t . setType ( " message/ rfc822 " ) ; sta rtActivit y ( emailintent ) ;
Powyższy kod uruchamia domyślną aplikację pocztową i pozwala zadecydować użytkownikowi o wysłaniu wiadomości e-mail. Dodatkowymi elementami, które można dodać do intencji wiadomości e-mail, są obiekty EXTRA_ C C i EXTRA_ BCC. Przejdźmy teraz do menedżera telefonii.
Praca
z
menedżerem telefonii
Wśród interfejsów API telefonii znajduje się także menedżer telefonii (a n d ro i d . telephony . ...TelephonyMan a g e r), dzięki któremu możemy uzyskać informacje o usługach telefonicznych dostępnych na urządzeniu i o subskrypcji, a także rejestrować zmiany stanu połączenia te lefonicznego. Zastosowanie funkcji telefonicznych wymaga, aby aplikacje zachowywały się w odpowiedni sposób po wykryciu połączenia przychodzącego. Na przykład odtwarzacz muzyczny wstrzymuje działanie w trakcie połączenia przychodzącego i odtwarzanie zostaje wznowione po zakończeniu rozmowy. \l\T tym paragrafie pokażemy, w jaki sposób rejestro wać zmiany stanu połączenia telefonicznego oraz jak wykrywać przychodzące połączenia telefoniczne. Szczegóły zostały przedstawione w listingu 9.13.
374
Android 2. Tworzenie aplikacji
Listing 9.13. Za stosowa nie menedżera telefonii public class TelephonySe rviceDemo extends Activity { private static final S t ring TAG="TelephonyServiceDemo" ; @Over ride p rotected void onCreate(Bundle savedinstanceState) { supe r . onCreate ( savedinstanceStat e ) ; TelephonyManager teleMgr = (TelephonyManage r ) getSystemService (Context . TELEPHONY_SERVICE) ; teleMg r . listen( new MyPhoneStatelisten e r ( ) , PhoneStatelistene r . LISTEN_CALL_STATE ) ; } class MyPhoneStateListener extends PhoneStateListener { @Over ride public void onCallStateChanged( int state, St ring incomingNumber) { supe r . onCallStateChanged( state , incomingNumbe r ) ; s1o1itch ( state ) { case TelephonyManage r . CALL_ STATE_IDLE : Log . d (TAG, "wywo.tanie stanu idle . . . numer przychodzący to [ "+ incomingNumber+ " ] " ) ; b reak; case TelephonyManage r . CALL_STATE_RINGING: Log . d (TAG, "wywo.tanie stanu ringing . . . numer przychodzący t o [ " + incomingNumber+ " ] " ) ; break; case TelephonyManag e r . CALL STATE_OFFHOOK: Log . d ( TAG, "wywalanie stanu Offhook . . . numer przychodzący t o [ "+ incomingNumber+ " ) " ) ; break; default : Log . d (TAG, " 1·1ywolanie stanu [ "+state+ " J numer przychodzący t o [ " + incomingNumber+" ) " ) ; brea k ; } } } }
Podczas pracy z menedżerem telefonii nie zapominajmy dodać u p rawni enia do pliku ma nifestu, aby uzyskać dostęp do informacji na temat stanu telefonu. W listi ngu 9. 1 3 widać, że będziemy otrzymywać powiadomienia o zmianie stanu telefonu poprzez zaimplementowa nie klasy PhoneStateli s t e n e r i wywołanie metody l i s t e n ( ) klasy TelephonyManag e r . Gdy zostanie wykryte połączenie przychodzące lub stan telefonu ulegnie zmianie, system wywoła metodę onCallStateChanged ( ) klasy PhoneStatelistene r wraz z nowym stanem i numerem dzwoniącego do nas telefonu. W przypadku połączeń przychodzących mamy do czynienia ze stanem CAL L_ STATE_ RINGING. W naszym przykładzie napisaliśmy wiadomość testową w pliku dziennika, jednak w tym miejscu można wstawić określone działanie apli kacji . Aby emulować przychodzące połączenia telefoniczne, możemy skorzystać z interfejsu Ul Emulator Control - podobnie jak w przypadku wysyłania wiadomości SMS (rysunek 9.8).
Rozdział 9 • Używanie szkieletu multimedialnego i interfejsów API telefonii
375
Podczas pracy ze zmianami stanu telefonu może być potrzebny również numer telefonu subskrybenta (użytkownika). Zwraca go metoda TelephonyManag e r . getlinelNumbe r ( ) .
Podsumowanie W rozdziale zajmowaliśmy się strukturą multimediów w Androidzie oraz interfejsami API telefonii. W kwestii multimediów pokazaliśmy, w jaki sposób można odtwarzać pliki audio i wideo. Omówiliśmy także sposoby rejestrowania dźwięków i obrazów wideo, zarówno bezpośrednio, jak i za pomocą intencji .
W drugiej części rozdziału omawialiśmy usługi telefonii w Androidzie. Skupiliśmy się zwłaszcza na wysyłaniu wiadomości tekstowych oraz monitorowaniu otrzymywanych wia domości SMS. Zaprezentowaliśmy także metody uzyskania dostępu do różnych folderów zwi ązanych z wiadomościami SMS, umieszczonych na urządzeniu. Temat zamknęliśmy analizą klasy TelephonyManage r. W następnym rozdziale zwrócimy uwagę na grafikę trójwymiarową poprzez omówienie za stosowania technologii OpenGL w aplikacjach tworzonych na system Android.
ROZDZIAŁ
10 Programowanie grafiki trójwymiarowej za pom ocą biblioteki O pe n G L
W obecnym rozdziale prZ}1rzymy się dokładnie sposobom pracy z interfejsem API biblioteki OpenGL ES na platformie Android. Biblioteka OpenGL ES jest odmianą specyfikacji OpenGL, zoptymalizowaną pod kątem systemów wbudowanych oraz innych urządze11 o małej mocy, na przykład telefonów komórkowych. Platforma Android obsługuję bibliotekę OpenGL ES w wersji 1.0. Ś rodowisko Android SDK zostało zaopatrzone w wiele przykładowych plików, pokazujących możliwości biblioteki OpenGL ES. Jednak w zestawie SDK niemal nie istnieje dokumentacja stanowiąca wstęp do pracy z biblioteką OpenGL ES. Założenie jest takie, że biblioteka OpenGL ES jest otwartym standardem i programiści mogą się uczyć jej użytkowania z ze wnętrznych źródeł. Wskutek tego w tych kilku dostępnych źródłach internetowych lub przykładowych kodach traktujących o korzystaniu z biblioteki OpenGL ES w Androidzie przyjmuje się założenie, że programiści są już zaznajomieni z architekturą OpenGL. W tym rozdziale pomożemy ominąć tę przeszkodę. Po spełnieniu kilku warunków wstępnych już pod koniec rozdziału programowanie za pomocą biblioteki Open GL ES stanie się· przyjemnością. Dokonamy tego niemalże bez udziału aparatu matematycznego (przeciwnie niż w wielu innych książkach poświęco nych bibliotece OpenGL). W pierwszym podrozdziale dokonamy przeglądu bibliotek OpenGL, OpenGL ES oraz niektórych konkurencyjnych standardów. W drugim podrozdziale zajmiemy się częścią teoretyczną, dotyczącą technolo gii OpenGL. Jest to podstawowa sekcja dla osób dopiero rozpoczynających przy godę z biblioteką OpenGL. Omówimy w niej współrzędne OpenGL, pojęcie kame ry oraz podstawy interfejsów API rysowania.
378
Android 2. Tworzenie aplikacji
Trzeci podrozdział jest poświęcony sposobom interakcji z interfejsem API OpenGL w An droidzie. Opisujemy tutaj interfejsy GLSurfaceView oraz Renderer, a także w jaki sposób współpracują ze sobą podczas procesu rysowania. Zaprezentujemy kilka prostych przykła dów, w których rysujemy trójkąt oraz pokazujemy, jak na proces rysowania wpływa zmiana interfejsów API konfiguracji sceny. -I
- :ojęcie kamery w bibliotece OpenGLjest podobne, lecz nie identyczne z pojęciem
-PT''S- klasy Carne ra z pakietu graficznego Androida, o którym była mowa w rozdziale 6.
Podczas gdy obiekt Carne ra symuluje wyświetlanie perspektywy trójwymiarowej poprzez rzutowanie dwuwymiarowego widoku poruszającego się po trójwymiarowej przestrzeni, kamera biblioteki OpenGLjest paradygmatem reprezentującym wirtualny punkt widzenia. Innymi słowy, ukazuje ona rzeczywistą scenerię, widzianą oczami obserwatora patrzącego przez obiektyw kamery. Więcej informacji znajduje się w ustępie „Kamera i współrzędne" w podrozdziale „P od stawy struktury OpenGL". Obydwa obiekty kamery są niezwiązane z fizyczną kamerą urządzenia podręcznego, dzięki której wykonujemy zdjęcia i rejestrujemy filmy.
W czwartym podrozdziale zajmiemy się nieco bardziej zaawansowanymi kwestiami biblio teki OpenGL i v.rprowadzimy pojęcie kształtów. Opiszemy również tekstury, a także zapre zentujemy sposób rysowania wielu figur geometrycznych za pomocą jednej metody d raw.
Rozdział zamkniemy listą zasobów, których używaliśmy w trakcie opracowywania mate riału do tego rozdziału. Zatem przyjrzyjmy się historii i podstawom biblioteki OpenGL.
Historia i podstawy bibl ioteki OpenGL Biblioteka OpenGL (jej pierwotna nazwa to Open Graphics LibraIJ', czyli otwarta biblioteka graficzna) jest dwu- oraz trójwymiarowym interfejsem graficznym, zaprojektowanym przez firmę Silicon Graphics, Inc. (SGI) dla produkowanych przez nią stacji roboczych. Chociaż stworzone przez firmę SGI wersje bibliotek.i OpenGL istnieją już od długiego czasu, pierwsza ustandaryzowana specyfikacja tej technologii pojawiła się w 1992 roku. Obecnie standard OpenGL został przystosowany do wszystkich systemów operacyjnych i jest szeroko stoso wany w procesie pisania gier, projektowania CAD (ang. Computer Aided Design - projek towanie wspomagane komputerowo), a nawet tworzenia wirtualnych rzeczywistości. Standard OpenGL jest obecnie zarządzany przez konsorcjum nazwane grupą Khronos ( http://www. khronos. org) , założone w 2000 roku przez takie firmy, jak NVIDIA, Sun Micro systems, ATI Technologies oraz SGI. Informacje dotyczące specyfikacji technologii OpenGL można znaleźć na stronie konsorcjum:
http://www.khronos.org/openg// Poniżej zamieściliśmy oficjalną stronę dokumentacji biblioteki Open GL:
http://www.opengl.org!documentation/ Po wejściu na wymienioną powyżej stronę uzyskujemy dostęp do podręczników oraz zaso bów internetowych dotyczących biblioteki OpenGL. Spośród nich klasyczną pozycją jest książka OpenGL Programming Guide: The Officia/ Guide to Learning OpenGL, Version 1 . 1 , znana także jako „czerwona księga" technologii OpenGL. Jest ona dostępna pod adresem:
http://www.glprogramming.com/red!
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
379
Podręcznik ten jest całkiem dobrze i przystępnie napisany. Mieliśmy jednak pewne proble my z rozszyfrowaniem natury jednostek i współrzędnych, vvykorzystywanych w procesie ry sowania. Spróbujemy vvyjaśnić te ważne pojęcia na podstawie sporządzanych przez nas i widzianych na ekranie obiektów za pomocą standardu OpenGL. Pojęcia te dotyczą konfi gurowania kamery OpenGL i definiowania bryły widzenia (ang. viewing box), zwanej także pojemnościq. widzenia (ang. viewing volume) lub ostroslupem widzenia (ang. frustum). Skoro zajmujemy się tematem biblioteki OpenGL, powinniśmy wspomnieć także o techno logii Direct3D, stanowiącej część interfejsu DirectX firmy Microsoft. Jest bardzo prawdo podobne, że Direct3D stanie się standardem w urządzeniach mobilnych, obsługiwanych przez system Windows. Co więcej, z powodu podobieństwa pomiędzy technologiami OpenGL i Direct3D można czytać nawet książki poświęcone temu drugiemu standardowi w celu zrozumienia procesów tworzenia grafiki trójwymiarowej. Utworzony przez firmę Microsoft w 1996 roku standard Direct3D wykorzystuje interfejsy COM (ang. Component Object Model model komponentów obiekto\vych). W systemach Windows interfejsy COM są używane do komunikacji pomiędzy różnymi składnikami da nej aplikacji. Po zaprojektowaniu i odsłonięciu składnika poprzez interfejs COM uzyskuje do niego dostęp każdy język projektowania platformy Windows, zarówno z wnętrza danej aplikacji, jak i spoza niej. W świecie systemów Unix rolę interfejsu COM spełnia architektu ra CORBA (ang. Common Object Req uest Broker Architecture wspólna architektura po średnika żądań obiektów). -
-
Z drugiej strony biblioteka OpenGL wykorzystuje powiązania z językiem, przypominające ich odpowiedniki w języku C. Takie powiązanie pozwala na używanie wspólnej biblioteki przez wiele różnych języków, takich jak C, C++, Visual Basic, Java i tak dalej. Przyjrzyjmy się teraz technologii OpenGL ES, zmodyfikowanej wersji standardu OpenGL, przystosowanej do urządzeń mobilnych.
OpenGL ES Grupa Khronos jest również odpowiedzialna za dwa dodatkowe standardy powiązane z technologią OpenGL: interfejs OpenGL ES oraz interfejs graficzny platformy natywnej EGL (zwany w skrócie interfejsem EGL). Jak już wspomnieliśmy, interfejs OpenGL ES jest mniejszą wersją standardu OpenGL, przeznaczoną dla systemów wbudowanych.
"''Tf'T" 1111111
Proces JCP (ang. Java Community Process) również projektuje abstrakcję obiektową standardu OpenGL dla urządzeń mobilnych, nazwaną interfejsem M3G (ang. Mobile 30 Graphics mobilna grafika trójwymiarowa). Omówimy krótko ten interfejs w podrozdziale „M3G: inny standard grafiki trójwymiarowej środowiska Java". -
Zasadniczo standard EGL jest interfejsem łączącym system operacyjny z interfejsami rende rującymi, dostępnymi w środowisku OpenGL ES. Ponieważ standardy OpenGL i OpenGL ES są ogólnymi interfejsami służącymi do rysowania, każdy system operacyjny musi im za pewnić standardowe środowisko bazowe, umożliwiające interakcję. Od wersji 1 .5 środowi ska Android SDK informacje dotyczące tych parametrów platformy są całkiem skutecznie ukrywane. Zajmiemy się tym dokładniej w podrozdziale „Tworzenie interfejsu pomiędzy standardem OpenGL ES a Androidem".
380
Android 2. Tworzenie aplikacji
Docelowymi urządzeniami standardu OpenGL ES są telefony komórkowe, sprzęt RTV, a nawet pojazdy. Ponieważ musi być on o wiele mniejszy od podstawowej wersji biblioteki OpenGL, zostało usuniętych wiele przydatnych funkcji. Na przykład nie ma funkcji bezpo średniego rysowania prostokątów; należy w tym celu narysować dwa trójkąty. Podczas nauki obsługi biblioteki OpenGL w Androidzie powinniśmy koncentrować się przede wszystkim na interfejsie OpenGL ES i jego powiązaniach z systemem poprzez języki Java oraz EGL. Dokumentację dla interfejsu OpenGL ES (spis treści) można znaleźć tutaj: http :I/www. khronos. otg!opengIes/documen tarion/openg!es I_O/h tm!/index. htm I Podczas pisania tego rozdziału bez przerwy powracaliśmy do tego źródła, ponieważ są w nim wymienione i opisane wszystkie interfejsy API OpenGL ES oraz ich argumenty. Interfejsy te przypominają interfejsy API środowiska Java, a w tym rozdziale omówimy najważniejsze z nich.
Środowisko OpenGL ES a Java ME Podobnie jak biblioteka OpenGL, tak i środowisko OpenGL ES jest płaskim interfejsem opartym na języku C. Ponieważ zestaw Android SDK jest interfejsem programowania ba zującym na języku Java, wymagane jest powiązanie języka Java z interfejsem Open GL ES. W przypadku środowiska Java ME takie powiązanie zostało już zdefiniowane w specyfikacji JSR 239: Java Binding for the OpenGL ES API. Sama specyfikacja JSR 239 opiera się na spe cyfikacji JSR 231, stanowiącej powiązanie języka Java z biblioteką Open GL 1.5. Specyfikacja JSR 239 mogłaby zostać podzbiorem specyfikacji JSR 231, nie wchodzi to jednak w grę, po nieważ musi ona zaadaptować pewne rozszerzenia interfejsu Open GL ES, których nie ma w bibliotece OpenGL 1.5. Dokumentacja specyfikacji JSR 239 jest dostępna tutaj:
h ttp:llja va.s un. comijavamelreferencelap isljsr23 91 Informacje zawarte pod powyższym adresem dadzą nam pojęcie na temat interfejsów API dostępnych w bibliotece OpenGL ES. Znaleźć tam można również cenne informacje doty czące następujących pakietów: • javax.microedition.khronos.egl • javax.microedition.khronos.opengles • java.nio
Pakiet nio jest niezbędny, ponieważ implementacje środowiska OpenGL ES przyjmują dane wejściowe jedynie w postaci strumieni bajtów, aby zachować wysoką wydajność. W pakiecie tym jest zdefiniowanych wiele narzędzi służących do przygotowania buforów natywnych pod kątem korzystania z nich przez bibliotekę Open GL. Niektóre z tych interfejsów API zo staną zastosowane w paragrafie „g!VertexPointer i określanie wierzchołków rysowania" podrozdziału „Korzystanie ze środowiska OpenGL ES". Pod poni:lszym adresem można znaleźć dokumentację (co prawda bardzo ubogą) dotyczącą obsługi biblioteki Open GL w środowisku Android:
http:lldeveloper.android.comlguideltopicslgraphicslopengl.html Pod tym adresem można znaleźć informację wskazującą na fakt, że implementacja tej bi blioteki w Androidzie pokrywa się w większości ze specyfikacją JSR 239, jednak w kilku miejscach mogą pojawić się odstępstwa.
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
381
M3G: inny standard grafiki trójwymiarowej środowiska Java Specyfikacja JSR 239 jest jedynie powiązaniem języka Java z natywnym standardem OpenGL ES. Wspomnieliśmy pobieżnie w paragrafie „OpenGL ES", że środowisko Java posiada inny in terfejs API obsługujący trójwymiarową grafikę w urządzeniach mobilnych: M3G. Ten standard obiektowy jest zdefiniowany w specyfikacjach JST 1 84 i nowszej wersji JSR 297. W przypadku specyfikacji JSR 184 technologia M3G stanowi uniwersalny, obiektowy, interaktywny in terfejs grafiki trójvvymiarowej dla urządzeń mobilnych. Obiektowa natura interfejsu M3G izoluje go od środowiska OpenGL ES. Ze szczegółami można się zapoznać na stronie specyfikacji JSR 184:
http://www.jcp.org/en!jsr/detail?id=l 84 Interfejsy API dla środowiska M3G dostępne są w pakiecie Java:
javax. microedition.m3g. *; W środowisku M3G zaimplementowany został interfejs API vvyższego poziomu w stosunku do środowiska OpenGL ES, więc jego nauka powinna przebiegać łatwiej. Jednak ciężko na razie ocenić, jak ta technologia będzie się sprawowała na handheldach. 1 a razie Android nie obsługuje technologii M3G. Dotychczas wymieniliśmy opcje dostępne w przestrzeni OpenGL pod kątem urządzeń przenośnych. Omówiliśmy środowisko OpenGL ES i wspomnieliśmy o standardzie M3G. Przejdźmy teraz do zapoznania się z podstawami biblioteki OpenGL ES.
Podstawy struktury OpenGL W tym ustępie pomożemy zrozumieć pojęcia kryjące się w interfejsach API OpenGL i OpenGL ES. Omówimy wszystkie zasadnicze interfejsy API. W razie potrzeby na końcu rozdziału znajduje się podrozdział „Zasoby środowiska OpenGL", w którym zamieszczona została li sta źródeł zawierających dodatkowe informacje. Wśród tych źródeł znajdują się odnośniki do czerwonej księgi, dokumentacji specyfikacji JSR 239 oraz interfejsów API grupy Khronos.
9 1!l W' R 1 • ,111 „ ilii ,• ·w ·
Podczas używania zasobów OpenGL zauważa się, że w wersji OpenGL ES bra kuj e części interfejsów API. W takich momentach przydaje się podręcznik referen cyjny środowiska OpenGL ES (ang. OpenGL ES ReferenceManuaf) grupy Khronos.
Szczegółowo omówimy wymienione poniżej interfejsy API, ponie;vaż są one niezbędne do zrozumienia działania bibliotek OpenGL i OpenGL ES: •
glVertexPointer
•
glD rawEleme n t s
•
glColor
• • • •
glClea r g l u l o o kAt g l F r u s t um glViewport
382
Android 2. Tworzenie aplikacji
Podczas omawiania tych interfejsów nauczymy się: • Wykorzystywać podstawowe interfejsy API rysowania za pomocą środowiska OpenGL ES. • Czyścić paletę. •
Określać kolory.
•
Używać współrzędnych i kamery biblioteki OpenGL.
Podstawy rysowania za pomocą biblioteki OpenGL W środowisku OpenGL rysujemy w trójwymiarowej przestrzeni. Rozpoczynamy od okre ślenia szeregu punktów zwanych wierzchołkami. Każdy punkt posiada trzy wartości: pierwsza odpowiada współrzędnej x, druga współrzędnej y, a trzecia - współrzędnej z. Punkty te są ze sobą łączone w celu uzyskania-kształtu. Za ich pomocą można uzyskać w środowisku OpenGL ES różne proste kształty, na przykład kropki, linie i trójkąty. Zwróćmy uwagę, ie do prostych kształtów zaliczane są również prostokąty i wielokąty. Po pewnym czasie pracy z bibliotekami OpenGL i OpenGL ES zauważymy, że ta druga pozycja posiada mniej funkcji niż pozycja pien�sza. Inny przykład: biblioteka OpenGL pozwala na oddzielne określanie każdego punktu, natomiast w bibliotece Open GL ES możliwe jest jedynie określa nie zbioru punktów za jednym zamachem. Jednak często możemy symulować brakujące opcje poprzez korzystanie z innych, prostszych funkcji. Na przykład możemy utworzyć pro stokąt poprzez złożenie dwóch trójkątów. Biblioteka OpenGL ES zawiera dwie podstawowe metody upraszczające proces rysowania: • glVertexPointer • glD rawElements
"'arrr"
W przypadku biblioteki OpenGL ES używamy zamiennie pojęć „interfejs API" i „metoda".
Metoda glVertexPointer używana jest do określania zbioru punktów lub wierzchołków, a dzięki interfejsowi glDrawElements są one rysowane za pomocą jednego z wymienionych wcześniej prostych kształtów. Opiszemy te metody dokładniej, ustalmy j ednak najpierw nomenklaturę nazewnictwa stosowaną w przypadku biblioteki OpenGL. Wszystkie nazwy interfejsów OpenGL rozpoczynają się od przedrostka gł. Po nim występuje nazwa metody. Po jej nazwie może pojawić się cyfra, na przykład 3, wskazująca albo na liczbę wymiarów (x, y, z), albo na ilość argumentów. Kolejnym segmentem nazwy jest litera sym bolizująca typ danych, na przykład litera f reprezentuje dane typu float (w internetowych zasobach dotyczących biblioteki OpenGL można znaleźć różne typy danych i odpowiadają ce im symbole literowe). Jest jeszcze jedna konwencja. Jeżeli metoda przyjmuje argumenty posiadające typ danych byte (b) lub float (f), to będzie posiadała dwie nazwy: jedna będzie kończyła się literą b, a druga - literą f. Przyjrzyjmy się teraz każdej z metod odpowiedzialnych za rysowanie, począwszy od inter fejsu glVertexPointer.
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
383
glVertexPointer i określanie wierzchołków rysowania Metoda glVertexPoinler jest odpowiedzialna za zdefiniowanie tablicy rysowanych punktów. Każdy punkt jest umiejscowiony w trzech wymiarach przestrzeni, zatem będzie posiadał trzy wartości: x, y i z. W listingu 1 0 .l pokazano tablicę ze zdefiniowanymi trzema punktami. Listing 10.1. Przykładowe współrzędne wierzchołków trójkąta utworzonego dzięki bibliotece OpenGL floa t [ ] coords = { - 0 . Sf , - 0 . S f , O , 0 . S f , - 0 . Sf , O , O . Of, 0 . Sf , O };
I/pl: (xl,yl,zl) llp2: (xl,yl,zl) llp3: (xl,yl,zl)
Struktura przedstawiona w listingu 10.1 jest ciągłym zbiorem liczb zmiennoprzecinkowych, przechowywanych w macierzy opartej na języku Java. Nie przejmujmy się na razie pisaniem lub kompilowaniem tego kodu - obecnie naszym celem jest wyjaśnienie zasady działania tych metod. Po zaprojektowaniu środowiska testowego służącego do rysowania prostych kształtów pokażemy kilka działających przykładów. Możemy się zastanawiać, jakie jednostki zostały użyte w listingu 1 0.l dla współrzędnych punktów pl, p2 i p3. Odpowiedź jest krótka: podczas modelowania przestrzeni trójwymia rowej te jednostki współrzędnych mogą być dowolne. Później jednak trzeba zdefiniować obiekt noszący nazwę b1yły okalającej .(ang. bounding box) lub objętości okalającej (ang. boun ding volume), dzięki któremu te jednostki zostają ilościowo wyrażone. Na przykład możemy określić bryłę okalającą jako sześcian o boku 5 cali lub 2 cale. Te współrzędne noszą również nazwę współrzędnych świata (ang. world coordinates), ponieważ określamy dzięki nim świat niezależny od fizycznych ograniczeń urządzenia. Współrzędne świata objaśnimy dokładniej w paragrafie „Kamera i współrzędne". Na razie załóżmy, że ko rzystamy z sześcianu o długości boku 2 cale, którego środkiem są współrzędne (x=O, y=O, z=O).
111111
"''lfSP"
Terminy objętość okalająca, bryła okalająca, objętość widzenia, bryła widzenia oraz ostrosłup widzenia dotyczą tego samego pojęcia: trójwymiarowej objętości w kształcie piramidy, dzięki której określamy, co jest widoczne na ekranie. Więcej informacji na ten temat można znaleźć w ustępie „glFrustum i objętość widzenia" podrozdziału „Kamera i współrzędne".
Możemy również założyć, że początek układu współrzędnych znajduje się pośrodku wy świetlanego obrazu. Oś z posiada wartości ujemne w kierunku dalszego planu (oddala się od nas), a dodatnie w stronę pierwszego planu (zbliża się w naszym kierunku). Wartości osi x są dodatnie w kierunku prawym, a ujemne w kierunku lewym. Jednak te współrzędne zależą również od kierunku, z jakiego widzimy scenę. Aby narysować punkty widoczne w listingu 10. l, musimy przekazać je bibliotece OpenGL ES poprzez metodę glVertexPointer. Jednak w celu zachowania \vydajności metoda glVertex Pointer przyjmuje natywny b u for niezajeżny od języka programowania, a nie macierz wartości zmiennoprzecinkowych. W tym celu musimy przekonwertować macierz języka Java na akceptowany bufor natywny, przypominający strukturę języka C. Dokonujemy tego za pomocą klas java.nio. W listingu 10.2 zaprezentowany został przykład wykorzystania buforów nio.
Android 2. Tworzenie aplikacji
384
Listing 1 0.2. Tworzenie zmiennoprzecinkowych buforów NIO j va . nio . ByteBuf f e r vbb j ava . nio . ByteBuffe r . allocateDirect ( 3 vbb . o rder( ByteOrde r . nativeOrde r ( ) ) ; j ava nio FloatBuffer mFVertexBuffer vbb . asFloatBuffer ( ) ; =
.
.
*
3
*
4);
=
W listingu 10.2 obiekt Byt eBu f fe r jest buforem pamięci zdefiniowanym w bajtach. Każdy punkt posiada trzy wartości zmiennoprzecinkowe z powodu umiejscowienia go w trójwy miarowym układzie współrzędnych, natomiast każda wartość zmiennoprzecinkowa jest czterobajtowa. Zatem na każdy punkt przypada 4·3 bajty. Ponadto trójkąt posiada trzy punkty. Potrzebujemy więc 3 . 3 .4 bajtów do przechowania wszystkich trzech zmiennoprze cinkowych wierzchołków trójkąta. Po umieszczeniu punktów w buforze natywnym możemy wywołać metodę glVertexPointer zgodnie z kodem pokazanym w listingu 10.3. Listing 1 0.3. Definicja interfejsu API g lVertexPointer g l Verte x Point e r (
li Ilu współrzędnych używamy dla każdego punktu
3, li każda wartość jest zmiennoprzecinkowa
w
buforze
GLlO . GL_ FLOAT , li Nie ma przestrzeni pomiędzy dwoma punktami
O, li wskaźnik poczqtku bufora
mFVertexBuffe r ) ;
Zatrzymajmy się na chwilę przy argumentach metody glVertexPointer. Pierwszy argu ment wskazuje bibliotece OpenGL ES, ile wymiarów przypada na punkt lub wierzchołek. Vl na szym przypadku podajemy wartość 3 dla współrzędnych x, y i z. Istnieje również możliwość wpisania wartości 2 dla wymiarów x i y. Wtedy parametr z przyjmuje wartość O. Pamiętaj my, że pierwszym argumentem jest nie ilość punktów w buforze, lecz ilość wykorzystywa nych wymiarów. Zatem jeżeli chcemy przekazać 20 punktów do narysowania większej ilości trójkątów, nie wpisujemy wartości 20 w pierwszym argumencie; umieszczamy w nim war tość 2 lub 3, w zależności od ilości użyvvanych wymiarów. Drugi argument określa, że współrzędne muszą być interpretowane jako liczby zmienno przecinkowe. Trzeci argument, noszący nazwę st ride, wskazuje ilość bajtów oddzielają cych każdy punkt. W naszym przypadku podajemy wartość o, ponieważ jeden punkt zostaje umieszczony tuż przy drugim. Czasami możemy dodawać atrybuty kolorów jako część bufora po każdym punkcie. W tym celu używamy atrybutu st ride do omijania punktów stanowią cych część specyfikacji wierzchołków. Ostatnim argumentem jest wskaźnik bufora zawierającego punkty. Teraz już wiemy, w jaki sposób konfigurować tablicę rysowanych punktów, zatem dowiedzmy się, co należy zrobić, aby narysować te punkty za pomocą metody glD ra1�Elements.
Rozdział 1 0 • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
385
g lDrawElements Po określeniu zbioru punktów za pomocą metody gl Ve rtexPoi n t e r stosujemy metodę g l D rawElements do narysowania tych punktów w postaci jednego z prostych kształtów dopuszczalnych przez bibliotekę OpenGL ES. Odnotujmy fakt, że biblioteka OpenGL jest ma szyną stanową. Zapamiętuje w sposób narastający wartości ustanowione przez jedną metodę podczas wywoływania kolejnej metody. Nie musimy vtięc jawnie przekazywać punktów usta nowionych przez metodę glVertexPointer metodzie glD rawElements. Ta ostatnia będzie z nich korzystała w sposób niejawny. Listing 10.4 ukazuje przykład zastosowania tej metody wraz z dopuszczalnymi argumentami. Listing 1 0.4. Przykład metody g lDrawElements glDrawElement s (
11 rodzaj kształtu GLlO. GL_TRIANGLE STRIP,
li Tlość indeksów 3, li Rozmiar każdego indeksu
G L l O . GL_UNSIGNED_SHORT,
li bufor zawierajqcy trzy indeksy
mindexBuffe r ) ;
Pierwszy argument definiuje typ kształtu geometrycznego, który ma zostać narysowany: GL_ TRIANG LE_ STRI P oznacza pas trójkątów. Pozostałymi wartościami mogą być same punkty (GL_POINTS), pasy linii ( G L_ LINE_ STRI P ) , same linie (GL_LINES), pętle linii ( G L_ LINE_ LOOP), same trójkąty (GL_ TRIANGLE$) lub wachlarze trójkątów ( G L_ TRIANGLE_ FAN ) . Koncepcja pasa (ang. strip) w argumentach G L_ LINE_ STRI P i GL_ TRIANGLE_ STRIP polega na dodawaniu nowych punktów podczas korzystania z punktów starych. W ten sposób może my uniknąć definiowania wszystkich punktów dla każdego nowego obiektu. Na przykład: jeśli określimy cztery punkty w macierzy, możemy wykorzystać pasy do utvmrzenia pierw szego trójkąta z wierzchołków ( 1 , 2, 3), a drugiego z wierzchołków (2, 3, 4). Każdy nowy punkt spowoduje dodanie kolejnego trójkąta (szczegóły można znaleźć w czerwonej księdze biblioteki OpenGL). Możemy również zróżnicować te parametry, aby zobaczyć, w jaki spo sób będą rysowane trójkąty po dodaniu nowych punktów. Koncepcja wachlarza (ang. fan) w argumencie G L_TRIANGLE FAN polega na wykorzystaniu pierwszego punktu jako punktu zaczepienia dla wszystkich trójkątów. Zatem tworzymy w istocie obiekt w kształcie wachlarza lub koła, którego pierwszy wierzchołek znajduje się w środku. Załóżmy, że posiadamy sześć punktów w macierzy: (1, 2, 3, 4, 5, 6). Użycie argu mentu wachlarza spowoduje narysowanie trójkątów o v.rierzchołkach (1, 2, 3), ( l , 3, 4), ( 1 , 4, 5) i (1, 5, 6). Każdy nowy punkt tworzy dodatkovvy trójkąt, podobnie jak to ma miejsce w przypad ku rozwijania wachlarza lub talii kart. Pozostałe argumenty metody glDrawElements służą do określania możliwości ponownego wykorzystania charakterystyki punktu. Na przykład kwadrat składa się z czterech punktów. Każdy kwadrat można narysować jako kombinację dwóch trójkątów. Czy w celu narysowa nia dwóch trójkątów tworzących kwadrat musimy wyznaczyć sześć punktów? Nie. Wystar czy określenie jedynie czterech punktów i sześciokrotne odniesienie się do nich w celu nary sowania dwóch trójkątów. Proces ten nosi nazwę indeksowania do bufora punktu.
386
Android 2. Tworzenie aplikacji
Przykład: Punkty: ( pl , p 2 , p 3 , p4) Rysuj indeksy (pl, p 2 , p 3 ,
p2 , p3 , p4 )
Zauważmy, że pierwszy trójkąt składa się z punktów pl, p2 ,p3, a drugi trójkąt z punktów p2, p3, p4. Dzięki tej wiedzy możemy poprzez drugi argument metody glDrawElements określić ilość indeksów w buforze indeksów. Trzeci argument metody glDrawElements (listing 10.4) wskazuje typ wartości w macierzy indek sów, czy jest to typ unsigned short (GL UNSIGNED SHORT) czy unsigned byte (GL UNSIGNED BYTE) _
_
,
_
_
.
Ostatni argument metody glDrawElements wskazuje bufor indeksu. Aby go wypełnić, mu simy przeprowadzić podobną operację jak w przypadku bufora wierzchołka. Rozpoczynamy od tablicy Java i konwertujemy ją za pomocą pakietu java.nio na bufor natywny. W listingu 10.5 został przedstawiony przykładowy kod służący do konwersji krótkiej tablicy zawierającej elementy { O , 1 , 2} na bufor natywny, nadający się do przekazania go metodzie glD rawElement s. Listing 1 0.5. Konwertowanie tablicy Java do postaci bufora NIO
//Określamy sposób rozmieszczenia punktów short [ ] myindecesArray = {O, l , 2 } ; //uzyskujemy bufor typu short j ava . ni o . ShortBuffer mindexBuffe r ; //Wyznaczamy po 2 bajty dla każdej wartości indeksu ByteBuffer ibb ByteBuffe r . allocateDirect ( 3 * 2 ) ; i b b . orde r ( ByteOrder . nativeOrde r ( ) ) ; mlndexBuffer i b b . asShortBuffer ( ) ; =
=
//umieszczamy go w buforze for ( int i=O ; i< 3 ; i++) { mindexBuffe r . pu t ( mylndecesArray ( i J ) ;
Skoro już wiemy, jak działa metoda mlndexBuf f e r (listing 10.5), możemy cofnąć się do li stingu 1 0.4, aby lepiej zrozumieć ideę tworzenia bufora indeksu i jego przekształcania. Bufor indeksu, zamiast tworzyć nowe punkty, indeksuje jedynie macierz punktów
mwrr= wskazaną przez metodę glVe rt ex Poi n te r. Jest to możliwe, ponieważ biblioteka
OpenGL zapamiętuje za pomocą stanów zasoby ustanowione przez poprzednie wywołania.
Zajmijmy się teraz dwiema powszechnie wykorzystywanymi metodami biblioteki OpenGL: glClear i glColor. Obydwie metody zostaną użyte w przykładowym środo"·isku testowym.
glClear Metodę glClear stosujemy do czyszczenia powierzchni rysowania. Za jej pomocą możemy wyzerować nie tylko kolor, lecz także głębię oraz rodzaj wykorzystywanych szablonów. Element,
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
który ma zostać wyzerowany, określamy poprzez odpowiednią stałą: lub GL_STENCIL B U F FER_BIT.
387
GL COLOR_ BU FFER_BIT,
GL_DEPTH_BUFFER BIT
Bufor koloru jest odpowiedzialny za widoczne na ekranie piksele, zatem jego wyczyszczenie jest równoznaczne wymazaniu wszelkich kolorów z powierzchni. Bufor głębi odnosi się do wszystkich pikseli widzianych w trójwymiarowej scenie, ·w zależności od odległości obiektu od kamery. Bufor szablonowy jest nieco zbyt skomplikowany, aby go tutaj omówić, wystarczy jednak wiedzieć, że jest używany do tworzenia efektów graficznych w oparciu o pewne dynamiczne kryteria, a metoda glClea r umożliwia jego wyczyszczenie.
• •w r.lllilil• ll,r 11 1• 1 • -
Szablon jest obiektem, dzięki któremu możemy wielokrotnie powielać proces rysowania. Na przykład: jeśli używamy aplikacji Microsoft Office Visio, wszystkie obiekty zapisywane jako pliki *.vss są szablonami. W świecie rzeczywistym tworzymy szablon poprzez wycięcie wzoru na papierze lub innej płaskiej powierzchni. Następnie przerysowujemy za pomocą szablonu jego obwiednię na arkuszu, a po zdjęci u szablonu powstaje wrażenie powielenia rysunku.
Dla naszych celów możemy zastosować poniższy kod do wyczyszczenia bufora koloru: //C;;yści powier:::clmię :;e wszelkie/z kolorów g l . glClea r ( g l . GL_COLOR BUFFER_BIT) ;
Zastanówmy się teraz nad sposobem dołączenia koloru do rysowanego obiektu.
glColor Metoda glColo r jest używana do ustawienia domyślnego koloru dla następnego obiektu, który będzie rysowany. W poniższym segmencie kodu metoda g 1 C o l o r4f generuje kolor czerwony: I/Ustanawia bieżący ku/or g1Color4f ( l . O f , O , O , O . S f ) ;
Przypomnijmy sobie nomenklaturę metod: oznaczenie 4f odnosi się do czterech argu mentów, posiadających wartości zmiennoprzecinkowe, pobieranych przez metodę. Cztere ma argumentami są składowe banvy czenvonej, zielonej , niebieskiej oraz współczynnika alfa (gradient koloru). Wartością początkową dla tych argumentów jest ( 1 , l , l , l). W naszym przykładzie wyb ral iśmy kolor czerwony z połową gradientu (określonego przez ostatni ar gument alfa). Chociaż omówiliśmy podstawowe interfejsy API rysowania, musimy jeszcze omówić kilka rzeczy związanych ze współrzędnymi punktów określanych w trój,vymiarowej przestrzeni. Następny ustęp wyjaśnia, w jaki sposób biblioteka OpenGL modeluje rzeczywiste sceny w perspektywie widzenia operatora kamery.
Kamera i współrzędne W procesie rysowania w przestrzeni trójwymiarowej musimy w pewnym momencie rzuto wać trój\vymiarowy widok na dwuwymiarowy ekran - tak samo, jak w przypadku reje strowania trójwymiarowej sceny za pomocą kameq' w świecie rzeczywistym. Taka symbolika
388
Android 2. Tworzenie aplikacji
jest formalnie rozpoznawana w standardzie OpenGL, zatem wiele koncepcji jest w nim wy jaśnianych za pomocą pojęcia kamery. Jak zobaczymy w tym paragrafie, widoczna część obiektu rysowanego zależy od położenia kamery, kierunku ustawienia jej obiektywu, orientacji kamery (może być na przykład po stawiona do góry nogami), poziomu przybliżenia oraz rozmiaru „kliszy". Wymienione aspekty rzutowania obrazu trójwymiarowego na dwuwymiarowy ekran są kontrolowane przez trzy metody: • glulookAt. Kontroluje kierunek obiektywu kamery. • glFrustum. Kontroluje objętość widzenia lub poziom powiększenia. • gl Viewpo rt. Kontroluje rozmiar ekranu lub rozmiar „kliszy". Dopóki nie zrozumiemy znaczenia tych trzech interfejsów API, nie będziemy mogli niczego zaprogramować w standardzie OpenGL. Rozwiniemy dalej symbolikę dotyczącą kamery, aby wyjaśnić, w jaki sposób te trzy metody ·wpływają na obraz widziany na ekranie. Roz poczniemy od metody g l u LookAt.
glulookAt i symbolika kamery Wyobraźmy sobie, że "''Yruszyliśmy na \'\'}'Cieczkę, której celem jest "''Ykonanie zdjęć krajo brazu przedstawiającego kwiaty, drzewa, strumienie i góry. Przybywamy na łąkę; sceneria roztaczająca się przed naszymi oczami jest równoważna temu, co chcemy narysować w standar dzie OpenGL. Możemy rysować obiekty tak duże jak góry lub tak małe jak kwiaty - dopóki zachowamy pomiędzy nimi proporcje. Jak już wcześniej wspomnieliśmy, współrzędne sto sowane w przypadku tych obiektów nazywane są współrzędnymi świata. Za ich pomocą ustanawiamy na osi x linię o długości 4 jednostek poprzez wprowadzenie punktów od ( 1 , O, O) do (4, O, O). Podczas przygotowań do \'\'}'konania zdjęcia znajdujemy miejsce, w którym umieścimy sta tyw. Następnie na statywie umieszczamy aparat. Położenie aparatu - nie statywu, a samego urządzenia - jest punktem początkmrym. Musimy więc wziąć kartkę i zaznaczyć tę lokację, noszącą nazwę punktu ocznego (ang. eye point). Jeżeli nie zdefiniujemy punktu ocznego, kamera będzie znajdować się w punkcie o współrzędnych (O, O, O), który znajduje się do kładnie na środku ekranu. Chcemy przeważnie odsunąć się od początku układu współrzęd nych, aby zobaczyć płaszczyznę (x, y), dla której wartość osi z wynosi O. Załóżmy, że umie ścimy aparat w punkcie (O, O, 5). Zostanie on przesunięty w naszą stronę o 5 jednostek. Na rysunku 10.l została ukazana pozycja aparatu. Po umieszczeniu aparatu musimy sprawdzić, jaki fragment sceny chcemy uch\'\rycić w apa racie. Umieścimy urządzenie w kierunku patrzenia. Ten odległy punkt, na który spoglądamy, nazywany jest punktem widoku (ang. viewing point) lub punktem spoglqdania (ang. look-at point). Określając ten punkt, definiujemy w rzeczywistości kierunek patrzenia. Zatem jeśli nasz punkt widoku będzie posiadał współrzędne (O, O, O), to aparat będzie „spoglądał" z od ległości 5 (przy założeniu, że współrzędne aparatu wynoszą (O, O, 5)) jednostek wzdłuż osi z na początek układu współrzędnych. Zostało to ukazane na rysunku 10.1. Wyobraźmy sobie dalej, że w początku układu współrzędnych został umieszczony prostopa dłościenny budynek. Chcemy zrobić mu zdjęcie nie w pozycji wertykalnej, a w horyzontalnej. Co robimy? Oczywiście nie zmieniamy położenia ani kierunku spoglądania aparatu, musimy go jednak obrócić o 90 stopni. Jest to orientacja aparatu, któremu został \\'}'Znaczony dany punkt widoku, na który „spogląda''. Orientacja taka nosi nazwę wektora góry (ang. up vector).
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
389
Oś Y
------- daleko (7) _ _ __, ..-_._..
Obrót kamery
-2
Oś Z -
I
,
- -
„li(>---- Pozycja kamery (5) ---+ł
�
Rzut
- - -
-
-
-
- - -
�
+ Ostrosłup _.. w1dzen1a
- -
Rysunek 1 0. 1 . Analogia kamery w koncepcji widzenia w standardzie OpenGL
Wektor góry w prosty sposób określa taką orientację aparatu, jak górna, dolna, lewa, prawa lub pod kątem. Orientacja aparatu jest również określana za pomocą punktu. Wyobraźmy sobie linię odchodzącą od środka układu współrzędnych - nie od środka aparatu, a od środka układu współrzędnych świata - do tego punktu. Kąt utworzony pomiędzy osiami a tą linią wyznacza właśnie orientację aparatu. Na przykład wektor góry dla aparatu może przybrać wartość (O, 1, O), a nawet (O, I 5, O), co da ten sam efekt. Punkt (O, 1, O) wskazuje punkt odchodzący od początku układu współ rzędnych po osi y w górę. Oznacza to, że ustawimy aparat w pozycji pionowej. W przypad ku wektora (O, - 1 , O) obrócimy urządzenie do góry nogami. W obydwu przypadkach aparat umieszczony jest ciągle w tym samym punkcie (O, O, 5) i „spogląda" na środek układu współrzędnych (O, O, O). Wymienione współrzędne możemy podsumować w następujący sposób: • (O, O, 5). Punkt oczny (położenie kamery). • (O, O, O). Punkt spoglądania (kierunek, w którym kamera jest zwrócona). • (O, I, O). Wektor góry (orientacja pozioma, pionowa lub nachylona).
Wszystkie trzy punkty - punkt oczny, punkt spoglądania oraz wektor góry - mogą zostać zdefiniowane w metodzie glulookAt w następujący sposób: glulookAt ( g l , 0 , 0 , 5 ,
0,0,0,
o,1,0);
Argumenty występują w kolejności: pierwszy zbiór współrzędnych odpowiada punktowi ocznemu, drugi zestaw współrzędnych należy do punktu spoglądania, natomiast pozostałe trzy współrzędne określają wektor góry w odniesieniu do początku układu współrzędnych. Teraz zajmiemy się objętością widzenia.
Android 2. Tworzenie aplikacji
390
g lfrustum i objętość widzenia Można zauważyć, że żaden z punktów op isujących położenie aparatu za pomocą metody g l u lookAt nie jest odpowiedzialny za rozmiar obrazu. Definiują one jedynie lokalizację, kierunek i orien tacj ę. W jaki sposób aparat ma nastawiać ostrość ? Jak daleko znajduje się obiekt, który staramy się uchwycić? Do określ en ia interesuj ącego nas obszaru sceny wyko rzystujemy metodę glFrustum. Wyobraźmy sobie obszar sceny otoczony przez bryłę, zwaną także ostrosłupem widzenia lub objętościq widzenia (ostrosłup widzenia jest zaznaczony pogrubioną linią w centrum rysun ku 10. l ) . Wszystkie elementy umieszczone wewnątrz b ryły zostaj ą zarejestrowane, a obiekty znajdujące się na zewnątrz zostają wycięte i zignorowane. Zatem w jaki sposób określamy bryłę widzenia? Najpierw wyznaczamy bliski punkt ( ang near point) lub odległość pomię dzy aparatem a początkiem bryły. Następnie zaznaczamy daleki punkt (ang. far point), defi n iujący dystans pomiędzy aparatem a końcem bryły. Odległość pomiędzy punktem bliskim a punktem dalekim wzdłuż osi z stanowi głębię b ryły. Jeżeli zdefiniujemy punkt bliski o wartości 50 i punkt daleki o wartości 200, uchwycim)' wszystkie obiekty znajdujące się pomiędzy tymi punktami, głębia bryły będzie posiadała wartość 150. Będziemy musieli określić również lewą stronę bryły, jej prawą stronę, a także jej górę i dół za pomocą wyima ginowanego promienia, łączącego aparat z punktem spoglądania. .
W bibliotece Open GL możemy sobie wyobrazić tę bryłę na jeden z dwóch sposobów. Jeden z nich nosi nazwę rzutowania perspektywicznego i wykorzystuje omówione przed chwilą pojęcie o strosłu p a widzenia. Widok ten, sym ulujący pracę normalnej kamery, wykorzystuje strukturę piramidową, której podstawę stanowi daleka płaszczyzna, a kamera jest jej szczytem. Płaszczyzna bliska odcina „szczyt" piramidy, co powoduje powstanie ostrosłupa ściętego pom iędzy p ła szczyzn ą bliską a daleką.
Drugi sposób wyobrażenia sobie tej bryły wymaga postrzegania jej ,,. postaci sześcianu. Ten dru gi scenariusz nosi nazwę rzutowania ortograficznego i j est wykorzystywany do rysowania obiek tów geometl)rcznych, które muszą zachować rozmiary bez \\·zględu na odległość od kamer . y
Przyjrzyjmy się w listingu 10.6, w jaki sposób definiujemy ostrosłup widzenia dla naszego przyktadu. Listing 1 0.6. Definiowanie ostrosłupa widzenia za pomocą metody glFrust u m //oblicza najpierw proporcje obrazu float ratio ( f loat) w I h ; //wskazuje na fakt, że wymagamy rzuto111a11ia perspekty111icznego glMat rixMode (GLlO . GL PROJECTION ) ; I/Definiuje ostrosłup 111idzenia: objętość widze11ia g l . glFrustumf( - ratio, li Lewa stro11a lnyły wid:::enia ratio, li prawa strona bryły wid;;enia 1. li s::;c:::yt b1yly wid:::enia 1 li spód b1yły widzenia 3, li odległość przedniej ściany bryły od aparatu 7) ; li odległość tyln;j ściany bryły od aparatu =
-
,
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
391
10.6 przypisaliśmy szczytowi bryły wartość 1, a jej spodniej - 1, wysokość przedniej ściany wynosi 2 jednostki. Rozmiary lewej i prawej
Ponieważ w kodzie z listingu ścianie wartość
strony ostrosłupa określamy za pomocą proporcjonalnych liczb, biorąc pod uwagę propor cję obrazu. Z tego właśnie powodu kod wykorzystuje wysokość i szerokość okna do okre ślenia proporcji. Zakłada on również, że obszar działania będzie znajdował się pomiędzy a
7. jednostką wzdłuż osi
z.
3.
Wszystkie obiekty narysowane poza tymi współrzędnymi,
względnymi wobec aparatu, będą niewidoczne. Ponieważ umieściliśmy aparat w punkcie (O,
O, 5) i
skierowaliśmy go w stronę punktu
(O, O, O),
trzy jednostki od aparatu w stronę początku układu współrzędnych znajdują się w punkcie
(O, O, 2), a siedem jednostek od kamery znajduje się punkt (O, O, -2). W ten sposób płaszczyzna początku układu współrzędnych znajduje się dokładnie pośrodku trójwymiarowej
bryły.
Teraz już wiemy, jak wielka jest nasza objętość widzenia. Musimy poznać jeszcze jeden in terfej s API, aby odwzorować te rozmiary na ekranie: gl Viewport.
g lViewport i rozmiar ekranu Metoda
glViewport jest odpowiedzialna za zdefiniowanie prostokątnego obszaru ekranu,
na który będzie rzutowana objętość widzenia. Przyjmuje ona cztery argumenty określaj ące prostokątne pole: współrzędne W listingu
x
i y lewego dolnego rogu figury oraz szerokość i wysokość.
10.7 zaprezentowano przykład określenia widoku jako celu rzutowania.
Listing 1 0.7. Definiowanie wziernika za pomocą metody glViewport glViewport ( O ,
o.
\�idt h .
height ) ;
li współrzędna x lewej dolnej krawędzi prostokąta li wspólrzrdna y lewej dolnej krawędzi prostokąta li szerokość prostokąta na ekranie li wysokość prostokąta na ekranie
100 pikseli, a wysokość ostrosłupa 10 pikseli, to każda jednostka logiczna współrzędnych zostanie przetłuma
Jeżeli rozmiar naszego okna lub widoku ma wysokość widzenia wynosi czona na
1 0 pikseli współrzędnych świata.
Dotychczas omóv.ri.liśmy niektóre istotne pojęcia wstępne dotyczące grafiki OpenGL - mate riał, który w specjalistycznych pozycjach zajmuje wiele rozdziałów. Zrozumienie tych pod staw przyda się do nauki programowania za pomocą biblioteki OpenGL. Po spełnieniu tego warunku możemy przejść teraz do omówienia elementów wymaganych do wywołania opi sanych powyżej interfej sów API.
Tworzen ie interfejsu pomiędzy standardem OpenGL ES a Androidem Jak już zdążyliśmy wspomnień, standard OpenGL platform.
U
ES jest
obsługiwany na wielu rodzajach
jego podstaw znajduj e się, przypominający strukturę języka
C,
interfejs API,
który zajmuje się wszystkimi obowiązkami rysowania. Jednak platformy i systemy operacyjne różnią się między sobą pod względem implementacji wyświetlania, buforów ekranu i tym po dobnych elementów. Te specyficzne dla każdego systemu operacyjnego aspekty są przetwarza ne i dokumentowane przez te systemy. Android nie jest pod tym względem wyjątkiem.
392
Android 2. Tworzenie aplikacji
Począwszy od wersji 1.5 środowiska SDK, procesy interakcji oraz inicjacji rysowania w stan dardzie OpenGL zostały uproszczone w Androidzie. Jest to możliwe dzięki pakietowi android.opengl. Główną klasą dostarczającą wiele funkcji jest GLSu rfaceView. Klasa ta po siada wewnętrzny interfejs GLSu rfaceView. Ren de re r. Znajomość tych dwóch elementów wystarczy do poczynienia znacznych postępów na polu programowania w standardzie OpenGL w Androidzie. Innymi klasami tego pakietu są: • GLU. Klasa ta zawiera narzędzia otaczające interfejs API Open GL ES w celu zebrania części wspólnych funkcji. Jednym z podstawowych interfejsów API klasy GLU jest omówiona powyżej metoda gluLookAt. W dokumentacji poświęconej interfejsom
API OpenGL środowiska SDK można znaleźć informacje dotyczące podobnych narzędzi. •
GLUtils. Ta klasa zawiera narzędzia systemu Android, które mają na celu ułatwienie interakcji z biblioteką OpenGL ES. Podstawową metodą tej klasy jest interfejs teximage2D, udostępniający mapy bitowe w środowisku Open GL ES jako tekstury.
•
M a t r i x . Jest to macierz transformacji, niezbędna do przeprowadzania takich przekształceń, jak skalowanie, przesuwanie i tak dalej.
•
Vis i bili ty. Kolejna klasa, której nie używaliśmy w tym rozdziale. Zajmuje się ona aspektami widoczności obiektów Open GL, na przykład tym, które siatki trójkątów mają być widoczne na ekranie.
• GLDebugHelper. Statyczna klasa umożliwiająca otaczanie interfejsów „GL" i „EGL",
dzięki czemu można wprowadzić obsługę dziennika, wyświetlania błędów, dodatkowych testów i tak dalej. Dzięki tym pakietom stają się dostępne następujące interfejsy: • G L S u rf aceView. Ren d e re r. Interfejs ten pozwala pochodnym klasom rysować obiekty. Za jego pomocą klasa G LS u rf a c eView wywołuje rysowanie podczas zmiany
powierzchni i tak dalej. Jest to podstawowy interfejs, na którym pracują programiści. Jest to przykład sposobu, w jaki Android próbuje oddzielić rzeczywisty proces rysowania za pomocą biblioteki Open GL od procesu jej konfiguracji (jest to charakterystyczne dla Androida). • G L Su rfaceView. EGLConfigChooser. Interfejs ten umożliwia klasie GL Su rfaceView wybranie właściwego obiektu EGLConfig, uruchamiającego środowiska OpenGL. Obiekt Config definiuje typ wyświetlacza. W środowiskach SDK starszych od wersji 1.5 musimy zarząd7.ać samodzielnie tymi klasami. W środowiskach SDK począwszy od wersji 1 .5 wartości domyślne są automatycznie konfigurowane i nie musimy ich
jawnie definiować. • GLSurfaceView. GLWrapper. Ten interfejs umożliwia otoczenie interfejsu typu „gl",
dzięki czemu możemy otrzymywać v.rywołania biblioteki OpenGL, w poprzek całego systemu.
Stosowanie klasy GLSurfaceView i klas pokrewnych Począwszy od wersji 1.5 środowiska SDK, powszechny wzorzec stosowania biblioteki OpenGL został znacznie uproszczony. Podczas rysowania za pomocą klas biblioteki Open GL sto sujemy zazwyczaj następujący algorytm:
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
393
1. Zaimplementuj interfejs Renderer. 2. Skonfiguruj w implementacji interfejsu Ren de re r ustawienia obiektu Carne ra. 3. Wprowadź w implementacji, do metody o n D rawFrame, kod odpowiedzialny za rysowanie.
4. Skonstruuj obiekt GLSu rfaceView. 5. Skonfiguruj silnik renderujący zaimplementowany w klasie GLSu rfaceView. 6. Skonfiguruj kontrolkę GLSu rfaceView '"' aktywności jako widok treści. Możemy również używać tego widoku wszędzie tam, gdzie korzystamy ze zwykłego widoku. W listingu 10.8 znajduje się typowa aktywność, korzystająca z niektórych etapów wymie nionych powyżej. Listing 10.8. Proste środowisko testowe biblioteki OpenGL nazwane OpenGLTestHarnessActivity ,
public class OpenGLTestHarnes sActivity extends Activity { private GLSu rfaceView mTestHa rne s s ; @Over ride protected void onCreate(Bundle savedlnstanceState) s up e r . o n C reate ( s avedinstanceStat e ) ; mTestHarness = new GLSu rfaceView ( this ) ; mTestHa rness . setEGLConfigChoose r ( false ) ; mTestHa rness . setRendere r ( new SimpleTriangleRende rer ( th is ) ) ; mTestHarness . setRende rMode(GLSurfaceView. RENDERMODE_WHEN_DIRTY) ; //mTestHarness.setRenderMode(GLSurfaceView. RENDEJUvfODE_ CONTINUOUSL Y); setContentView(mTestHarnes s ) ; } @Over ride protected void onResume ( ) { supe r . onResume ( ) ; mTestHarness . onResume ( ) ; @Over ride protected void onPause ( ) { supe r . onPause ( ) ; mTestHarnes s . onPause ( ) ; } Wyjaśnijmy kilka kluczO\vych elementów tego kodu źródłowego. Fragment odpowiedzialny za utworzenie widoku GLSu rfaceView wygląda następująco:
mTestHarness = new GLSurfaceView(this ) ; Następnie informujemy ten widok, że nie potrzebujemy możliwości wyboru specjalnej konfigu racji biblioteki EDL i wystarczą nam ustawienia domyślne:
mTestHarness . setEGLConfigChooser( false ) ; Kolejnym etapem jest skonfigurowanie silnika renderowania:
mTestHarnes s . setRendere r ( new SimpleTriangleRende re r ( this ) ) ; Nie omawialiśmy jeszcze klasy SimpleTriangleRenderer, ale jest to wystąpienie interfejsu Renderer pozwalającego na rysowanie prostego trójkąta (wkrótce zajmiemy się tym tematem).
394
Android 2. Tworzenie aplikacji
Jedna z dwóch metod umieszczonych w dalszej części kodu odpowiedzialna jest za umożli wianie procesu animacji: mTestHa rness . setRenderMode (GLSu rfaceView . RENDERMODE_WHEN_DIRTY ) ; //mTestHa rness . setRenderMode (GLSu rfaceView. RENDERMODE_CONTINUOUSLY ) ;
W przypadku wybrania pierwszej linijki proces rysowania zostanie wywołany tylko raz, a ściślej mówiąc, za każdym razem, gdy zaistnieje konieczność jego \"')'korzystania. Wybór drugiej opcji spowoduje, że kod rysowania będzie wywoływany nieprzerwanie, co można "")'korzystać do animowania obiektów rysowanych. To tyle na temat korzystania z interfejsów biblioteki OpenGL w Androidzie. Omówimy te raz klasę SimpleTriangleRender oraz wprowadzimy proste środowisko testowe pozwalają ce na przetestowanie tej klasy.
Proste środowisko testowe rysujące trójkąt Jak już wcześniej stwierdziliśmy, proces rysowania v.rymaga zaimplementowania interfejsu Ren d e re r. Sygnatura tego interfejsu została ukazana w listingu 10.9. Listing 10.9. Interfejs Renderer public static interface GLSu rfaceView . Renderer { void onD rawFrame (GLlO g l ) ; void onSuraceChanged(GLlO g l , int width, int height ) ; void onSurfaceC reated (GLlO g l , EGLConfig config ) ; } Główny proces rysowania przebiega w metodzie ono rawF rame ( ) . Za każdym razem, gdy jest tworzona nowa powierzchnia dla tego widoku, zostaje wywołana metoda on S u rfaceC rea t ed ( ) i możemy wywołać wiele interfejsów API biblioteki OpenGL, takich jak roztrząsanie (ang. dithering), kontrola głębi, oraz inne, które można wywołać bezpośrednio spoza metody onD rawFrame( ) .
Analogicznie w przypadku zmiany powierzchni, n a przykład szerokości i wysokości okna, zostaje wywołana metoda on Su rfaceChanged ( ) . Możemy dzięki niej konfigurować kamerę oraz objętość widzenia. Nawet w metodzie onD rawF rame ( ) istnieje wiele elementów, które mogą być wspólne dla określonego kontekstu rysowania. Możemy "")'korzystać tę powszedniość i umieścić te metody na kolejnym poziomie abstrakcji, zwanym AbstractRenderer, zawierającym tylko jedną nie zaimplementowaną metodę d raw ( ) . W listingu l 0.10 został zaprezentowany kod obiektu Abst ractRende re r. Listing 10.1 O. Obiekt AbstractRenderer //nazwa pliku: AbstractRenderer.java public a b s t ract class Abst ractRenderer implements GLSurfaceView . Renderer { public void onSu rfaceCreated (GLlO g l , EGLConfig eglConfig) { g l . glDisable(GLlO. GL_DITHER ) ;
Rozdział 1 0 • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
395
g l . glHint ( GLlO . GL_ PERSPECTIVE_CORRECTION_HINT , GLlO. GL_ FASTEST ) ; g l . glClea rColo r ( . S f , . Sf , . Sf , l ) ; g l . glShadeModel ( GLlO . GL_SMOOTH } ; gl . glEnable ( GLlO . GL_DEPTH_TEST) ;
} public void onSu rfaceChanged (GLlO g l , int w, int h } { g l . glViewport ( O , O , w, h } ; float ratio ( float} w I h ; g l . glMat rixMode ( GLlO . GL_PROJECTION) ; g l . glloadldentity ( ) ; g l . glFrustumf ( - ratio, ratio, - 1 , l , 3 , 7 ) ; =
public void onD rawFrame ( GLlO g l ) { g l . glDisable(GLlO. GL_DITHER} ; GLlO . G L_DEPTH_BUFFER BIT ) ; gl . glClea r ( GLlO . GL_COLOR_BUFFER BIT g l . glMat rixMode ( GLlO . GL_MODELVIEW) ; g l . glLoadidentity ( ) ; GLU . glulookAt ( g l , O , O , - 5 , Of, Of, Of, O f , l . O f , O . O f } ; g l . glEnableClientState (GLlO. GL_VERTEX_ARRAY) ; d raw ( g l ) ; protected abstract void d raw(GLlO g l ) ;
}
Posiadanie tej klasy jest bardzo pożyteczne, ponieważ pozwala nam ona skupić się jedynie na metodach rysowania. Wykorzystamy ją do utworzenia naszej prostej klasy SimpleTriangle <+Renderer; listing 1 0. 1 1 ukazuje kod źródłowy klasy SimpleTriangleRenderer. Listing 10.1 1 . Klasa Sim pleTriangleRenderer //nazwa pliku: SimpleTriangleRenderer.java public class SimpleTriangleRenderer extends AbstractRenderer { !!Ilość używanych punktów lub wierzchołków p rivate final static int VERTS = 3 ; //Nieskompresowany, natywny bufor przechowujący współrzędne punktów private FloatBuffer mFVertexBuffe r ; //Nieskompresowany, natywny bufor przechowujący indeksy /!pozwalające na wielokrotne wykorzystywanie punktów. private ShortBuffer mindexBuffer;
public SimpleTriangleRenderer( Context context) { ByteBuffer vbb = ByteBuffe r . allocateDi rect ( VERTS vbb . o rd e r ( ByteO rde r . nativeOrde r ( ) ) ; mFVertexBuffer = vbb . as FloatBuffe r ( } ; ByteBuffer ibb
=
ByteBuf fe r . allocateDirect(VERTS
*
3 * 4) ;
�
2};
396
Android 2. Tworzenie aplikacji
ibb . o rd e r ( ByteOrde r . nativeOrde r ( ) ) ; rnlndexBuffer = ibb . asShortBuffe r ( ) ; float [ ] coords = { - 0 . S f , - 0 . S f , O , ll (xl,yl, zl) O . Sf , - 0 . Sf , O , O . Of , O . Sf , O }; for ( int i = O ; i < VERTS ; i++) { f o r ( i n t j = O ; j < 3 ; j++) { rnFVertexBuffe r . pu t ( coord s [ i*3+j ] ) ; } short [ ] rnylndecesArray f o r ( int i=O ; i< 3 ; i++)
{0 , 1 , 2 } ;
{
rnlndexBuffe r . put ( rnylndecesArray [ i ] ) ; } rnFVertexBuf f e r . positio n ( O ) ; rnlndexBuffe r . positio n ( O ) ; }
//przesłonięta metoda protected void d raw(GllO g l ) { g l . g1Color4f ( l . Of , O , O , O . Sf ) ; gl . glVertexPointe r ( 3 , GLlO . GL_FLOAT, O , rnFVertexBuffe r ) ; g l . g1D ra1-1Elements (GLIO. GL_ TRIANGLES , VERTS , GllO . GL_UNSIGNED SHORT, mlndexBuff e r ) ; }
}
Chociaż wydaje się, że w powyższym listingu umieszczono dużą ilość kodu, większość in formacji w nim zawartych służy do definiowania wierzchołków i konwertowania ich z bufo rów kodu języka Java na bufory NIO. Sama metoda d raw składa się jedynie z trzech wierszy: ustanowienia koloru, ustanowienia wierzchołków i rysowania. Teraz posiadamy wszystkie elementy niezbędne do przetestowania procesu rysowania. Ak tywność mamy zaprezentowaną w listingu 10.8; abstrakcyjny silnik renderujący dostępny jest z listingu 1 0 . 1 0, a sama klasa SimpleTriangleRend e r e r z listingu 1 0 . l l . Teraz mu simy jedynie wywołać klasę aktywności poprzez dowolny element menu w sposób przed stawiony poniżej: -
private void invokeSimpleT riangle ( ) { Intent intent = new Inten t ( this , OpenGLTestHarnessActivity . class ) ; sta rtActivity ( intent ) ;
Oczywiście niezbędne jest zarejestrowanie aktywności w pliku manifeście Androida:
Po uruchomieniu tego kodu naszym oczom ukaże się trójkąt przedstawiony na rysunku 10.2.
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
Rysunek 1 0.2. Prosty trójkąt utworzony za pomocą biblioteki OpenGL
e:ęm.;a
W
naszym kodzie nigdy nie uwalniamy buforów NIO, chociaż przydzielamy dla nich pamięć. Zatem w jaki sposób są one uwalniane? Jak wykorzystanie tej pamięci wpływa na bibliotekę OpenGL?
Na podstawie badań stwierdziliśmy, że pakiet java.nio przydziela przestrzeń pamięci spoza stosu Java, która może być bezpośrednio wykorzystana przez takie systemy jak OpenGL, File 1/0 i ta k dalej. W rzeczywistości bufory nio są obiektami Java, które ostatecznie wskazują na bufor natywny. Te obiekty nio są odśmiecane (ang. garbage collection gc). Oznacza to, że wykonują pracę i usuwają pamięć natywną. Programy Java nie muszą przeprowadzać żadnych specjalnych operacji, aby zwolnić pamięć. -
Jednak proces odśmiecania nie zosta nie przeprowadzony, dopóki istnieje w stosie Java wykorzystywana pamięć. Oznacza to, że możemy uruchomić pamięć natywną, a proces gc nie będzie zdawał sobie z tego sprawy. W internecie można znaleźć informacje na temat wyjątku braku pamięci uruchamiającego proces gc, po którym można spróbować ponownie uzyskać dostęp do pamięci. W
warunkach standardowych - to jest istotne w przypadku b i b l i oteki OpenGL - możemy przydzielać bufory natywne i nie musimy martwić się jawnym zwalnianiem przydzielonej pamięci, ponieważ jest ono wykonywane przez proces gc.
397
398
Android 2. Tworzenie aplikacji
Zmiana ustawień kamery Aby lepiej zrozumieć współrzędne biblioteki OpenGL, poeksperymentujmy z metodami definiującymi kamerę i sprawdźmy, w jaki sposób wpływają one na trójkąt z rysunku 10.2. Za pamiętajmy współrzędne jego wierzchołków: ( - 0 . 5 , - 0 . 5 , O 0 . 5 , - 0 . 5 , O O , 0 . 5 , O ) . Za ich pomocą poniższe trzy metody, użyte w obiekcie Abst ractRende r e r (listing 10.10), wygenerowały trójkąt widoczny na rysunku 10.2: //Spoglqda na ekran (początek układu współrzędnych) z odległości 5 jednostek od //przedniej części ekranu GLU . glulookAt ( g l , 0 , 0 , 5 , 0 , 0 , 0 , 0 , 1 , 0 ) ; //Ustanawia 2 jednostki wysokości i 4 jednostki głębi g l . glFrustumf ( - ratio, ratio, - 1 , 1 , 3 . 7 ) ; I/definiuje normalne okno g l . glViewpo rt ( O , O, w, h ) ;
Załóżmy teraz, że przypisujemy przeciwny zwrot wektorowi góry kamery: GLU . glulookAt ( g l , 0 , 0 , 5 ,
0,0,0,
O, - 1 , 0 ) ;
Jeśli przeprowadzimy taką operację, ujrzymy od\\·rócon�· trójkąt z rysunku 10.3. Aby wprowadzić taką zmianę, powinniśmy znaleźć właściwą metodę w pliku AbstractRenderer.jal'a (listing 10.10).
Rysunek 10.3. Trójkąt rejestrowany przez odwróconą kamerę
Spójrzmy teraz, co się stanie, j eśli zmienimy ostrosłup widzenia (zwany także objętością lub bryłą widzenia). Dzięki poniższej linijce kodu zostaje zwiększona wysokość i szerokość bryły widzenia o współczynnik 4 (wym iary zostały zilustrowane na rysunku 1 0 . 1 ) . Przypo minamy, że pierwsze cztery argumenty klasy g l F ru s t u m definiują przednią ścianę bryły wi dzenia. Mnożąc każdą wartość przez 4, powiększyliśmy bryłę widzenia czterokrotnie: g l . glFrustumf ( - ratio * 4 , ratio * 4, - 1 * 4, 1 *4,
3,
7) ;
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
399
W wyniku tego kodu trójkąt ulega zmniejszeniu, ponieważ jego rozmiary nie uległy zmia nie, ale bryła widzenia została powiększona. Wywołanie tej metody pojawia się w klasie Abst ractRende re r . j ava (listing 10.10). Obraz po zmianie zostat zilustrowany na rysu nku 10.4.
Rysunek 1 0.4. Trójkąt umieszczony w czterokrotnie powiększonej bryle widzenia
Wykorzystanie indeksów do dodania kolejnego trójkąta Omówienie tych prostych przykładów z trójkątem zakoi'1czymy opisem dziedziczenia z klasy Abst ractRende re r i utworzenia jeszcze jednego trójkąta poprzez wstawienie dodatkowego punktu i skorzystanie z indeksów. W teorii zdefiniujemy cztery punkty ( - 1 , - 1 ; 1 , - 1 ; O , 1 ; l , 1 ) . Następnie każemy bibliotece OpenGL narysować wierzchołki ( O , 1 , 2 ; O , 2 , 3 ) . Listing 10.12 przedstawia odpowiedzialny za to kod (zwróćmy uwagę, że zmieniliśmy rozmiary trójkąta). Listing 10.12. Klasa SimpleTriangleRenderer2
//nazwa pliku: SimpleTria11gleRenderer2.java public class SimpleTriangleRenderer2 extends Abst ractRenderer { private final static int VERTS = 4 ; p rivate FloatBuffer mFVertexBuffer; private ShortBuffer mindexBuffer; public SimpleTriangleRende rer2( Context context) { ByteBuffer vbb ByteBuffer. allocateDi rect ( VERTS * 3 * 4 ) ; vbb. order( ByteO rde r . nativeOrder ( ) ) ; mFVertexBuffer vbb. asFloatBuffe r ( ) ; =
=
ByteBuffer ibb ByteBuff e r . allocateDirect ( 6 ibb . o rd e r ( ByteOrd e r . nativeOrde r ( ) ) ; =
*
2);
400
Android 2. Tworzenie aplikacji
mlndexBuffer = ibb. asShortBuffer ( ) ; float [ ] coords = {
- 1 . 0f , 1 . 0f , O . Of , l . Of , }; for
- 1 . 0 f , O , ll (xl,yl, zl) -1.0f, O, l . Of , O , l . Of , O
( int i O ; i < VERTS ; i ++) { f o r ( int j = O ; j < 3 ; j++) { mFVertexBuffe r . put ( coord s [ i*3+j ] ) ; } =
short[] myindecesArray { 0 , 1 , 2 , 0 , 2 , 3} ; f o r ( int i=O ; i<6 ; i++) { mlndexBuffe r . pu t ( mylndecesArray [ i ) ) ; } mFVertexBuffe r . position ( O ) ; mlndexBuffer. position ( O ) ;
} protected void d raw(GllO g l ) {
g l . g1Color4f ( l . Of , O , O, O . S f ) ; g l . glVertexPointe r ( 3 , GllO . GL_FLOAT, O , mFVertexBuffe r ) ;
g l . g lD rawElements ( G LlO . G L_TRIANGLES , 6 , GllO . G L_UNSIGNED_SHORT ,
mlndexBuffe r ) ;
} }
Po utworzeniu klasy SimpleTriangleRenderer2 możemy zmienić kod w aktywności OpenGL --.. TestHa rnessActivity (listing 10.8), aby wywołać właśnie ten silnik renderujący, a nie SimpleTriangleRenderer: mTestHarness = new OpenGLTestHa rness ( this ) ; mTestH a rnes s . setRenderer( new SimpleTriangleRen d e rer2 ( t hi s ) ) ;
Tłustym drukiem zaznaczyliśmy zmieniony fragment. Teraz po ponownym uruchomieniu aplikacji Open GLTest Ha r ne s s A c t i vity zobaczymy narysowane dwa trójkąty (rysunek 10.5).
Animowanie prostego trójkąta w bibliotece OpenGL Możemy w prosty sposób dostosować animowanie za pomocą biblioteki OpenGL poprzez zmianę trybu renderowania obiektu G LS u rf a c eVi ew. W listingu 10.13 przedstawiliśmy przy kładowy kod. Listing 1 0.13. Zd efiniowa n i e trybu ciągłego renderowania I/pobiera obiekt GLSurface View GLSurfaceView openGLView;
//Ustanawia tryb ciqglego rysowania openGLView . setRenderingMode(GLSu rfaceView. RENDERMODE_CONTINUOUSLY) ;
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
'6\Milu.J
401
16:59
Rysunek 10.5. Dwa trójkąty utworzone za pomocą czterech punktów
Zwracamy uwagę na tryb renderowania, ponieważ w poprzednim przykładzie określiliśmy tryb RENDE RMOD E_WHEN_ DIRTY. Jak już wspomnieliśmy, tryb RENDERMODE_ CONTINUOUSL Y jest w rzeczywistości domyślnym ustawieniem, zatem animacja jest pierwotnie dostępna. Jeżeli został wybrany tryb rysowania ciągłego, zjawiska wpływające na animację są zależne od metody onD ra�1 silnika renderującego. W celach demonstracyjnych zaprezentujemy przykład obra cania dookoła własnej osi narysowanego uprzednio trójkąta (listing 10. 1 1 i rysunek 10.2). W przykładzie tym są wykorzystane dwa pliki:
AnimatedTriangleActivity.java, który jest prostą aktywnością przechowującą obiekt GL Su
r f a ceView.
AnimatedSimpleTriangleRenderer.java, odpowiedzialny za rysowanie animacji. Przeanalizujmy każdy z tych plików.
AnimatedTriangleActivity.java Aktywność AnimatedTriangleAct ivity (przedstawiona w listingu 10.14) przypomina ak tywność z listingu 10.8, za pomocą której testowaliśmy rysowanie prostego trójkąta. Celem tej aktywności jest zapewnienie powierzchni rysowania oraz wyświetlenie jej na ekranie Androida. W listingu 10.14 został ukazany jej kod źródłowy. Listing 1 0.14. Kod źródłowy aktywności AnimatedTria ngleActivity //nazwa pliku: AnimatedTriangleActivity.java public class AnimatedTriangleActivity extends Activity { private GLSurfaceView mTestHa rnes s ; @Over ride p rotected void onCreate( Bundle savedinstanceState) { supe r . onCreate( savedinstanceState) ;
402
Android 2. Tworzenie aplikacji
mTestHarness new GLSurfaceView( this ) ; mTestHarness . setEGLConfigChooser (false) ; mTestHa rness . setRenderer( new AnimatedSimpleTriangleRenderer(this ) ) ; =
//mTestHarness.setRenderMode(GLSuiface View.RENDERMODE_ WHEN_DIRTY);
setContentView(mTestHa rnes s ) ; } @Over ride p rotected void onResume ( ) { supe r . onResume ( ) ; mTestHarnes s . onResume ( ) ;
}
@Over ride protected void onPause ( ) { supe r . onPause ( ) ; mTestHarness . onPause ( ) ; }
Najważniejsza linijka kodu została wytłuszczona. Skorzystaliśmy z kodu aktywności używa nej w procesie prostego rysowania (listing 10.8) i oznaczyliśmy komentarzem tryb rende rowania. W ten sposób obiekt GLSu rfaceView przechodzi w domyślny tryb ciągłego ren derowania, w którym powtarzające się W)".vołania są dostosm"')""'ane do metody onDraw silnika renderowania, w tym przypadku silnika AnimatedSimpleTriangleRende re r. Przyjrzyjmy się teraz klasie AnimatedSimpleTriangleRe n d e r e r widocznej w listingu 10.15. Jest ona odpowiedzialna z a rysowanie prostokąta w wąskich przedziałach czasu, c o powoduje symulowanie animacji.
AnimatedSingleTriangleRenderer Klasa AnimatedSimpleTriangleRenderer bardzo przypomina klasę SimpleTriangleRende re r (listing 10.1 1), nie licząc zjawisk zachodzących w metodzie onD ra1-1. W metodzie tej definiujemy nowy kąt dla obrotu wykonywanego co cztery sekundy. Ponieważ obraz będzie systematycznie rysowany, uzyskamy wrażenie powoli obracającego się trójkąta. Listing 10. 1 5 zawiera pełną implementację klasy AnimatedSimpleTriangleRende re r.
Listing 10.15. Kod źródłowy klasy AnimatedSimpleTriangleRenderer //nazwa pliku: AnimatedSimpleTriangleRenderer.java public class AnimatedSimpleTriangleRenderer extends AbstractRenderer { p rivate int scale = 1 ; I/Ilość używanych punktów lub wierzchołków private final static int VERTS = 3 ; I/Nieskompresowany bufor natywny, przechowujący współrzędne punktu private FloatBuffer mFVertexBuffer; //Nieskompresowany bufor natywny, przechowujący indeksy I/pozwalające na wielokrotne wykorzystywanie punktów. private ShortBuffer mindexBuffer;
public AnimatedSimpleTriangleRendere r ( Context context)
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
{
403
ByteBuffer vbb = ByteBuffe r . allocateDirect (VERTS * 3 * 4 ) ; vbb . orde r ( ByteDrd e r . nativeOrde r ( ) ) ; mFVertexBuffer = vbb . asFloatBuff e r ( ) ; ByteBuffer ibb = ByteBuffer. allocateDirect (VERTS * 2 ) ; ibb . o rde r ( ByteOrde r . nativeOrder ( ) ) ; mlndexBuffer = ibb . asShortBuffe r ( ) ; float [ ) coords = { - 0 . S f , - 0 . S f , O , l! (xl,yJ, zl) O . Sf , - 0 . S f , O, O . Of , O . Sf , O }; for ( int i O ; i < VERTS ; i++) { f o r ( int j O ; j < 3 ; j++) { mFVertexBuffe r . put ( coord s [ i*3+j ) ) ; } } short [ ) mylndecesArray { O , l, 2} ; for ( int i=O ; i< 3 ; i++) =
=
=
{
mindexBuff e r . p ut ( mylndecesArray [ i ] ) ;
mFVe rtexBuffer . position ( O ) ; mlndexBuffe r . position ( O ) ;
} I/przesłonięta metoda protected void draw(GLlO g l ) {
long time = SystemClock . u ptimeMillis ( ) % 4000L;
float angle = 0 . 090f * ( ( int ) time ) ; g l . glRotatef ( angle , O , O , l . Of ) ;
g l . g1Color4f ( l . O f , O , O , 0 . Sf ) ; g l . g lVertexPointe r ( 3 , GLlO . GL_ FLOAT, O , mFVertexBuffer) ; g l . glD rawElements (GllO . GL_TRIANGLES, VERTS , GLIO . GL UNSIGNED_SHORT, mindexBuffe r ) ;
Po utworzeniu plików AnimatedTriangleActivity.java i AnimatedSimpleTriangleRenderer.java możemy uruchomić tę animowaną aktywność za pomocą dowolnego elementu menu, poprzez wywołanie metody zdefiniowanej w listingu 10.16.
Listing 1 0.16. Wywoływanie animowanej aktywności private void invokelSSimpleTriangle ( ) {
Intent intent = new Intent ( this , AnimatedTriangleActivity . class ) ; sta rtActivit y ( intent ) ;
404
Android 2. Tworzenie aplikacji
Nie zapominajmy o zarejestrowaniu aktywności w pliku AndroidManifest.xml (listing 10.17).
Listing 1 0.17. Rejestrowa ni e nowej aktywności w pliku AndroidManifest.xml
Stawianie czoła bibliotece OpenGL: kształty i tekstury Omówiliśmy już wiele zagadnień związanych z technologią OpenGL. Zaprezentowaliśmy algorytm rysowania prostego trójkąta i jednocześnie wyjaśniliśmy sposoby rysowania pro stych obiektów. Opisaliśmy system współrzędnych poprzez analogię z aparatem fotograficznym, a także rolę trzech najważniejszych interfejsów API: glulookAt (konfiguracja kamery),
gluFrustum (konfiguracja objętości widzenia) i glViewPort (odwzorowanie objętości widzenia na ekranie). Znajomość tych podstaw umożliwiła nam wprowadzenie Czytelnika w strukturę biblioteki OpenGL dla systemu Android. Pokazaliśmy, w jaki sposób można zdefiniować bazowe, abstrakcyjne klasy do zawarcia w nich często powtarzanych ustawień. Zademonstrowaliśmy, w jaki sposób można za pomocą tych abstrakcyjnych klas narysować prosty trójkąt i ani mować go, korzystając z macierzy translacji.
W dalszej części rozdziału będziemy zajmować się kolejnym poziomem biblioteki OpenGL. W dotychczas ukazanych przykładach definiowaliśmy w sposób jawny wierzchołki trójkąta. Ta kie podejście staje się niewygodne w przypadku rysowania kwadratów, pięciokątów, sześcioką tów i tak dalej. W ich przypadku potrzebne będą wysokopoziomowe abstrakcje obiektów, takie jak kształty, a nawet grafy sceny, w przypadku których kształty decydują o ich współrzędnych.
W taki sposób pokażemy algorytm rysowania dowolnego wielokąta w dowolnym miejscu. Następnie zajmiemy się teksturami biblioteki OpenGL. Umożliwiają one umieszczanie map bitowych oraz innych obrazów na powierzchni narysowanego obiektu. W celach demon stracyjnych umieścimy tekstury na znanych nam już wielokątach. Przy tej okazji pojawi się kolejna bardzo istotna czynność wykonywana w środowisku OpenGL: rysowanie wielu figur geometrycznych lub kształtów za pomocą potoku rysowania. Znajomość wymienionych powyżej tematów powinna przybliżyć nas do skutecznego two rzenia trójwymiarowych obiektów i scen.
Prosta sztuczka z menu, przydatna dla aplikacji demonstracyjnych Do tej pory tworzyliśmy oddzielne aktywności dla każdego przykładu. Z tego wynika, że musimy tworzyć aktywność dla każdej aplikacji testowej, a następnie rejestrować ją w pliku manife ście XML. Pokażemy teraz sztuczkę pozwalającą na zaprojektowanie jednej aktywności, która, w zależności od klikniętego elementu menu, może albo zmieniać powiązany z nią widok, albo - tak jak w przypadku aktywności GLSurfaceView używać innego silnika rende -
rowania dla każdego elementu menu.
Rozdział 1 O • Programowanie grafiki trójwymiarowej za pomocą biblioteki OpenGL
405
Aby łatwiej to zrozumieć, zaczniemy od skonfigurowania widocznego w listingu 1 0 . 1 8 ze stawu elementów menu, opisujących utworzone przez nas aplikacje demonstracyjne. Tłustą czcionką zaznaczyliśmy najważniejsze fragmenty listingu, aby uwidocznić, co będzie ry sowane po wybraniu danego elementu menu.
Listing 10.18. Stru ktura menu aplikacji demonstracyjnych Op e nGL
package com . sy h ; import android . o s . Bundle; import android . p reference . P referenceActivity; public c l a s s FlightPrefe renceActivity extends Prefe renceActivity
{ @Ove r ride protected void onCreate(Bundle savedinstanceState) { s u pe r . onCreate( savedinstanceState) ; addPreferencesf romResource ( R . xm l . flightoption s ) ;
} }
Listing 1 1 . 1 zawiera fragment kodu XML, w którym przedstawione jest usta\vienie prefe rencji lotów. W listingu tym znalazła się również klasa aktywności, wczytująca plik Xt\ifL preferencji. Zacznijmy od kodu XML. Android posiada pełną strukturę obsługi preferencji. Oznacza to, że dzięki tej strukturze możemy definiować własne preferencje, wyświetlać je użytkownikowi i zapamiętywać jego decyzje w magazynie danych. Preferencje definiujemy w katalogu /res/xml. Aby wyświetlić użytkownikov.ri preferencje, tworzymy klasę aktywności, która rozszerza predefiniowaną klasę Androida and roid . p re f e r e n c e . P r e f e r e n c e A c t i v i t y ,
Rozdział 1 1 • Zarządzanie preferencjami i ich organizacja
437
a następnie dodajemy zasób do zbioru zasobów aktywności
za pomocą metody addPreferences -.. f romRe s o u r c e ( ) . Struktura zajmuje się pozostałymi etapami (zapisywaniem i przecho-
v.rywaniem).
·
W naszym scenariuszu z rozkładami lotów tworzymy plik lightoptions. xml i umieszczamy go w /res/xml!flightoptions.xml. G eneruj emy następnie klasę aktyw no ści FlightP re fe ren ceActi vity, rozszerzającą klasę a n d ro i d . p re f e rence . Prefe renceActivity. W dalszej kolejności wywołujemy metodę addPreferencesF romRe s o u rce ( ) i przekazujemy jej zasób R . xml . flightoptions. Zauważmy, że kod XML zasobów ustawień wskazuje kilka zasobów typu string. Aby kompilacja została p rzeprowad zona pomyślnie, musimy dodać kilka cią gów znaków do projektu. Wkrótce pokażemy, jak tego dokonać. Na razie przyjrzyjmy się utworzonemu w listingu 1 1 . 1 interfejsowi użytkownika (rysunek 1 1 . 1 ). f
Rysunek 1 1 .1 . Interfejs Ul preferencji lotów
1 LI zostały ukazane dwa widoki. Wi dok po lewej jest nazywany ekranem prefe rencji, a interfej s po prawej stronie nosi nazwę listy preferencji. Po kliknięciu pola Opcje lotu pojawia się widok Wybierz opcje lotu w formie okna dialogowego modalnego, zawierającego Na rysunku
przycisk opcji dla każdego ustawienia. Użytkownik wybiera opcję, która natychmiast zostaje zapisana, a widok ulega zamkni ęciu . Po powrocie do ekranu opcji widok odzwierciedla do konany wybór.
XML preferencj i oraz powiązana z nim klasa akt)".vności zo 1 1.1. Kod w tym listingu definiuje klasę P r e f e renceScreen, a następnie tworzy klas ę podrzędną L i s t P refe rence. Klasa PreferenceSc reen posiada trzy właściwości: key, t itle i summa ry . Właściwość key j est ciągiem znaków, za pomocą któ rego Jak już wspomnieliśmy, plik
stały przedstawione w listingu
można się odnosić programistycznie do elementu (podobnie jak w przypadku właściwości
and ro id : id); t i tle definiuje nazwę ekranu (Opcje lotu); a d zi ęki właściwości summa ry opi sujemy przeznaczenie ekranu, umieszczone mniejszą czcionką pod nazwą ekranu (w naszym wypadku jest to Ustaw opcje wyszukiwania). Dla listy preferencji definiujemy właściwości key, t i tle i summa ry, a także atrybuty ent ries, ent ryValues, dialogTi tle i defaultValue. W t abeli 1 LI zostały podsumowane te atrybuty.
Tabela 1 1 . 1 . N i ektóre atrybuty klasy android.preference.ListPreference Atrybut
Opis
a n d roi d : ke y
Nazwa lub klucz opcji (na przykład s elected_ fl ight_ s o rt_ o pt io n).
android : title
Tytuł opcji.
a n d ro i d : s ummary
Krótki opis opcji.
and roid : e n t ri e s
Nazwy elementów listy, które mogą być wybierane w ramach opcji.
438
Android 2. Tworzenie aplikacji
Tabela 1 1 . 1 . Niektóre atrybuty klasy android.preference.ListPreference - ciąg dalszy Atrybut
Opis
a n d roid : en t ryValues
Definiuje klucz lub wartość każdego elementu. Każdy element posiada tekst i wartość. Tekst jest definiowany w atrybucie ent ries, a wartości - w e nt r yValu e s .
and roid : d ialogTitle
Nazwa okna dialogowego - atrybut jest używany w przypadku przedstawiania widoku w postaci okna dialogowego modalnego.
a n d ro i d : d e f a u lt V a l u e
Domyślna wartość opcji na liście elementów.
Aby nasz przykład zadziałał, musimy dodać lub zmodyfikować pliki tak, jak pokazano w li stingu 1 1 .2. Listing
1 1 .2. Konfigurowanie
reszty projektu w naszym przykładzie
c?xml version= " l . O " encoding= " u t f - 8 " ?> est ring name=" app_ name ">Demons t racj a pre fe ren ej ie/ st ring> Moje preferencje Ustaw preferencje o p c j i lotu l Wybierz o p c j e lotuc/string> Ustaw opcje wyszukiwania Opcje lotu est ring name="selected_flight sort option">selected_flight_ so rt_option Ustawienia Wy j ście c?xml version= " l . O " encoding= " ut f - 8 " ?> capplication android : icon="@d rawable/icon" android: label="@string/app_name"> cactivity android: name= " . MainActivity" android: label="@st ring/app_name"> cintent- filter> ccategory and roid : name="and roid . intent . category. LAUNCHER" />
Rozdział 1 1 • Zarządzanie preferencjami i ich organizacja
441
c/intent-filter> c/application>
Po wprowadzeniu zmian i uruchomieniu aplikacji ujrzymy prosty komunikat tekstowy Wartość opcji wynosi I (Ilość przesiadek). Kliknijmy przycisk Menu, a następnie Settings, aby przejść do aktywności PreferenceActivity. Po zakończeniu wystarczy kliknąć strzałkę cofania, aby natychmiast ujrzeć tekst in fo rmujący o wprowadzonych zmianach. Pierwszym dodanym plikie m jest /res!values/arrays.xml. W pliku tym znajdują się dwie ta blice typ u string, które są nam potrzeb ne do wprowadzenia możl iwości v.ryboru opcj i. Pierwsza tablica przechowuje wyświetlany tekst, w drugiej natomiast są umieszczone warto ści, które otrzymamy podczas wywołania metody, oraz wartość p rzechowywana w pliku XML preferencji. W celach demo nstracyjnych wp rowadziliśmy wartości indeksów tablic O, 1 i 2 dla obiektu f lig h t_ s o rt_ op t i o n s_ v a l u e s . Możemy wprowadzić każdą wartość usprawniającą działanie aplikacji . Gdyby nasza opcja była natury numerycznej (na p rzykład początkowa wartość odliczania w liczniku), moglibyśmy wykorzystać wartości 60, 120, 300 i tak dalej. Wartości nie muszą być numeryczne, dopóki mają sens dla projektanta; użyt kownik ich nie widzi, chyba że zostaną \\')'ekspo n owane . Widoc zny jest jedynie tekst za warty w pierwszej tablicy, f l ight_so rt_ options. Jak już stwierdziliśmy wcześniej, struktura Androida zajmuje się także przechowywaniem preferencji. Na przykład po \.\rybraniu przez użytkownika opcji sortowania Android prze chowuje wybór w pliku XML, umi ejscowionym w katalogu aplikacji !data (rysunek 1 1 .2). com. goog!e.andr01d. googleapps com. google. android. street com.my.client com.my.serv1ces com.sayed
+
com. s�yt-dhashrffil
-
com.syh l1b -
shar ed_pr efs
:ooa-0?·27 2008-09·.?7 2008· 12-22 2008-12-:?2
03:37 03:37 19:51
drW'Jr-xr-, drwvr-xr·x
drwxr-xr-x
19:36
drwxr-xr-x
:ooa-i:::-03
'·" O-·-'-
drw>r·>-f·X
2008·1:?-12
0.2:30
dtWXl·)t-<
2006·1.2-12 2009-01-18
0:::30 01 :26
dr 1..v:xrwx-·
.2008-12-15
0:?·16
drv•JYf-Xl-X dtt,.".}Yf•Ał •
Rysunek 1 1 .2. Ścieżka do zachowanych preferencji aplikacji
Rzec zywista ścieżka pliku wygląda następująco: !data/datal[PACKAGE_NAME}lshared_ prefs/{PACKAGE_NAME}_ p references.xml. W listingu 1 1 .3 widoczny jest plik com.syh_ preferences.xml z naszego przykładu. Listing 1 1 .3. Przykładowe zachowane preferencje cmap> l
442
Android 2. Tworzenie aplikacji
Widzimy, że w przypadku listy preferencji wartość wybranego elementu jest przechowy>va na za pomocą atrybutu key listy. Zwróćmy również uwagę, że przechowywana jest wartość elementu - nie tekst. Mała uwaga: ponieważ w pliku XML preferencji jest przechowywana je dynie wartość, a nie tekst, to jeżeli będziemy kiedykolwiek modernizować aplikację i zmieniać tekst opcji lub dodawać elementy do tablic typu string, wszystkie wartości przecho....rywane . w tym pliku powinny pozostać powiązane z odpowiednim tekstem po aktualizacji. Plik XML preferencji jest przechowywany podczas aktualizowania aplikacji. Jeżeli przed modernizacją aplikacji w tym pliku znajdowała się wartość 1 oznaczająca Ilość przesiadek, to po aktualiza cji będzie ona oznaczała dokładnie to samo. Następnym utworzonym przez nas plikiem jest !res!values/strings.xml. Dodaliśmy kilka cią gów znaków do tytułów, podsumowań i elementów menu. Umieszczone zostały tu dwa ciągi znaków, na które szczególnie warto zwrócić uwagę. Pierwszy z nich to flig h t _ s o rt_ -+option_default_ value. W naszym przykładzie przypisaliśmy wartość 1 argumentowi Ilość przesiadek. Zazwyczaj warto wybrać domyślną wartość dla każdej opcji. Jeżeli nie została ustanowiona domyślna wartość i nie wybrano żadnej innej wartości, metody zwrócą war tość null dla tej opcji. W takim przypadku kod musi potrafić przetwarzać puste wartości. Drugim interesującym nas ciągiem znaków jest s ele cted_ flight_sort_option. Gwoli ścisło ści, użytkownik nie będzie widział tego ciągu znaków. Nie musimy więc umieszczać go w pliku strings.xml w celu zapewnienia alternatywnego tekstu w innych językach. Ponieważ jednak ten ciąg znaków stanowi klucz używany przez metody do odczytania wartości, poprzez utwo rzenie z niego identyfikatora możemy się upewnić w czasie procesu kompilacji, że nie po pełniliśmy żadnej literówki w nazwie klucza. Trzecim dodanym przez nas plikiem jest /res/menu/mainmenu.xml. Zakładamy, że chcemy uzyskać dostęp do widoku preferencji poprzez menu, a nie za pomocą przycisku. Plik ten reprezentuje menu naszej aplikacji. Czwarty plik to !res/layout/main.xml. Stanowi on główny interfejs użytkownika naszej apli kacji. Dotychczas omawialiśmy sposób obsługiwania preferencji za pomocą specjalnej klasy aktywności P referenceActivity. Chcemy jednak korzystać z preferencji w głównej aktyw ności, a nie w P re f e renceActivity. Musimy więc uzyskać w jakiś sposób dostęp do prefe rencji z poziomu innej aktywności. W naszym przykładzie układem graficznym jest prosta kontrolka TextView, wyświetlająca bieżącą wartość preferencji lotu. Następnie umieszczony został kod źródłowy aktywności MainActivity. Jest to podstawowa aktywność, która zajmuje się obsługą widoku TextView, następnie wywołuje metodę od czytującą bieżącą wartość opcji, dzięki czemu możliwe jest wstawienie jej do widoku. Konfi gurujemy menu oraz jego wy>voływanie zwrotne. Wewnątrz wywoływania zwrotnego menu uruchamiamy obiekt Intent wobec aktywności FlightPreferenceActivity. Kiedy obiekt I n t e n t preferencji zostaje zwrócony, wy>vołujemy metodę setOptionText ( ) służącą do aktualizacji kontrolki TextView. Metoda setOptionText ( ) wykonuje najcięższą pracę. Pierwszym etapem jest nawiązanie łącz ności z preferencjami poprzez odniesienie do nazwy pliku XML zawierającego odpowiednie preferencje. Oczywiście musimy wiedzieć, jakiego wzorca opisanego powyżej będzie uży wała nazwa pliku. Druga opcja odnosi się do możliwości wyłącznego odczytywania lub za pisywania wartości. Temat ten poruszymy nieco później. Posiadając odniesienie do prefe rencji, wywołujemy odpowiednie metody służące do odczytywania wartości. W naszym przypadku wy>vołujemy metodę getSt ring ( ) , ponieważ uzyskujemy z preferencji wartość typu string. Pierwszym argumentem jest ciąg znaków, reprezentujący klucz opcji. Stwierdziliśmy
Rozdział 1 1
•
Zarządzanie preferencjami i ich organizacja
443
wcześniej, że stosowanie identyfikatorów chroni nas przed popełnieniem literówki w proce sie kompilacji. W miejsce pierwszego argumentu moglibyśmy również po prostu wstawić ciąg znaków selected_ flight_ s o rt_ option, gdyż zależy nam na utworzeniu jak najmniej szej i jak najszybszej aplikacji. Drugi argument służy do określenia wartości domyślnej w przypadku, gdy nie można jej znaleźć w pliku XML preferencji. Gdy aplikacja zostaje uruchomiona po raz pierwszy, nie istnieje jeszcze plik preferencji, zatem bez określenia wartości drugiego argumentu zawsze za pierwszym razem będzie zwracana pusta wartość. Jest to prawdą, nawet jeśli zdefiniowaliśmy domyślną wartość w specyfikacji L i s t P reference, umieszczonej w pliku flightoptions.xml. W naszym przykładzie ustanowiliśmy domyślną wartość, więc kod w metodzie setOptionText ( ) może posłużyć do jej odczytania. Zwróćmy uwagę, że gdybyśmy nie stworzyli identyfikatora domyślnej wartości, jej odczyt z obiektu List P reference byłby znacznie utrudniony. Poza wyświetlaniem wartości preferencji wy świetlamy także jej treść. Korzystamy w naszym przykładzie ze skrótu, ponieważ użyliśmy indeksu tablicy dla wartości w obiekcie f lig ht_ so rt_ options_ values. Dzięki prostej kon wersji wartości do typu int wiemy, który ciąg znaków ma zostać odczytany z argumentu f l ig ht_ so rt_ options. Gdybyśmy użyli w przypadku argumentu f light_ s o rt_ options_ ._values innego zestawu wartości, musielibyśmy określić indeks elementu stanowiącego naszą preferencję, a następnie wykorzystać ten indeks do odczytania tekstu tej preferencji z tablicy flight_ so rt_ opti ons. Ostatnim plikiem użytym w naszym przykładzie jest AndroidManifest.xml. Ponieważ posia damy teraz dwie aktywności w aplikacji, potrzebujemy dwóch znaczników aktywności. Pierw szy z nich określa standardową aktywność kategorii LAUNCHER. Drugi znacznik jest przezna czony dla aktywności P re f e re n ceAct i v i t y, dzięki czemu ustanawiamy nazwę działania zgodnie z konwencją dla intencji, oraz ustanawiamy kategorię PREFERENCE. Prawdopodob nie nie chcemy, aby aktywność PreferenceActivity pojawiała się wraz z pozostałymi apli kacjami, dlatego nie wyznaczyliśmy jej do kategorii LAUNCHER. Poprzez aktywność rozszerzającą klasę Prefe renceActivity o wiele łatwiej jest uzyskać odniesienie do preferencji. Zamiast wywoływać metodę getP references ( ) , moglibyśmy skorzystać z następującej konstrukcji: SharedPreferences prefs
=
getPreferenceManager ( ) . getDefaultSharedPreferences (this ) ;
Manipulowanie preferencjami w sposób programowy Nie trzeba mówić, że może zaistnieć potrzeba uzyskania dostępu do rzeczywistych kontro lek preferencji w sposób programowy. Na przykład: co zrobić w przypadku, gdy chcemy wprowadzić parametry ent ry i ent ryValue do klasy L i s t P refe ren ce w trakcie działania aplikacji? Możemy definiować kontrolki preferencji i uzyskiwać do nich dostęp w podobny sposób, jak dokonujemy tego w przypadku plików układu graficznego i aktywności. Na przy klad: aby uzyskać dostęp do listy preferencji zdefiniowanej w listingu 1 1 . 1 , musielibyśmy wywołać metodę findP re fe ren ce ( ) aktywności P re fe renceActi v i t y, przekazując wartość właściwości key (zwróćmy uwagę na podobieństwo do metody f indViewByid ( ) ) . Następ nie oddalibyśmy kontrolę obiektowi L i s t P reference i zajęlibyśmy się manipulowaniem kontrolką. Jeśli na przykład chcemy ustanowić v.rpisy obiektu ListPreference, wywołuje my metodę setEnt ries ( ) , i tak dalej. Możemy również wykorzystać kod do utworzenia preferencji lub do wykonywania na nich innych operacji. Temat ten zostanie dokładniej opisany w rozdziale 1 3.
444
Android 2. Tworzenie aplikacji
Teraz już wiemy, w jaki sposób działają preferencje w Androidzie. Mamy świadomość, że Android zawiera gotowy zestaw interfejsów UI, wyświetlających i przechowujących prefe rencje. W dodatku dostępna jest również klasa a n d ro i d . p re fe ren ce . P re f e re n ceA c t i v i t y którą rozszerzamy podczas implementowania preferencji w aplikacji. Są w niej umieszczone interfejsy API umożliwiające wczytanie preferencji oraz powiązanie i rozszerzenie struktury preferencji. ,
Pokazaliśmy, w jaki sposób można v.rykorzystać widok L i s t P reference; obecnie przyjrzyj my się innym elementom interfejsu użytkownika, stosowanym w strukturze preferencji w An droidzie. Mianowicie przeanalizujmy widoki C he c k B o x P re f e r e nce Edi tTex t P r ef e rence ,
i RingtonePreference.
Widok CheckBoxPreference Zobaczyliśmy, że preferencja ListPreference wyświetla listę jako element swego interfejsu użytkownika. W analogiczny sposób preferencja CheckBoxPreference wyświetla widget pola wyboru w postaci składnika swego interfejsu UI. Załóżmy, że w ramach rozbudowania naszej aplikacji >vyszukującej loty chcemy pozwolić użytkownikowi na możliwość \\ryboru wyświetlanych kolumn w zestawieniu >vyników. Prefe rencja ta v\ryświetla spis dostępnych kolumn i umożliwia użytkownikowi vvybranie pożąda nych kolumn poprzez zaznaczenie odpowiedniego pola wyboru. Interfejs użytkownika dla tego przykładu jest przedstawiony na rysunku 1 1 .3, a zawartość pliku X.NIL preferencji zo stała umieszczona w listingu 1 1.4.
Rysunek 1 1 .3. Interfejs Ul preferencji pola wybo ru Listing 1 1 .4. Zastosowanie widoku CheckBoxPreference
Rozdział 1 1
•
Zarządzanie preferencjami i ich organizacja
445
li CheckBoxPreferenceActivity.java import android . os . Bundle ; import android . p reference. PreferenceActivity; public class CheckBoxPreferenceActivity extends PreferenceActivity
{
@Over ride p rotected void onCreate(Bundle savedlnstanceState) supe r . onCreate( savedinstanceStat e ) ; addPreferencesFromReso u rce ( R . xml . chkbox ) ;
} W listingu 1 1 .4 został ukazany plik XML preferencji, chkbox.xml, oraz prosta klasa aktyw ności wczytująca ten plik za pomocą metody add PreferencesF romResou rce ( ) . Jak widać, w interfejsie użytkownika zostało umieszczonych pięć pól wyboru, każde reprezentowane przez węzeł CheckBox P re f e rence w pliku XML. Każde z tych pól posiada również wartość key, która - jak można było s ię spodziewać - jest ostatecznie wykorzystywana do prze chowywania stanu elementu interfejsu użytkownika w trakcie procesu zapisywania wpro wadzonych zmian preferencji. Dzięki widokowi CheckBoxPreference zostaje ona zapisana po zmianie stanu preferencji. Innymi słowy, jeżeli użytkownik zaznacza kontrolkę preferen cji lub usuwa jej zaznaczenie, jej stan zostaje zapisany. Listing 11 .5 przedstawia magazyn danych preferencji dla naszego przykładu. Listing 1 1 .5. Magazyn danych dla p refe rencj i w postaci pola wyboru
446
Android 2. Tworzenie aplikacji
Ponownie widać, że każda preferencja zostaje zachowana poprzez atrybut key. Typem da nych widoku CheckBoxP re fe ren ce jest boolean, przyjmujący wartość t r ue lub fal se : wartość t rue wskazuje zaznaczenie preferencji, wartość f a l s e przyjmuje znaczenie prze ciwne. Aby odczytać wartość z pola wyboru preferencji, musielibyśmy uzyskać dostęp do współdzielonej preferencji, a następnie wywołać metodę getBoolean ( ) i przekazać jej atry but key preferencji:
Boolean option = pref s . getBoole a n ( "show p ri c e_ col um n_ p ref " , false ) ; Inną pożyteczną funkcją widoku CheckBoxPreference jest możliwość zmiany treści pod sumowania w zależności od tego, czy pole wyboru jest zaznaczone, czy nie. W tym przypadku stosowane są atrybuty summa ryOn i s umm a ryOff. Przyjrzyjmy się teraz kontrolce Edi tText '+-Preference.
Widok EditTextPreference W Androidzie dostępna jest również preferencja umożliwiająca pisanie tekstu, zwana Edi t '+-TextPreference. Użytkownik, zamiast zostać poproszony o dokonanie zaznaczenia, dostaje możliwość wpisania własnej treści. Załóżmy, że posiadamy aplikację służącą do generowa nia kodu Java. Jednym z ustawień preferencji tej aplikacji może być nazwa domyślnego pa kietu, wyznaczonego dla generowanych klas. Chcemy więc ""yświetlić użytkownikowi pole tekstowe i pozwolić mu na wpisanie nazwy takiego pakietu. Na rysunku 1 1 .4 został zapre zentowany interfejs użytkownika takiej aplikacji, zaś w listingu 1 1 .6 pokazaliśmy kod XNIL.
Rysunek 1 1 .4. Zastos owanie widoku EditTextPreference Listing 1 1 .6. Przykładowy widok EditTextPreference c?xml version=" l . 0" en c od i ng = " u tf - 8 " ?>
xmln s : and roid=htt p : //schema s . android . com/apk/res/android and ro i d : key=" package_name_ sc reen" android : title="Nazwa pakietu" android : summary="Wp rowadź nazwę pa kietu" >
Rozdział 1 1 • Zarządzanie preferencjami i ich organizacja
447
EditTextPreferenceActivity.java import android . o s . Bundle; import android . p reference . P refe renceActivity; public class EditTextPreferenceActivity extends Prefe renceActivity{ @Over ride protected void onCreate(Bundle savedinstanceState) { supe r . onC reate( saved!nstanceState ) ; addPreferencesF romReso u rce ( R . xml. packagepref ) ;
} } Widzimy, że w listingu 1 1 .6 zostaje zdefiniowana klasa PreferenceScreen, zawierająca widok Edi tTextPreference. W wygenerowanym interfejsie użytkownika klasa Pre f ere n ceSc ree n jest widoczna po stronie lewej, a widok E d i tTextPreference - po prawej (rysunek 1 1 .4). Po kliknięciu przez użytkownika opcji Wprowadź nazwę pakietu pojawi się okno dialogowe, w którym można wpisać odpowiednią nazwę. Kliknięcie przycisku OK spow oduje zapis anie ustawienia w m agazynie preferencji. Podobnie jak ma to m iej sce w przypadku innych preferencji, możemy odczyta ć widok Edi tTextPreference z poziomu klasy aktywności za pomocą atryb utu key tej preferencji. Po wczytaniu preferencji EditTextPreference możemy konfigurować widok EditText po przez wywołanie metody getEdi tText ( ) - jeśli na przykład chcemy wprowadzić spraw dzanie wartości wpisanej przez użytkownika jej przetwarzanie wstępne lub przetw arza ni e końcowe. Aby odczytać treść widoku EditTextPreference, wykorzystujemy po prostu metodę getText ( ) ,
.
Przyj rzyjmy się teraz widokowi RingtoneP reference struktury preferencji.
Widok RingtonePreference Widok R i n g t o n e P re f e re n c e służy do obsługi dzwonków. Jest on wstawiany do aplika cji w przypadku, gdy użytkownik otrzymuje m ożliwość wyboru dzwonka w formie preferencji. Rysunek 1 1 .5 ilustruje przykładowy interfejs Ul widoku Ringt o n e Preference, a w listingu 1 1.7 został wstawiony jego kod XML. Listing 1 1 .7. Definiowanie preferencji Ringto ne P reference
448
Android 2. Tworzenie aplikacji
0 Wybierz dzwonek Cichy D o myś l ny
dzwo n ek
BeeBeep Beep B ee p B ee p Brzęczyk
Rysunek 1 1 .5. Przykładowy interfejs U l widoku RingtonePreference android : title="Wybierz preferencję dzwonka" android : showSilent=" t rue" android : ringtoneType="alarm" android : summary= "Ustanawia dzwonek" />
li RingtonePreferenceActivity.java import and roid . o s . Bundle; import and roid . p reference . P referenceActivity; public class RingtonePreferenceActivity extends PreferenceActivity
{ @Over ride p rotected void onCreate(Bundle savedlnstanceState) { s u p e r . onCreate( savedinstanceState) ; addP referencesF romRes o u rce ( R . xml . ringtone) ;
} Kiedy użytkownik kliknie prqcisk Wybierz preferencję dzwonka, zostanie wyświetlony widok Lis t P reference zawierający dzwonki umieszczone w urządzeniu (rysunek 1 1.5). Można tam zaznaczyć dzwonek, a następnie kliknąć przycisk OK lub Anuluj. Po wybraniu opcji OK dane zostaną zapisane w magazynie preferencji. Zwróćmy uwagę, że w przypadku dzwonków wartością przechov,rywaną w magazynie preferencji jest identyfikator URI danego dzwonka - chyba że zostanie ·wybrana preferencja Cichy, która w miejsce przechowywanej wartości wstawia pusty ciąg znaków. Przykładowy identyfikator URI wygląda następująco: content : //media/inte rnal/audio/media/26
Rozdział 1 1
•
Zarządzanie preferencjami i ich organizacja
449
pilf'++ Jeżeli emulator jest skąpo wyposażony w dzwonki, można dodać je samemu. Wystarczy
skopiować muzykę na kartę SD (proces ten został omówiony w rozdziale 9.), przejść do a plika cj i M us ic Pl aye r, zaznaczyć plik muzyczny, kliknąć przycisk Menu i wybrać op cję Use as ringtone.
Przedstawiony w listingu 1 1 .7 widok R in g t on eP r e f e ren ce korzysta z takiego samego algo rytmu, jak pozostałe pr efere ncje. Różnica polega na tym, że ustanawiamy kilka różnych atrybutów, w tym showSilent i ringtoneType. Atrybut showSilent służy do wstawienia wy ciszon ego dzwonka na listę, a r i n g t oneType do ograniczania typów wyświ etlanyc h dzwon ków. Dla tej właściwości dostępne są wartośc i ringtone, not i f i c a t i o n , alarm i a l l .
Organ izowanie preferencji Struktura preferencji pozwala na organizowanie p refere n cji w kategorie. Jeśli na przykład posiadam y wiele preferencji, możemy zbudować widok przedstawiający ich wyso kopozi o mowe kategorie. U żytkownicy mogliby wtedy ·wyświetlać każdą z tych kategorii w celu przeglądania preferen cj i umieszczonych w danej grupie i zarządzania nimi.
Taką stru kturę implementujemy na jeden z dwóch sposobów. Możemy albo wp rowadzić zagnieżdżone elementy PreferenceScreen wewnątrz nadrzędnej kl asy Prefe renceScreen, albo możemy w podobny sposób wykorzystać elementy PreferenceCategory. Pierwszy z wy mienionych sposobów, dotyczący grupowania preferencji za pomocą zagnieżdżonych elemen tów P r e fe r e n c e Sc re en, został zaprezentowany na rysunku 1 1.6 i w listingu 1 1.8.
Rysunek 1 1 .6: Tworzenie grup preferencji poprzez za g ni eżdże ni e elementów Prefe re nce Sc ree n Listing 1 1 .8: Zagnieżdżanie elementów PreferenceScreen umożliwiające organizowanie preferencji
450
Android 2. Tworzenie aplikacji
Widok po lewej stronie na rysunku 1 1 .6 prezentuje dwa ekrany preferencji, jeden zatytuło wany Mięso, a drugi noszący nazwę Warzywa. Kliknięcie danej grupy spowoduje wyświe tlenie jej elementów. Listing 1 1 .8 prezentuje, w jaki sposób tworzy się zagnieżdżone ekrany. Na rysunku 1 1.6 tworzymy grupy poprzez zagnieżdżenie elementów P referenceSc reen wewnątrz nadrzędnej klasy P r e f e renceSc reen. Organizowanie w ten sposób preferencji jest korzystne w przypadku, gdy posiadamy wiele preferencji, a nie chcemy, aby użytkownik musiał przewijać ekran w celu znalezienia danej preferencji. Jeżeli nie posiadamy wielu preferencji, a mimo to chcemy utworzyć ich v.rysokopoziomowe kategorie, możemy zasto sować drugą metodę, związaną z obiektem P r e f e re n c e C a t e g o ry. Szczegóły zostały zapre zentowane na rysunku 1 1 .7 i w listingu 1 1.9.
Rysunek 1 1 .7. Za stos owa ni e obiektu PreferenceCategory do orga nizowa nia preferencji
Rozdział 1 1
•
Zarządzanie preferencjami i ich organizacja
451
'\a rysunku 1 1 .7 zostały ukazane grupy wykorzystane również w poprzednim przykładzie, _,-:óre teraz są jednak zorganizowane w kategorie preferencji. Jedyna różnica pomiędzy kodem :\..\IL z listingu 1 1.9 a kodem Xi\1L z listingu 11.8 polega na utworzeniu obiektu Preference -Catego ry dla zagnieżdżonych ekranów, zamiast zagnieżdżenia elementów Pref e renceSc reen. Listing 1 1 .9. Tworzenie kategorii preferencji c?xml version=" l . O " encoding= " ut f - 8 " ?> cPreferenceCategory xmlns : android= " h t t p : //schemas . an d roid . com/apk/res/android" android : key="meats category" android : title="Mięso" a n d roid : summa ry= " P referencje odnoszące się do mięsa">
452
Android 2. Tworzenie aplikacji
Podsumowanie W tym rozdziale zajmowaliśmy się zarządzaniem preferencjami w Androidzie. Pokazali śmy, w jaki sposób można korzystać z widoków ListPreference, CheckBoxPreference, EditTextPreference i RingtonePreference. Omówiliśmy także programowy sposób mo dyfikowania preferencji , a także techniki organizowania preferencji w grupy.
ROZDZIAŁ
12 Badanie aktywnych fol derów
W rozdziale 10. omówiliśmy szczegółowo interfejs OpenGL w Androidzie. Nato
miast rozdział 1 1 . poświęciliśmy sposobom zarządzania preferencjami aplikacji na platformie Android. W niniejszym rozdziale chcemy zapoznać Czytelnika z kolejną zaawansowaną strukturą Androida: aktywnymi folderami (ang. live
folder). Wprowadzone w wersji 1 .5 środowiska Android aktywne foldery umożliwiają projektantom umieszczanie takich dostawców treści, jak kontakty, notatki oraz multimedia, w domyślnym ekranie początkowym urządzenia (które będziemy nazywać stroną początkową urządzenia). Kiedy dostawca treści, na przykład contacts, zostanie umieszczony na stronie głównej jako aktywny folder, będzie on automatycznie odświeżany w razie dodawania, usuwania lub modyfikowa nia kontaktów w bazie danych. Wyjaśnimy sens istnienia aktywnych folderów, sposoby ich implementacji oraz ich „uaktywnienia".
Badanie aktywnych folderów W Androidzie aktywny folder ma się do dostawcy treści tak, jak czytnik RSS do
publikowania strony internetowej. W rozdziale 3. stwierdziliśmy, że dostawcy tre ści przypominają strony internetowe dostarczające informacje poprzez identy fikatory URl. W miarę rozpowszechniania witryn sieciowych, z których każda publikowała informacje na swój własny sposób, pojawiła się potrzeba zbierania treści z wielu różnych stron internetowych, aby dać użytkownikowi możliwość ich śledzenia za pomocą jednego czytnika. W taki sposób została zaprojekto wana technologia RSS. Zródła RSS wymusiły na nas dostrzeżenie wspólnego wzor ca wśróµ różnorodnych zbiorów informacji. Dzięki takiemu wzorcowi istnieje możliwość zaprojektowania czytnika, który będzie odczytywał dane, dopóki będą one posiadały jednolitą strukturę. Taka sama jest koncepcja aktywnych folderów. Podobnie jak czytnik RSS za pewnia wspólny interfejs dla opublikowanej treści sieciowej, tak aktywny folder definiuje wspólny interfejs dla dostawcy treści w Androidzie. Dopóki dostawca treści spetnia wymagania protokołu, możemy w Androidzie utworzyć ikonę
454
Android 2. Tworzenie aplikacji
aktywnego folderu, reprezentującą tego dostawcę na stronie początkowej urządzenia. Po kliknięciu tej ikony system automatycznie skontaktuje się z dostawcą treści. Spodziewamy się zatem, że dostawca treści zwróci nam kursor. Zgodnie z kontraktem aktywnego folderu kursor ten musi posiadać predefiniowany zbiór kolumn. Następnie jest on wyświetlany po przez widoki ListView lub G ridView.
W oparciu o koncepcję aktywnych folderów aktywne foldery działają w następujący sposób: 1. Najpierw tworzymy na stronie początkowej ikonę reprezentującą zbiór wierszy pochodzących od dostawcy treści. Tworzymy takie powiązanie poprzez przypisanie identyfikatora URI do ikony. 2. Po kliknięciu tej ikony przez użytkownika system pobiera identyfikator URI i wykorzystuje go do wywołania dostawcy treści. Dostawca ten poprzez kursor zwraca nam zbiór wierszy. 3. Dopóki kursor zawiera kolumny spodziewane przez aktywny folder (na przykład nazwę, opis oraz program wywoływany po kliknięciu wiersza), system będzie je ·wyświetlał jako widoki Lis tView lub G ridView. 4. Ponieważ obiekty ListView oraz G ridView posiadają możliwość aktualizowania swoich danych podczas wprowadzania zmian w bazie danych, widoki te są nazywane „aktywnymi" stąd wzięła się nazwa „aktywne foldery". -
W przypadku aktywnych folderów istnieją dwie podstawowe zasady. Pierwsza z nich defi niuje wspólne nazwy kolumn dla wszystkich kursorów. Dzięki niej wszystkie kursory prze znaczone dla aktywnych folderów w Androidzie są traktowane tak samo. Wedle drugiej za sady widoki w Androidzie potrafią wyszukiwać aktualizacje danych kursora i odpowiednio się do nich dostosowywać. Nie jest to reguła specyficzna jedynie dla aktywnych folderów, lecz standardowa dla wszystkich widoków interfejsu Ul w Androidzie, zwłaszcza dla widoków korzystających z kursora.
Skoro przedstawiliśmy już koncepcję aktyvmych folderów, będziemy systematycznie zagłę biali się w ich strukturę. Proces poznawania podzielimy na dwa podrozdziały. W pierwszym podrozdziale przyjrzymy się wnikliwie całokształtowi doświadczeń użytkownika z aktyw nym folderem. Po tej części aktywne foldery staną się jeszcze bardziej zrozumiałe. W drugim podrozdziale zaprezentujemy sposoby poprawnego tworzenia tych struktur, aby były naprawdę aktywne. Aby „uaktywnić" folder, należy przeprowadzić kilka dodatkowych czynności, zatem zajmiemy się tym wcale nie tak oczywistym aspektem aktywnych folderów.
W jaki sposób użytkownik odbiera a ktywne foldery Aktywne foldery są dostępne dla użytkowników poprzez stronę początkową urządzenia. Poniższa sekwencja przedstawia sposób wykorzystywania aktywnych folderów: 1. Wejdź na stronę początkową urządzenia.
2. Przejdź do menu kontekstowego początkowej strony. Zostaje ono wyświetlone po długim kliknięciu pustej przestrzeni na stronie początkowej. 3. Znajdź w menu kontekstowym opcję Folders i kliknij ją, aby ujrzeć listę dostępnych aktywnych folderów.
4. Na wyświetlonej liście zaznacz nazwę aktywnego folderu, który chcesz umieścić na stronie początkowej. Zostanie utworzona ikona reprezentująca wybrany folder aktywny. 5. Kliknijmy ikonę konfiguracji aktywnego folderu, utworzoną w punkcie 4., aby wyświetlić wiersze zawierające informacje (dane reprezentowane przez ten aktywny folder) w widokach L i stView lub G r idView.
Rozdział 1 2 • Badanie aktywnych folderów
455
6. Kliknij jeden z wierszy, aby przywołać aplikację potrafiącą go wyświetlić. 7. Skorzystaj z opcji menu wyświetlanych przez aplikację, aby przeglądać elementy lub manipulować danym elementem. Za ich pomocą możemy również tworzyć nowe elementy dozwolone przez aplikację.
8. Zwróć uwagę, że aktywne foldery automatycznie odzwierciedlają wszelkie zmiany dokonane na elemencie lub zbiorze elementów. Omówimy wszystkie powyższe etapy, ilustrując każdy z nich zrzutem ekranu. Rozpoczniemy od punktu 1.: typowej strony początkowej Androida (rysunek 12.1). Strona początkowa może się nieznacznie różnić w zależności od stosowanej wersji Androida.
Rysunek 12.1. Strona początkowa Androida
Jeżeli na stronie początkowej wykonamy długie kliknięcie, zostanie wyświetlone jej menu kontekstowe (rysunek 12.2).
Rysunek 12.2. Menu kontekstowe strony początkowej w Androidzie
456
Android 2. Tworzenie aplikacji
Po wybraniu opcji Polders pojawi się kolejne menu, przedstawiające dostępne foldery aktywne (rysunek 12.3). W następnym podrozdziale stworzymy aktywny folder, na razie jednak załóżmy, że posiadamy już zbudowany folder aktywny, nazwany Nowy aktywny folder (rysunek 12.3). �Rmm 6:4s
0 Wybierz folder Kontakty z numerami telefonów Wszystkie kontakty
-.-.,,_
Nowy fo l der aktywny
";-t
r
Aktywn ość fold eru aktywnego
� ··� r
Wszystkie aktywn. folderu aktywneg o
-·
J
·�· ·
Rysunek 1 2.3. Przeglądanie listy dostępnych aktywnych folderów
Po kliknięciu opcji Nowy aktywny folder na stronie początkowej Androida zostanie utwo rzona ikona reprezentująca aktywny folder. W naszym przykładzie nazwą tej ikony będzie Kontakty AF, czyli skrót do „Kontakty Aktywny Folder" (rysunek 12.4). W folderze tym będą wyświetlane kontakty z bazy kontaktów (podczas omawiania klasy AllContact sliveFolder '+CreatorActivity pokazanej w listingu 1 2.2 wyjaśnimy, w jaki sposób tworzy się nazwy aktywnych folderów). ądMi]
I
I
-=---- .··· "'
--
�
:}
mmi
,, � .„.„.. Tełefon
�
-- -
Kontakry
,
Internet
-----Rysunek 1 2.4. Ikona aktywnego folderu na stronie początkowej
Rozdział 1 2 • Badanie aktywnych folderów
457
\V następnym
podrozdziale będzie można zobaczyć, że za utworzenie folderu Kontakty AF odpowiedzialna jest aktywność. Na razie interesują nas wrażenia użytkown ika zatem klik nij my ikonę Kontakty AF, aby ujrzeć listę kontaktów wyświetloną w widoku ListView (ry sunek 12.5). ,
FQllml m a:41
Rysunek 12.5. Wyśw ietla nie aktywnego folderu kontaktów
W zależn ości od ilości posiadanych kontaktów lista może wyglądać inaczej. Po kliknięciu jednego z kontaktów ujrzymy jego szczegóły (rysunek 12.6). �!fl'.i]©
10:33
Rysunek 12.6. Otwieranie aktywnego folderu kontaktów
458
Android 2. Tworzenie aplikacji
Możemy kliknąć znajdujący się u dołu ekranu przycisk Menu, aby zobaczyć możliwości edycyjne danego kontaktu (rysunek 12.7).
��G
10:35
Edytuj kontakt
{>
Udostępnij
�
Usuń kontakt
rr:
Opce
Il( ft
""""'
WJ
Rysunek 1 2.7. Opcje menu pojedynczego kontaktu
Po wybraniu opcji edycji kontaktu pojawi się ekran ukazany na rysunku 12.8. �rfłll G
10:36
Rysunek 12.8. Edycja szczegółowych informacji o kontakcie
Rozdział 1 2 • Badanie aktywnych folderów
459
Aby ujrzeć „aktywny" aspekt tego folderu, możemy usu ną ć ten kontakt lub utworzyć nowy. Po powrocie do widoku folderu Kontakty AF ujrzymy wprowadzone zmiany. D okonujemy tego poprzez wielokrotne kliknięcie przycisku cofania, dopóki nie wrócimy do folderu Kontakty AF.
Tworzenie aktywnego folderu Skoro już wiemy, czym są aktywne foldery, czas zapre zentować proces ich tworzenia. Po zbudowaniu aktywnego folderu możemy go wykorzystać do utworzenia jego ikony na stronie początkowej. Pokażemy także, w jaki sposób działa aktywna" część folderu. „
W celu wygenerowania aktywnego folderu wymagane są dwa elementy: aktywność i wyspe cjalizowany dostawca treści. An droid wykorzystuj e „etykietę" tej aktywności do zapełnienia wido cznej na rysunku 12.3 listy dostępnych aktywnych folderów. Android v.'ywołuj e rów nież tę aktywn ość w celu uzyskani a i de n tyfikatora URI umożliwi ające go ot rzyma ni e l isty wyświetlanych \vie rszy. Dostarczany przez aktywność i dentyfikator URI powinien wskazywać wyspecjalizowanego dostawcę treści, odpowiedzialnego za zwracanie krotek. Dostawca zwraca te krotki poprzez właściwie zdefi niowany kursor. Stwierdzamy, że kursor j est „właściwie zdefiniowany", p o n ieważ oczekujemy, że będzie znał predefiniowany zestaw nazw kol umn. Zazwyczaj umieszczamy te dwa elementy w aplikacji, a następ nie wdrażamy j ą na urządze nie. Będziemy potrzebowali również kilku pomocniczych plików, bez których aktywne fol dery nie będą działać. Objaśnimy i zademon strujemy wym ienione koncepcje na przykładzie zawierającym następujące pliki: • AndroidManifest.xml. W pliku tym są definiowane aktywno ści, które są wywoływane w cel u utworzenia definicji aktywnego folderu. • AllContactsLiveFolderCreatorActivity.java. Ta aktywno ść jest odpowiedzial na za dostarczanie defin icji aktywnego folderu, pozwalaj ącej na wyświetlanie wszystkich k ontaktów z b azy danych. • MyContactsProvider.java. Te n dostawca treści reaguj e na identyfikator URI aktywnego folderu, zwracaj ącego kursor z kontaktami. Dostawca ten wewnętrznie wykorzystuj e dostępnego w Androidzie dostawcę treści kontaktów . • MyCursor.java. Jest to wyspecjalizowany kursor, wykonujący op eracj ę reque ry podczas zmiany danych. • BetterCursor Wrapper.java. Plik ten jest potrzebny klasie MyCu rso r do p rzeprowadzania op eracj i reque ry. • SimpleActivity.java. Ta prosta aktywność jest nieobowiązkovvym plikiem, służącym do testowania projektu. W wersji koń cowej aplikacji plik ten nie będzie potrzebny.
Omówimy każdy plik, aby łatwiej było zrozumieć zasadę działania aktywnych folderów.
AndroidManifest.xml Mieliśmy już wielokrotnie do czynienia z plikiem AndroidManifest.xm/; jest to ten sam plik, któ rego wymagają do działania wszystkie aplikacje. Fragment pliku poświęcony aktywnym folde rom, który został oddzielony komentarzem , wskazuje istnienie aktywności AllContactslive '+FolderCreat o rActivity odpowiedzialnej za utworzenie aktywnego folderu (listing 1 2 . 1 ) . Fakt ten został wyrażony poprzez deklarację intencji, której działaniem jest a n dr oi d . intent . '+action . C REATE_ LIVE_FOLDER.
460
Android 2. Tworzenie aplikacji
Listing 12.1. Plik AndroidManifest.xml definicji aktywnego folderu c?xml version= " l . 0 " encoding= " ut f - 8 " ?> ccategory and roid : name="android . intent . category . LAUNCHER" /> cactivit y and roid : name= " . AllConta ctsliveFolder C reat orActivity " android : label="Nowy aktywny folder " and roid : i con="@d rawable/icon ">
cuse s - s d k and roid : minSdkVe rsion="3" /> cuses-permission android: name="android .permission. READ_CONTACTS">
Etykieta tej aktywności, Nowy aktywny folder, pojawi się w menu kontekstowym strony po czątkowej (rysunek 12.3). Jak zostało wyjaśnione w paragrafie „W jaki sposób użytkownik odbiera aktywne foldery", uzyskujemy dostęp do menu kontekstowego strony początkowej poprzez długie kliknięcie na obszarze tego ekranu początkowego. Kolejnym godnym uwagi elementem kodu z listingu 1 2 . 1 jest deklaracja p rovider, zakotwi czona do identyfikatora URI content : I I com . a i . livefolde rs . contacts i obsługiwana przez klasę dostawcy MyContactsProvider. Dostawca ten jest odpowiedzialny za dostarczenie kursora wypełniającego kontrolkę ListView, która zostaje otwarta po kliknięciu odpowiedniej ikony aktywnego folderu (rysunek 12.5). Al'tyW11ość AllContactsliveFolderC reato rActivit y takiego folderu musi „wiedzieć", czym jest ten identyfikator URI, i zwrócić go po wywoła niu przez system. Android przywołuje tę aktywność, gdy na stronie początkowej zostaje wybrana nazwa folderu aktywnego, którego ikona zostanie utworzona. Zgodnie z protokołem aktywnych folderów intencja CREATE_ LIVE_ FOLDER będzie pozwalała wyświetlać aktywność A l l C o n t a c tsliveFolderC reato rActivity jako opcję zatytułowaną Nowy aktywny folder w menu kontekstowym strony początkowej (rysunek 12.3). Kliknięcie
Rozdział 12 • Badanie aktywnych folderów
461
tej opcji spowoduje utworzenie ikony na stronie początkowej, co zostało pokazane na sunku 12.4.
ry
Obowiązkiem aktywności AllContactsLiveFolderCreatorActivity jest zdefiniowanie ikony, składającej się z obrazu i etykiety. W naszym przypadku kod aktywności AllContactsLive --.. F olderCreatorActivity nadaje etykiecie nazwę Kontakty AF (listing 12.2). Przyjrzyjmy się zatem kodowi źródłowemu tego twórcy aktyvmych folderów. Listing 12.2. Kod źródłowy klasy A ll ContactsliveFold erCreatorActivity public class AllContactsliveFolderC reatorActivity extends Activity { @Over ride protected void onCreate (Bundle savedinstanceState)
{
supe r . onC reate ( s avedinstanceState ) ; final Intent intent = getlntent ( ) ; final St ring action = intent . getAction ( ) ; if ( Livefolde rs . ACTION_CREATE_LIVE_FOLDER . equal s (action ) ) setResult ( RESULT_OK, createlivefolde r (MyContactsProvider. CONTACTS_URI , "Kontakty A F " , R . d rawable . icon) ); else { setResult ( RESULT_ CANCELED ) ;
}
finish ( ) ;
} private Intent createlivefolder ( Uri u r i , String name, int icon)
{
final Intent intent = new Intent ( ) ; intent . setData ( u ri ) ; intent . putExt r a ( LiveFolde rs . EXTRA_ LIVE_ FOLDER_NAME, name ) ; intent . put Ext r a ( Livefolde rs . EXTRA_LIVE_ FOLDER_ ICON , Intent . Sh o rtcuticonResource . f romContex t ( t h i s , icon ) ) ; intent . putExt r a ( LiveFolde rs . EXTRA_ LIVE_FOLDER_DISPLAY_MODE, Livefolde rs . DISPLAY MODE LIST) ; return intent;
}
AllContactslivefolderCreatorActivity.java Klasa AllContactsli vefolde rC rea to rActivity ma do spełnienia jedną rolę: generatora lub kreatora aktywnego folderu (listing 12.2). Możemy ją sobie ·wyobrazić jako szablon takiego folderu. Podczas każdorazowego kliknięcia tej aktywności (poprzez opcję Folders w menu kontekstov,rym strony początkowej) zostanie wygenerowany aktywny folder.
462
Android 2. Tworzenie aplikacji
To zadanie zostaje wykonane za pomocą aktywności poprzez podanie obiektowi wywołują cemu - stronie początkowej lub, w naszym przypadku, strukturze aktywnego folderu nazwy aktywnego folderu, obrazu ikony tego folderu, identyfikatora URI danych oraz trybu wyświetlania (lista lub siatka). Z kolei struktura jest odpowiedzialna za utworzenie ikony aktywnego folderu na stronie początkowej.
Metoda c reateliveFolder przede wszystkim ustanawia wartości wobec intencji wywołującej. Kiedy intencja zostaje zwrócona procedurze wyw·ołującej, procedura ta otrzyma następujące informacje:
• nazwę aktywnego folderu; • obraz wykorzystywany jako ikona aktywnego folderu; • tryb wyświetlania: lista lub siatka; • identyfikator URI danych lub treści, służący do przywoływania informacji. Informacje te v.rystarczą do utworzenia ikony aktywnego folderu, przedstawionej na rysun ku 1 2.4. Kiedy użytkownik ją kliknie, zostanie wywołany identyfikator URI, dzięki któremu zostaną odczytane dane. Zadaniem dostawcy treści określonego dzięki temu identyfikatorowi jest dostarczenie znormalizowanego kursora. Zademonstrujemy teraz kod tego dostawcy treści: klasę MyContactsProvider.
MyContactsProvider .java Przed klasą MyContact s P rovider stoją następujące zadania:
1 . Rozpoznanie przychodzącego identyfikatora URI content : //com . a i . livefolders . contacts/contacts. 2. Wewnętrzne wywołanie dostawcy treści kontaktów Androida, identyfikowanych adresem con ten t : I I contacts/people/. 3. Odczytanie wszystkich krotek kursora oraz odwzomwanie ich na kursorze typu Mat rixCu r s o r, wraz z umieszczeniem poprawnych nazw kolumn wymaganych przez strukturę aktywnego folderu.
4. Umieszczenie obiektu Mat rixCu rso r w innym kursorze, aby przeprowadzenie operacji requery na tym obiekcie powodowało w razie potrzeby wywołanie dostawcy treści kontaktów. Kod dostawcy MyContactsProvider został umieszczony w listingu 12.3. Istotne elementy zostały zaznaczone tłustym drukiem.
Listing 12.3. Kod źródłowy klasy MyContactsProvider public class MyContactsProvider extends ContentProvider { public static finał String AUTHORITY
=
"com. ai. livefolders . contacts " ;
//Identyfika tor Uri biorqcy udział w procesie tworzenia aktywnego folderu jako dane //wejściowe public static final Uri CONTACTS URI = U r i . pars e ( "content : // " + AUT80RITY + "/contacts" ) ;
Rozdział 1 2 • Badanie aktywnych folderów
//Aby ten identyfikator URI został rozpoznany private static final int TYPE MY_URI = O ; private static final UriMatcher URI MATCHER; static{ URI_MATCHER = new U riMat c h e r ( UriMatche r . NO MATCH ) ; URI_MATCHER . addURI (AUTHORITY, "contact s " , TYPE MY_ URI ) ; } @Over ride public boolean onC reate ( ) return t rue;
} @Over ride public int bulkinsert ( U ri a rg O , ContentValues [ ) values) { return O ; //niczego nie wstawiamy
} /!Zbiór kolumn
wymaganych przez aktywnyfolder
//Jest to kontrakt aktywnegofolderu private static final St ring [ ) CURSOR COLUMNS { BaseColumns ._ID , LiveFolde rs . NAME, LiveFolde r s . DESCRIPTION, LiveFolde rs . INTENT, LiveFolders . ICON PACKAG E , LiveFolders . ICON_ RESOURCE };
new String [ )
/IW przypadku braku krotek //wp rowadzamy zastępstwo w postaci komunikatu o błędzie //Zauważmy, ze posiada taki sam zbiór kolumn, jak aktywnyfolder private static final St ring [ ) CURSOR ERROR_COLUMNS = new String [ ) { BaseColumn s . I D , LiveFolders . NAME, LiveFolders . DESCRIPTION
}; //Krotka komunikatu o błędzie p rivate s t a t i c final Obj ect [ ) ERROR_MESSAGE ROW new O b j ect [ J { //identyfikator -1, //nazwa "Nie znaleziono kontaktów" , //opis " S p ra11dż bazę kontaktów"
}; //Stosowany kursor błędu
private static MatrixCursor s E r ro r C u r s o r new 4MatrixC u rso r ( CURSOR_ERROR COLUMNS ) ; static { =
463
464
Android 2. Tworzenie aplikacji
sErrorCu rso r . addRow( ERROR_MESSAGE_ ROW) ;
} //Kolumny odczytywane z bazy kontaktów private static final String [ ) CONTACTS COLUMN NAMES { People. ID , People . DISPLAY_NAME , People . TIMES_CONTACTED, People. STARRED };
new String [ )
public C u rs o r query ( U ri u ri , String [ ) projection, String selection, String [ ] selectionArg s , String sortOrder) { //Sprawdza identyfikator uri i zwraca błąd, jeżeli nie znajdzie dopasowania
int type = URI_MATCHER . match ( u ri) ; i f ( type == U riMatch e r . NO_MATCH )
{
return s E rrorCursor;
} Log . i ( " s s " , " kwerenda wywołan a " ) ; t ry { MatrixCursor me = loadNewData( this ) ; mc . setNotificationU r i ( getContext ( ) . getContentResolve r ( ) , U r i . parse ( " content : //contacts/people/ " ) ) ; MyCursor �1mc = new MyC u r s o r ( m c , this ) ; return wmc;
}
catch (Th rowable e )
{
return s E rrorCu rso r ;
} } public static MatrixCursor loadNewDat a ( ContentProvider c p )
{
MatrixCursor me = new MatrixC u r s o r ( CURSOR_COLUMN S ) ; C u r s o r allContacts null; try { allContacts c p . getContext ( ) . getContentResolve r ( ) . query( People . CONTENT_URI , CONTACTS COLUMN_NAMES, n u l l , //filtr krotek null, People . DISPLAY_NAME) ; //sortowanie =
=
while (allContact s . moveToNex t ( ) ) { String timesContacted = " Nawiązane połączenia : "+allContact s . getint ( 2 ) ;
Rozdział 1 2 • Badanie aktywnych folderów
Object [ J rowObject new Object [ ] { allContact s . getlong ( O ) , allConta ct s . getString ( l ) , timesContacted, U ri . pa rse ( " content : //contacts/people/" +allContact s . getlong ( O ) ) , c p . getContext ( ) . getPackageName ( ) , R . d rawable . icon }; mc . addRow( rowOb j e ct ) ;
465
=
//identyfikator //nazwa //opis //id. uri intencji I/pakiet I/ikona
}
return m e ; finally
{
allContact s . close( ) ;
} @Over ride public String getType ( U ri u r i ) { //wskazuje typ MIME danego identyfikatora URI //zdefiniowanego dla osłonowego dostawcy //Typ ten wygląda zazwyczaj następująco: li "vnd.android.cursor.dir/vnd.google.note" return People . CONTENT_TYPE ; }
public Uri insert ( U ri u r i , ContentValues initialValues ) { t h row new UnsupportedOperationException( " ni c nie zostaje wstawione , poniewa± j est to wyłącznie otoczka " ) ;
} @Over ride public int delet e ( U ri u r i , String selection , String [ ] selectionArgs) { t h row new UnsupportedOperationException ( " nic nie zostaje usunięte, ponieważ j es t to wyłącznie otoczka " ) ;
} public int update ( U ri u r i , ContentValues value s , String selection , String [ ] selectionArgs) { t h row new UnsupportedOperationException( "nic nie zostaje zaktualiz01�ane, ponieważ j es t to wyłącznie otoczka " ) ;
} }
Zaprezentowany w listingu 12.4 zbiór kolumn zawiera standardowe kolumny, wymagane przez strukturę aktywnego folderu.
466
Android 2. Tworzenie aplikacji
Listing 1 2.4. Kolumny pot rze bne do wypełnienia kontraktu aktywnego fold e ru p rivate static final String [ ] CURSOR COLUMNS = new Strin g [ ]
{
BaseColumns . I D , LiveFolde rs . NAME, LiveFolders . DESCRIPTION, LiveFolde rs . INTENT, LiveFolde rs . ICON_PACKAGE, LiveFolde rs . ICON_ RESOURCE
};
Poza elementem INTENT, przeznaczenie pozostałych obiektów jest oczywiste. Jeżeli przyj rzymy się rysunkowi 12.5, zauważymy, że obiekt NAME dotyczy nazwy elementu na liście. Obiekt DESCRIPTION został umieszczony na tej samej liście pod obiektem NAME. Pole INTENT jest w rzeczywistości polem typu string, wskazującym identyfikator URI danego elementu w dostawcy treści. W przypadku kliknięcia tego elementu Android zastosuje działan ie VIEW poprzez ten identyfikator URI. Dlatego właśnie pole to nosi nazwę pola INTENT, ponieważ wewnętrznie Android uzyska obiekt INTENT z identyfikatora URI. Dwa ostatnie elementy są związane z obiektem ICON, v.ryświetlanym jako część listy. Przyj rzyjmy się ponownie rysunkowi 12.5, aby zobaczyć ikony, oraz listingowi 12.3, aby spraw dzić, w jaki sposób kolumny te dostarczają wartości z bazy kontaktów. Zwróćmy również uwagę, że omówiona pm\ryżej klasa MyCont a c t sContentP rovider (osło nov.ry dostawca treści) v.rykonuje kod z listingu 12.5 wymuszający na podstawowym kurso rze pilnowanie wszelkich zmian danych. Listing 12.5. Rejestrowanie identyfikatora
URI
za pomocą kursora
MatrixCursor me = loadNewDa t a { this ) ; m c . setNotificationUri { g etContext { ) . g etContentResolve r { ) ,
U ri . parse ( " content : //contacts/people/ " ) ) ;
Funkcja loadNewData ( ) uzyskuje od dostawcy treści zbiór kontaktów i tworzy obiekt Mat rixCu rsor, którego kolumny są widoczne w listingu 12.4. Następnie obiekt ten zostaje poinstruowany, że ma się zarejestrować wraz klasą ContentResolver, aby mogła ona po wiadomić kursor o jakiejkolwiek zmianie danych wskazywanych przez identyfikator URI (content : // contacts/people).
I n teresujący jest fakt, że obserwowanym identyfikatorem URI jest nie identyfikator naszego dostawcy Lreści MyCont a c t s P rovider, lecz identyfikator dostawcy treści kontaktów dostar czony przez Androida. Wynika to z faktu, że dostawca MyCont a c t s P ro v i d e r stanowi jedy nie osłonę „prawdziwego" dostawcy treści. Zatem kursor ten musi obserwować właściwego dostawcę treści, a nie jego osłonę. Ważne jest również, aby otoczyć obiekt M a t rixC u r s o r we własnym kursorze, co zostało po kazane w listingu 12.6.
Rozdział 1 2 • Badanie aktywnych folderów
467
Listing 1 2.6. Osłanianie k urs ora MatrixCursor me = loadNewDat a ( t h is ) ; m c . setNotificationUri( getContext ( ) . getContentResolve r ( ) , U ri . pa rs e ( " content : //contacts/people/ " ) ) ; MyCursor wmc = new MyCursor(mc , this ) ;
Aby zrozumieć sens osłaniania kursora, musimy dowiedzieć się, w jaki sposób widoki prze prowadzają aktualizację zmienionej treści. Taki dostawca treści, jak Contacts, zazwyczaj informuje kursor, że musi obserwować zmiany poprzez zarejestrowanie identyfikatora URI jako części implementacji metody q u e ry. Jest to do ko nywane za pomocą obiektu cu rso r . setNoti f i c a t i o n U ri. Kursor może następnie zarejest rować ten identyfikator URI oraz jego wszystkie podrzędne identyfikatory wraz z dostawcą treści. Podczas przeprowa dzenia operacji wstawienia lub usunięcia na dostawcy treści kod odpowiedzialny za te ope racje musi wprowadzić zdarzenie oznaczające zmianę danych w krotkach definiowanych przez określony identyfikator URI.
W ten sposób kursor będzie aktualizowany za
pomocą operacji requery, a widok zostanie stosownie odświeżony. Niestety, obiekt Mat rixCu rso r nic jest dostosowany do operacji reque ry. Obsługuje j<} kursor SQLiteCu r s o r, jednak nie możemy z niego tutaj skorzystać, ponieważ odwzorowujemy kolumny zgodnie z nowym zestawem kolumn.
Aby pominąć to ograniczenie, umieściliśmy obiekt Mat rixCu r s o r w osłonie kursora i prze słoniliśmy metodę requery w celu pozostawienia tego obiektu i utworzenia nowego, zawie rającego zaktualizowane dane. Chcemy również, żeby przy każdej zmianie danych był gene rowany nowy obiekt MatrixCursor. Jednakże do struktury aktywnego folderu w Androidzie zwracamy jedynie zewnętrzny kursor osłaniający. Szkielet aktywnego folderu będzie wiedział tylko o jednym kursorze, w j ego wnętrzu jednak będą pojawiały si ę nowe kursory w m iarę wprowadzania zmian w danych. Zajmują się tym dwie następne klasy.
MyCursor.java Zauważmy, w jaki sposób jest inicjowany obiekt MyC u r s o r zawierający na początku klasę ( listing 12.7). Podczas przeprowadzania operacji reque ry kursor MyCu rso r będzie zwrotnie wywoływał dostawcę w celu zwrócenia obiektu MatrixC u r s o r. Nowy obiekt Mat rixCu rso r zastąpi obiekt stary za pomocą metody s e t . Mat rixCu r s o r
Listing 12.7. Kod źródłowy klasy MyCursor public class MyC u rs o r extends BetterC u rs o rWrapper
{
private ContentProvider mcp
null;
public MyCu rsor(Mat rixCu r s o r m e , ContentProvider inCp )
{ s u pe r ( m c ) ; mcp = inC p ;
} public boolean requery ( ) { Mat rixCu r s o r m e = MyContact s P rovide r . loadNewDat a ( m c p ) ;
Android 2. Tworzenie aplikacji
468
this . setinternalCurso r ( mc ) ; return super. requery ( ) ; }
Moglibyśmy tego dokonać, przesłaniając metodę reque ry klasy Mat rixCu r s o r, klasa
• ta nie może jednak w żaden sposób wyczyścić danych i rozpocząć działania od początku ·• 1' •'11 •r• 11 r.i•il•r·1-•
.
Jest to więc rozsądne obejście (zwróćmy uwagę, że obiekt My Cu r s o r rozszerza klasę Bet te rCu r s o rWrappe r, co zostanie omówione w dalszej części rozdziału).
Przyjrzymy się teraz klasie Bet te rCu r s o rWrappe r, aby poznać technikę osłaniania kursora.
BetterCursorWrapper .java Klasa Bet te rCu rso rWrappe r (listing 12.8) znacznie przypomina klasę Cu rso rWrappe r struktury bazodanowej w Androidzie. Potrzebujemy jednak dwóch elementów, których brakuje klasie Cu rso rWrappe r. Po pierwsze, nie zawiera ona metody set, służącej do zastąpienia wewnętrznego kursora, pochodzącego z metody requery. Po drugie, obiekt C u r s o rWrapper nie jest klasą C rossProcessCu rsor. Aktywne foldery wymagają klasy C ros s P r o ces s C u r s o r, a nie zwykłego kursora, ponieważ przekraczają one granice procesów. Listing 1 2.8. Kod źródłowy klasy BetterCursorWrapper public class BetterCu rsorWrapper implements CrossProcess C u r s o r {
//Przechowuje wewnętrzny kursor służący do delegowania metod protected CrossProces sCursor internalCurs o r ; //Konstruktor pobiera obiekt crossprocesscursor w postaci danych wejściowych public BetterC u rsorWrappe r ( C ro s s P roces sCursor inCu rsor) {
this . setinternalCurso r ( inCursor ) ;
}
//Możemy zresetować wjednej z metod klasy pochodnej public void setinte rnalCu rso r ( CrossProcessCursor inCursor) {
internalCursor = inCu rsor;
//Tu znajdują się wszystkie delegowane metody public void fillWindow ( int a rg O , CursorWindow argl) { internalCu rso r . fillWindow ( a rg O , a rg l ) ; } li ... . inne delegowane metody .
}
Nie pokazaliśmy całej klasy, można ją jednak łatwo wygenerować w środowisku Eclipse. Po wczytaniu powyższego fragmentu umieszczamy kursor w zmiennej inte rnalC u r s o r. Kli kamy prawym przyciskiem myszy i wybieramy opcje Source/Generate Delegated Methods.
Rozdział 1 2 • Badanie aktywnych folderów
469
W ten sposób zostanie zapełniona reszta klasy. Zademonstrujemy teraz prostą aktywność, wymaganą do dokończenia projektu testowego. SimpleActivity.java Plik SimpleActivity.java (listing 12.9) nie jest klasą niezbędną do tworzenia aktywnych fol derów, jednak po dołączeniu jej do projektu może ona posłużyć jako szablon dla następnych projektów. W dodatku możemy dzięki niej wdrożyć aplikację i obserwować ją na ekranie podczas wyszukiwania błędów w środowisku Eclipse.
listing 1 2.9. Kod źródłowy kl a sy Simpl eActiv i ty public class SimpleActivity extends Activity { @Ove r ride public void onCreate(Bundle saved!nstanceState) { supe r . on C reate ( s avedinstanceState ) ; setContentVie11 ( R . layout . mai n ) ;
} Możemy wykorzystać dowolny, prosty układ graficzny XML, zdefiniowany w pliku main.xml.
W listingu 12.10 pokazujemy przykład. listing 12.1 O. Prosty plik XML układu graficznego c?xml version= " l . O " encoding="utf - 8 " ?> cTextVie11 androi d : layout_11idth="fill_parent" android : layout_height="11rap_content" and roid : text="P rzyklado11y a kty1�ny folder" />
Posiadamy teraz wszystkie klasy niezbędne do zbudowania, wdrożenia i uruchomienia przykładowego projektu aktywnych folderów w środowisku Eclipse. Podsumujmy ten podroz dział omówieniem zjawisk zachodzących podczas uzyskania dostępu do aktywnego folderu.
Testowanie aktywnych folderów Po przygotowaniu wszystkich plików projektu aktywnych folderów możemy je skompilo wać i wdrożyć na emulatorze. Po wdrożeniu aplikacji poprzez środowisko Eclipse w emu latorze zostanie wyświetlona prosta aktywność. Jesteśmy teraz gotowi do wykorzystania utworzonego przez nas aktywnego folderu.
470 Android 2. Tworzenie aplikacji Przejdźmy na stronę początkową urządzenia, powinna ona przypominać ekran z rysunku 1 2 . 1 . Przeprowadźmy czynności wypunktowane na początku podrozdziału W jaki sposób użytkownik odbiera aktywne foldery". Zlokalizujmy zwłaszcza nasz aktywny folder i utwórzmy jego ikonę, tak jak pokazano na rysunku 12.4. Kliknijmy ikonę Kontakty AF, a ujrzymy listę zapełnioną kontaktami, podobnie jak na rysunku 1 2.5. „
Podsumowanie Dzięki aktywnym folderom otrzymujemy innowacyjny, obsługiwany jednym kliknięciem mechanizm, v.ryświetlający zmienione dane na stronie początkowej. Potencjalnie możemy umieszczać dane dowolnego rodzaju, dopóki istnieje możliwość ich zdefiniowania w postaci listy tworzonej przez wiersze. Wszystkie dane muszą posiadać właściwości nazwy i opisu, pozwalające na ich zidentyfikowanie. Niemal każdy typ danych spełnia ten wymóg, gdyż mogą one zostać w jakiś sposób nazwane i opisane. Przydatna okazuje się także obecność aktywności wyświetlającej szczegółowe informacje na temat klikniętego obiektu w aktywnym folderze. Dane mogą być lokalne, na przykład kontakty, a nawet sieciowe, czego przykładem może być spis blogów. W rozdziale pokazaliśmy również, w jaki sposób Android wykorzystuje aktywność do uzy skania identyfikatora URI treści dla aktywnego folderu. Z kolei identyfikator ten stanowi podstawę do otrzymania zbioru wierszy, wyświetlanych jako zawartość aktywnego folderu. Zademonstrowaliśmy, jak można zaimplementować dostawcę treści dostarczającego dane do aktywnego folderu w oparciu o wspomniany identyfikator URI. Rozdział ten zawiera także opis nowości w kursorach aktywnych folderów oraz mechani zmów wymaganych do wyeksponowania istniejących dostawców treści jako źródeł aktyw nych folderów. Wyjaśniliśmy przyczyny osłaniania kursorów, a także zaprezentowaliśmy sposób rejestrowania klasy ContentResolver w celu otrzymywania aktualizacji.danrch. Następny rozdział został poświęcony kolejnej innowacji strony początkowej, znanej pod nazwą widgetów strony początkowej (ang. Home widget).
ROZDZIAŁ
13 Wid gety ekranu początkoweg o
W niniejszym rozdziale szczegółowo omówimy zagadnienie widgetów ekranu początkowego. Podobnie jak aktywne foldery, tak i widgety ekranu początkowego stanowią kolejny sposób przedstawiania często aktualizowanych danych na stronie początkowej urządzenia zaopatrzonego w system Android. Generalnie widgety ekranu początkovvego są odłączonymi widokami (chociaż wypełnio nymi danymi), wyświetlanymi na ekranie początkowym. Dane znajdujące się w tych widokach są aktualizowane w regularnych odstępach czasu przez pro cesy zachodzące w tle. Na przykład widget poczty elektronicznej może informować użytkownika o ilości nieprzeczytanych wiadomości. Jednak należy pamiętać, że widget będzie przedsta wiał jedynie ;,liczbę" tych wiadomości, a nie ich treść. Kliknięcie licznika wia domości przeniesie nas do aktywności wyświetlającej właściwe wiadomości. Mogą to być nawet zewnętrzne źródła poczty e-mail, na przykład Yahoo, Gmail lub Hotmail, dopóki urządzenie posiada możliwość połączenia się z serwerem poczty za pomocą protokołu HTTP albo innego mechanizmu sieciowego. Rozdział podzielimy na trzy części. W pierwszym podrozdziale omówimy po jęcie widgetów ekranu początkowego oraz ich architekturę. Wyjaśnimy, w jaki sposób Android wykorzystuje widok RernoteView do wyświetlania widgetów oraz dołącza odbiorniki transmisji, służące do aktualizowania tych widoków. Poka żemy technikę tworzenia aktywności umożliwiających konfigurowanie widge tów na ekranie początkowym oraz zaobserwujemy związek pomiędzy usługami a widgetami. Po przeczytaniu tego podrozdziału będziemy dobrze rozumieć cykl życia widgetów ekranu początkowego. W drugim podrozdziale zademonstrujemy sposób projektowania i rozwijania widgetów ekranu początkowego za pomocą kodu zaopatrzonego w komentarz. Czytelnicy dowiedzą się, jak można definiować widgety w Androidzie oraz jak pisać kod tworzący odbiorniki transmisji aktualizujące te widgety. Pokażemy metody zarządzania stanem widgetu za pomocą współdzielonych preferencji oraz przedstawimy kod aktywności służącej do konfigurowania widgetów.
472
Android 2. Tworzenie aplikacji
W podrozdziale trzecim zajmiemy się kwestiami przydatności, ograniczeń oraz ogólnych wskazówek ułatwiających pracę z widgetami. W tej części omówimy zakres i stosowalność widgetów. Przedstawimy również porady dotyczące pisania widgetów wymagających bardzo częstych aktualizacji. Rozdział zakończymy listą zasobów dotyczących programowania widgetów w Androidzie.
Architektura widgetów ekran u początkowego Rozpocznijmy omówienie architektury widgetów ekranu początkowego od ustalenia ich do kładnej definicji.
Czym są widgety ekranu początkowego? Jak zostało wspomniane we wstępie, widgety ekrany początkowego są często aktualizowanymi widokami, wyświetlanymi na ekranie początkowym. Skoro widget ekranu początkowego jest widokiem, jego v.rygląd i działanie są definiowane w pliku XML układu graficznego. Poza tym układem graficznym będziemy musieli jeszcze zdefiniować ilość zajmowanej przez widget przestrzeni na ekranie.
W definicji widgetu zostało zawartych również kilka klas Java, odpowiedzialnych za inicja cję widoków i jego częste aktualizacje. Klasy te zarządzają cyklem życia widgetu na ekranie początkowym. Reagują one na procesy przeciągnięcia widgetu na ekran początkov.ry oraz jego usunięcia do kosza.
''ł"wr• 111111
-11 • 1· -
Widok oraz odpowiadająca mu klasa Java są zaprojektowane w taki sposób, że obydwa obiekty są od siebie oddzielone. Na przykład każda usługa l u b aktywność w Androidzie może odczytać widok za pomocą identyfikatora jego układu graficznego oraz wypełnić go danymi (podobnie jak w przypadku wypełniania szablonu), a następnie wysłać go na ekran początkowy. Po przesłaniu widgetu na ekran początkowy zostaje on oddzielony od obsługującego go kodu Java.
Najprostsza definicja widgetu zawiera następujące elementy: • Układ graficzny widoku wyświetlany na ekranie początkowym, a także określony rozmiar jego dopasowania na ekranie początkowym. Pamiętajmy, że jest to jedynie widok bez żadnych wstawionych danych. Zadaniem klasy Java będzie jego aktualizacja. •
Zegar określający częstotliwość aktualizacji.
• Klasa Java nazywana „dostawcą widgetu" (ang. widget provider), reagująca na aktualizacje zegara w celu zmiany widoku w określony sposób, umożliwiający zapełnienie go danymi. Po zdefiniowaniu widgetu oraz wprowadzeniu klas Java będzie on zdatny do użytku. Naj pierw jednak omówimy wrażenia użytkownika podczas korzystania z widgetu ekranu po czątkowego.
Rozdział 13 • Widgety ekranu początkowego
473
Wrażenia użytkownika podczas korzystania z widgetów ekranu początkowego Wśród funkcji widgetu ekranu początkowego w Androidzie znajduje się możliwość umiesz czenia widgetu na ekranie początkowym. Po umieszczeniu na ekranie początkowym można go w razie konieczności skonfigurować za pomocą aktyvmości. Rozpocznijmy od zlokalizo wania poszukiwanego widgetu i utworzenia jego instancji na ekranie początkowym.
Utworzenie instancji widgetu na ekranie początkowym Aby uzyskać dostęp do listy dostępnych widgetów, należy zastosować długie kliknięcie na ekra nie początkowym. Zostanie vqświetlone menu kontekstowe ekranu początkowego, przedsta wione na rysunku 13.1.
Skróty Widżety Foldery
Rysunek 1 3 . 1 . Menu kontekstowe ekranu początkowego Po wybraniu opcji Widgets pojawi się kolejny ekran, zawierający listę dostępnych widgetów, co zostało przedstawione na rysunku 13.2. Większość widgetów stanowi integralną część Androida. Lista dostępnych widgetów może wyglądać inaczej w zależności od wersji używanego oprogramowania. W celach demonstracyj nych wybraliśmy widok Bi rthday Widget. Po jego kliknięciu zostanie utworzona odpowiednia instancja widgetu na ekranie początkowym, wyglądająca jak przykładowy obiekt Urodziny z rysunku 13.3. Wybraliśmy widget powiadamiający o urodzinach. W jego nagłówku są wyświetlane takie dane, jak imię osoby, liczbę dni do jej urodzin, a także data urodzenia oraz łącze do sklepu z upominkami.
1111111
Widok tej definicji widgetu utworzony na ekranie początkowym nosi nazwę instancji
• w r· r• _ . , . , - widgetu. Wynika z tego wniosek, że można utworzyć więcej definicji instancji tego widgetu.
4 74
Android 2. Tworzenie aplikacji
Muzyka
�
0
�f ..
-\..;·
Ramka zdjęcia U rod ziny Wyszukiwarka Zarządzanie energią
Rysunek 13.2. Lista widgetów ekranu początkowego
Rysunek 1 3.3. Przykładowy widget
Konfigurator widgetów Doszliśmy do etapu, w którym powinniśmy •vprowadzić wspomniany uprzednio konfigurator widgetów. Definicja widgetu może opcjonalnie zawierać specyfikację aktywności, zwanej aktywnością konfiguratora widgetów. Po wybraniu widgetu z listy dostępnych widgetów w celu utworzeniu jego instancji Android wywołuje powiązaną z nim aktywność konfigura cji widgetu. Taka samodzielnie napisana aktywność jest odpowiedzialna za konfigurację wy stąpienia widgetu.
Rozdział 1 3 • Widgety ekranu początkowego
475
W przypadku naszego widgetu urodzinowego aktywność konfiguracji poprosi o wprowa dzenie imienia jubilata oraz daty jego urodzin, tak jak zostało pokazane na rysunku 13.4. Zadaniem konfiguratora jest zapisanie tych informacji w stałym miejscu, aby po wywołaniu aktualizacji przez dostawcę widgetu dostawca ten mógł zlokalizować informacje i zaktuali zować je o nowe wartości, wstawiane następnie do widgetu przez konfigurator.
np. 10/1 /2009
Rysunek 1 3.4. Aktywność konfig uratora widgetu Jeżeli użytkownik wybierze utworzenie dwóch i n stancj i widgetu urodzinowego na
• •• 1 w 1 r1 r • ekranie początkowym, aktywność konfiguratora zostanie wywołana dwukrotnie • • • • • •" Uednorazowo dla każdego wystąpienia widgetu).
Android wewnętrznie śledzi instancje widgetów poprzez przypisy-wanie im identyfikatora. Identyfikator ten jest przekazywany wywołaniom zwrotnym kodu Java oraz klasie konfigu racyjnej w celu skierowania aktualizacji do właściwej instancji. Na rysunku 13.3, w ciągu znaków satya : 3, cyfra 3 stanowi identyfikator widgetu - a ściślej identyfikator instancji widgetu. Sam zaś widget jest identyfikowany po jego nazwie (na którą składa się nazwa klasy oraz pakietu, w którym ta klasa przebywa; w tym rozdziale pojęcia „identyfikator widgetu" oraz „identyfikator instancji widgetu" są używane ·wymiennie). Identyfikator instancji wid getu został zamieszczony na rysunku 13.3 w celach demonstracyjnych. Po ogólnym omówieniu ·widgetu nadszedł czas na szczegółowe zapoznanie się z jego cyklem życia.
Cykl życia widgetu Wspomnieliśmy kilkakrotnie o definicji widgetu. Poruszyliśmy także temat roli klas Java. W tym ustępie poświęcimy o wiele więcej uwagi obydwu zagadnieniom oraz prześledzimy cykl ży cia widgetu. Cykl ten składa się z następujących faz:
476 Android 2. Tworzenie aplikacji 1 . definiowanie widgetu, 2. tworzenie instancji widgetu, 3. zastosowanie metody onUpdate ( ) (po wygaśnięciu interwału czasowego),
4. odpowiedź na kliknięcia (w widoku widgetu na ekranie początkowym), 5. usunięcie widgetu (z ekranu początkowego), 6. odinstalowanie.
Omówimy teraz szczegółowo każdy z wymienionych etapów.
Faza definiowania widgetu Cykl życia widgetu rozpoczyna się od zdefiniowania widoku widgetu. W definicji tej okre ślamy nazwę widgetu wyświetlaną na liście dostępnych widgetów (rysunek 13.2), wywoływanej z poziomu ekranu początkowego. Do utworzenia definicji wymagane są dwa elementy. Po trzebujemy klasy Java implementującej dostawcę AppWidgetProvider oraz układu graficz nego widgetu. Po uhvorzeniu obydwu plików możemy zdefiniować widget w Androidzie. Rozpoczniemy definiowanie widgetu od następującego wpisu w pliku manifeście, definiują cego obiekt AppWidgetP rovide r (listing 13.1 ). Listing 1 3. 1 . Definicja widgetu w pliku manifeście Androida .
.
-
Definicja ta wskazuje istnienie odbiornika transmisji klasy Java noszącego nazwę BDa yWid g et '+ P rovider (jak się przekonamy, wywodzi się on z podstawowej klasy Androida AppWidget '+Provider mieszczącej się w pakiecie widget), który odbiera komunikaty zawierające aktu alizacje widgetu.
"'%"§" 111111
Android dostarcza informacje o aktualizacji w formie komunikatów rozgłoszeniowych na podstawie częstości interwałów czasowych.
Definicja widgetu z listingu 13.1 jest również związana z plikiem XML w katalogu /res/xml, który z kolei określa widok widgetu oraz częstotliwość odświeżania, co zostało zaprezento wane w listingu 13.2.
Rozdział 1 3 • Widgety ekranu początkowego
477
Listing 13.2. Definicja wi do ku wi dg etu w pliku XML informa cji o dostawcy wid g et u
Plik ten jest nazywany plikiem informacji o dostawcy widgetu. Zostaje on wewnętrznie przetłu maczony na klasę Java AppWi d g e t P rovide r! nfo. Wartości szerokości i wysokości układu graficznego zostają tu ustalone odpowiednio na lSOdp i 120dp. Zostaje tu również określony wyrażony w milisekundach czas przedziału czasowego równy 12 godzinom. Definicja ta wska zuje także plik układu graficznego (listing 13.7), opisujący widok widgetu (rysunek 13.5). Zauważmy jednak, że układ graficzny tych widoków widgetów może posiadać jedynie nie które typy elementów widoku. Złożony widok w:idgetu podpada pod !
Linearlayout Relativelayout AnalogClock Button Chronometer ImageButton ImageView ProgressBar TextView
Powyższa lista może różnić się w zależności od wersji środowiska SDK. Zasadniczym po wodem ograniczenia dopuszczalnych typów elementów w widoku zdalnym jest fakt, że są one odcięte od kontrolujących je procesów. Te widoki widgetów są obsługiwane przez takie aplikacje, jak na przykład Home. Kontrolerami tych widoków są przetwarzane w tle proce sy, wywoływane przez zegary. Z tego widoku obiekty te noszą nazwę widoków zdalnych. Istnieje odpowiednia klasa Java, nazwana RemoteViews, udzielająca dostępu do tych wido ków. Innymi słowy, programiści nie muszą otrzymywać bezpośredniego dostępu do tych widoków, aby wywołać wobec nich metody. Dostęp do nich posiadamy wyłącznie poprzez klasę RemoteViews (pełni rolę bramki). Metody klasy RemoteViews zostaną omówione w następnym podrozdziale, podczas oma wiania przykładowego widgetu. Na razie wystarczy pamiętać, że w pliku układu graficznego widgetu możemy umieścić ograniczony zestaw widoków (listing 13.3). Definicja widgetu (listing 13.2) zawiera również specyfikację aktywności konfiguracyjnej, która musi zostać wywołana podczas tworzenia instancji widgetu przez użytkownika. Ta aktywność nosi nazwę C o n f i g u reBDayWidgetActivity w listingu 13.2. Jest to standardowa
478
Android 2. Tworzenie aplikacji
aktywność zawierająca kilka pól formularza. Pola te mają na celu uzyskanie od użytkownika informacji wymaganych przez instancję widgetu.
Faza tworzenia i nstancji widgetu Po utworzeniu wszystkich elementów XML wymaganych przez definicję widgetu oraz udo stępnieniu wszystkich klas Java widgetów możemy się przyjrzeć, co się stan ie po wybraniu przez użytkownika nazwy widgetu z listy dostęp n ych widgetów (rysunek 13.2). Android wywołuje aktywność konfiguratora (rysunek 13.3) i wymaga od niej przeprowadzenia na stępujących czynności: L
Otrzymanie identyfikatora in stancji widgetu od intencji wyw"Ołującej, która uruchomiła konfigurator.
2. Zebranie informacji potrzebnych instancji widgetu poprzez wyświetlenie formularza użytkownikowi.
3. Zachowanie u zyskanych przez widget informacji w celu uzyskania do nich dostępu podczas wywołania metody update.
4. Przygotowanie do v,ryświetlania widoku widgetu po raz pierwszy poprzez odczytanie układu graficzn ego tego widoku i utworzenie obiektu RemoteViews. 5. Wywołan ie metod klasy RemoteViews ustanawiających wartości pojedync zych
,
obiektów widoku takich jak tekst, obraz i tak dalej.
6. Wyko rzystanie obiektu RemoteViews do zarejestrowania wszelkich zdarzeń o n C l i c k wobec dowolnego podelem en tu widgetu. 7. Zmuszenie ob iektu AppWidgetManag e r do narysowania klasy RemoteViews na ekranie początkowym z wykorzystaniem identyfikatora instancji tego widgetu. 8. Powrót do identyfik a tora widgetu i zakończenie działania. Po powrocie z tej aktywn ości Android narysuje widok widgetu zgodnie z wytycznymi kon figuratora. Zwróćmy uwagę, że w tym przypadku pierwsze rysowanie jest wyko na n e przez konfigurator, a nie przez metodę onUpdate( ) klasy AppWidget Provider.
Aktywność konfiguratora jest elementem dodatkowym. Jeżeli nie zostanie ona
"'rS:Ofz zdefiniowana, wywołanie przejdzie bezpośrednio do metody onUpdate ( ) , nawet za
pierwszym razem. Do metody onUpdate ( ) należy obowiązek aktualizowania widoku.
Android będzie powtarzał ten proces dla każdej i n stan cj i widgetu utwo rzo nej przez użyt kownika. Warto również zauważyć, że nie istnieją udokumentowane ograniczenia zmusza jące użytkownika do korzystania z tylko jednej instan cj i widgetu. Poza przywoływaniem aktywności ko n figurato ra Android '"'Y'.Vołuje zwrotnie również me todę onEnabled klasy AppWidgetProvider. Poświęćmy chwilę wywołan iom zwrotnym klasy AppWidget Provider poprzez przyjrzenie się powłoce naszego dostawcy BDayWidgetProvider (listing 13.4). P ełny kod tego pliku umieszczony został w listingu 13.9.
Listing 13.4. Powłoka dostawcy widgetu public class BDayWidgetProvider extends AppWidgetProvider { public void onUpdate( Context context,
Rozdział 1 3 • Widgety ekranu początkowego
}
479
AppWidgetManager appWidgetManag e r , i nt [ ] ap pWidget ids ) { } public void onDeleted ( Context context, int [ ] appWidgetids ) { } public void onEnabled( Context context ) {} public void onDisabled(Context cont ex t ) { }
Metoda zwrotna onEnabled ( ) wskazuje istnienie co najmniej jednej instancji widgetu dzia łającej na ekranie początkov,rym. Oznacza to, że użytkownik musiał umieścić na ekranie po czątkowym przynajmniej jeden widget. Musimy w tym wywołaniu uruchomić otrzymywanie komunikatów dla tego składnika (z listingu 13.9 dowiemy się, jak tego dokonać). W Androidzie klasy są nazywane czasami składnikami, szczególnie w przypadku, gdy tworzą wielokrotnie wykorzystywane jednostki, takie jak aktywność, usługa lub odbiorca transmisji. W naszym przypadku klasa AppWidgetProvider jest składnikiem odbiorcy transmisji; możemy ją włą czać lub ·wyłączać w celu otrzymywania transmitowanych komunikatów. Metoda zwrotna onDeleted ( ) jest wywoływana podczas przeniesienia przez użytkownika instancji widgetu do kosza. To właśnie tu musimy usunąć wszystkie przechowywane warto ści dla instancji widgetu. Metoda zwrotna onDisabled ( ) zostaje wywołana po usunięciu ostatniej instancji widgetu z ekranu początkowego. Następuje to w momencie przeniesienia ostatniej instancji do kosza. Powinniśmy używać tej metody do wyrejestrowania procesu otrzymywania transmitowa nych komunikatów przez ten składnik (zobaczymy to w listingu 13.9). Metoda zwrotna onUpdate ( ) jest wywoływana za każdym razem, gdy wygaśnie zegar zapre zentowany w listingu 13.2. Metoda la jest również wywoływana na samym początku, pod czas genero,vania instancji widgetu w przypadku, gdy nie zdefiniowaliśmy aktywności kon figuratora. Jeżeli aktywność konfiguratora jest dostępna, ta metoda nie jest wywoływana podczas procesu utworzenia instancji widgetu. Następnie będzie ona wywoływana z częstotliwością równą wygaśnięciom zegara.
Faza metody onU pdate Po wyświetleniu instancji widgetu na ekranie początkowym następnym ważnym zdarze niem jest wygaśnięcie zegara. Jak już wspomnieliśmy, zostaje wtedy wywołana metoda onUpdate ( ) . Do jej wywołania służy odbiornik transmisji. Oznacza to, że zostanie wczytany właściwy proces Java, w którym jest zdefiniowana metoda onUpdate ( ) , i będzie on trwał aż do chwili zakończenia wywołania. Po zwrocie wywołania proces ten będzie gotowy do za mknięcia. Zalecane jest także utworzenie usługi lokalnej i wyznaczenie jej do pracy w przypadku, gdy obawiamy się czasochłonnego przetwarzania odpowiedzi. W ten sposób umożliwiamy po wrót wątku transmitowania. Usługa będzie działała w oddzielnym wątku, przeznaczonym dla jej procesu. W każdym razie po otrzymaniu danych w metodzie onUpdate ( ) możemy przywołać klasę AppWidgetManager, aby narysowała zdalny widok, który zostanie zaktualizowany otrzyma nymi informacjami. Gdybyśmy do przeprowadzenia aktualizacji wykorzystali usługę, mu sielibyśmy przekazać intencji uruchamiającej tę usługę identyfikator widgetu w postaci do datkowych danych.
480
Android 2. Tworzenie aplikacji
Chcemy w ten sposób pokazać, że klasa AppWidgetProvider jest bezstanowa i być może nie będzie nawet zdolna do utrzymania zmiennych statycznych pomiędzy wywołani am i. Powo dem jest możliwość zamknięcia procesu Java zawierającego tę klasę odbiornika tran smisj i oraz jego rekonstrukcji pomiędzy dwoma wywołaniami, co powoduje ponowną inicjację zmien nych statycznych. W wyniku tego musi my w razie potrzeby wprowadzić schemat zapamiętywania stanów. Je
żeli aktualizacje nie są zbyt częste - powiedzmy, co kilka sekund - sensownym rozwiąza niem jest zapisywanie stan u instancj i widgetu w trwałym magazynie, na przykład pliku, we współdzielo n ych preferencjach lub w bazie danych sqlli te. W następnym przykładzie wykorzystamy do tego celu współdzielone preferencje.
1+66Hi
Aby zaoszczędzić energię, bardzo zalecane jest ustalenie interwalów aktualizacji dłuższych niż godzina, wskutek czego urządzenie nie będzie budzone zbyt często. W dokumentacji Androida widnieje również ostrzeżenie, że w przyszłych wersjach oprogramowania może zostać wprowadzone ustawienie minimalnego i nterwału równego 30 minutom lub dłuższego.
W przypadku krótszych przedziałów czasov.rych, rzędu pojedynczych sekund, musimy samo dzielnie wywołać metodę onUpdat e ( ) za pomocą funkcji dostępnych w klasie AlarmManager. Jeżeli korzystamy z klasy Ala rmMa n a g e r, pojawia się możliwość n iewyw oływani a metody on Update ( ) , a zamiast tego pracy z metodami zwrotnymi alarmu.
Po niżej wyp u n ktowa l iś my standardowe czynności wymagane podczas pracy z metodą onUpdate ( ) : L
2. 3.
Upewnij się, że konfigurator zakończył pracę, w p rze c iwn ym razie po prostu wróć. Nie p owi nno to stanowić problemu w wersji oprogramowania co naj m n i ej 2.0, gdzie są oczekiwane dłuższe interwały czasowe. Jeżeli będzie inaczej, istnieje możliwość, że metoda onUpdate ( ) zostanie wywołana przed zakończeniem pracy konfiguratora.
Uzyskaj dane pr7 echowyv.rane dla tej in stancj i widgetu. Uzyskaj układ graficzny widoku widgetu i utwórz wraz z nim obiekt RemoteViews. .
4. Wywołaj metody klasy RemoteViews w celu ustanowienia wartości dla pojedynczych obiektów widoku, na przykład dla tekstu, obrazu i tak dalej.
5. Za rej estruj wszelkie zdarzenia onClick w dowolnym widoku p oprzez wykorzystanie inten cj i będących w toku. 6. Każ klasie AppWidgetMa nager narysować obiekt RemoteViews, korzys taj ąc z identyfikatora instan cji .
Jak widać, istnieje duża zbieżność pomiędzy działaniem konfiguratora a działaniem metody onUpdate ( ) . P rzydatn a może okazać się możliwość wielokro tnego wykor zystania tej zbież ności pomiędzy obydwoma elementami.
Faza wywołań zwrotnych zdarzeń generowanych przez kliknięcie widoku widgetu Ustaliliśmy już, że metoda onUpdate ( ) na bi eżąco aktualizuje widoki widgetu. Widok widgetu oraz jego podelementy mogą posiadać wywołania zwrotne rejestrowania podczas ich kliknięcia. Zazwyczaj w celu zarejestrowania działania takiego jak kliknięcie wykorzysty-
Rozdział 1 3 • Widgety ekranu początkowego
481
z..-: . a jest przetwarzana intencja. Działanie to może następ n ie uruchomić usługę lub wyko :.ac czynność, na przykład otwarcie okna przeglądarki.
Taka wywołana usługa lub aktywność może następnie nawiązać w razie potrzeby komuni kację z widokiem za pomocą identyfikatora instancji widgetu i klasy AppWidgetManager. Ważne więc jest, aby przetwarzana intencja zawierała identyfikator instancji widgetu.
Usunięcie instancji widgetu Kolejnym oddzielnym wydarzeniem, które może się przytr afić i nsta n cj i wid getu, jest jej usunięcie. Aby tego dokonać, użytkownik musi przez chwilę przytrzymać palec na widgecie ekranu początkowego. U spodu ekranu zostanie wyświetlony kosz. Można do niego przenieść instancję widgetu. Spowoduje to usunięcie tej instancji z ekranu. Zostaje wtedy również wywołana metoda onDelet e ( ) wobec dostawcy widgetu. Jeżeli za chowaliśmy dla tej instancji jakieś informacje o stanie, musimy usunąć te dane w metodzie onDelete. Android wywołuje także metodę onDisable ( ) w przypadku, gdy zostaj e usun ięta ostatnia instancja danego typu. Ta metoda zwrotna służy do wyczyszczenia wszystkich atrybutów prz.echowywanych dla każdej instancji widgetu oraz do wyrej es trowa nia wywołań zwrot nych z transmisji metody onUpd a t e ( ) (listing 13.9).
Odinstalowanie pakietów Widget Przedstawiliśmy pełny cykl życia widgetu. Zanim przejdziemy do następnej części, krótko wspomnimy o potrzebie uporządkowania 1Nidgetów w przypadku planowania odinstalowa nia oraz instalowania nowej wersji pliku .apk, zawierającego te widgety.
Zalecane jest usunięcie ;vszystkich instancji widgetów przed próbą odinstalowania pakietu. Należy postępować zgodnie z instrukcjami opisanymi w paragrafie „Usunięcie instancji widgetu" aż do usunięcia ostatniej instancji. Teraz możemy odinstalować starą wersję i zainstalować nową. Jest to szczególnie istotne w przypadku osób wykorzystujących wtyczkę Eclipse ADT do tworzenia widgetów, ponieważ w trakcie projektowania próbuje ona tego dokonać podczas każdego uruchomienia aplika cji. Zatem pomiędzy momentami działania aplikacji należy usunąć instancje widgetu.
Przykładowy widget Do tej pory zajmowaliśmy się teorią oraz sposobami tworzenia widgetów. Wykorzystajmy tę wiedzę do zaprojektowania, wdrożenia i przetestowania przykładowego widgetu. Celem następnego ćwiczenia jest utworzenie widgetu przypominającego o urodzinach. Każda instancja widgetu będzie \\')'Świetlała imię jubilata, datę j ego urodzin oraz liczbę dni do tego święta. Zostanie także utworzony obszar onClick, któ rego kliknięcie pozwoli nam na zakup upominku. Zostanie uruchomiona przeglądarka, która przeniesie nas pod adres http://www.
google.com. Końcowy układ graficzny widgetu został zilustrowany na rysunku 13.5.
482
Android 2. Tworzenie aplikacji
�1
··a 0 <;ar •y _)
1
li
1212512009
Rysunek 1 3.5. Wygląd i działanie widgetu urodzinowego Implementacja tego widgetu składa się z plików wymienionych poniżej. W zależności od stosowanego źródłowego pakietu Java pliki Java będą przechowy>vane w podkatalogu src wraz ze strukturą katalogów, którą chcemy wykorzystać w przypadku tych pakietów. W celu . zachowania zwięzłości te podkatalogi są reprezentowane przez wyrażenie „ .
.
.
"
• AndroidManifest.xml li: Jest tu zdefiniowany dostawca AppWidgetProvider. • reslxmllbday_appwidget_provider.xml li: Wymiary i układ graficzny widgetu. • resllayoutlbday_widget.xml li: Układ graficzny widgetu. • resldrawablelboxl.xml li: Zapewnia ramki dla sekcji układu graficznego widgetu. • srcl. „IBDayWidgetProvider li: Implementacja klasy AppWidgetP rovide r. W implementacji zawarte są również następujące pliki zarządzające stanem widgetu:
• src/. . . /IWidgetModelSaveContract li: Model kontraktu zachowywania widgetu. • src/. „IAPrejWidgetModel li: Abstrakcyjny model widgetu opartego na preferencjach. • src/. . . IBDayWidgetModel li: Model widgetu przechowującego dane dla widoku widgetu.
• srcl.. ./Utils.java li: Kilka klas użytkowych. Ponadto w implementacji zostały umieszczone następujące pliki, odpowiedzialne za aktyw ność konfiguracji:
• srcl.. ./ConfigureBDayWidgetActivity.java li: Aktywność konfiguracji. • layoutledit_bday_widget.xml li: Układ graficzny do wpisywania imienia jubilata i daty urodzin.
Omówimy każdy z wymienionych plików oraz wyjaśnimy wszelkie pominięte do tej pory pojęcia. Pod koniec tej części będziemy mogli utworzyć te pliki i przetestować widget uro dzinowy we własnym środowisku.
Definiowanie dostawcy widgetu Definiowanie widgetu rozpoczyna się w pliku manifeście aplikacji Androida. To właśnie tutaj określamy dostawcę widgetu, aktywność konfiguracji widgetu oraz wskaźnik pliku XML definiującego układ graficzny widgetu. Wszystkie te elementy zostały zaznaczone tłustym drukiem w pliku manifeście, widocznym w listingu 13.5. Zwróćmy uwagę na definicję dostawcy BDayAppWidgetProvider pełniącego funkcję odbiornika transmisji, a także na definicję aktywności konfiguracji Configu re
�soayWidgetActivity.
Rozdział 1 3 • Widgety ekranu początkowego
483
Listing 1 3.S. Plik manifest przykładowej aplikacji BDayWidget capplication android: icon="@d rawable/icon" android : label="Urodziny">
Odbiomik dostawcy widget11 urodzinowego
�********************************************************************* -->
creceiver and roid : name= " . BDayWidgetProvider"> cintent - filter> c/receiver>
cactivity android : name=" . Configu reBDayWidgetActivity" and roid : label="Konfiguruj widget Urodziny"> c/activity> c/application> cuse s - sd k android : minSdkVersion= " 3 " />
P Po przejrzeniu pliku manifestu okazuje się, że odbiornik jest węzłem równorzędnym ij •liil /)ll.!lli.ij• •1 1 • 1• •
z węzłem aktywności. Jest on również bezpośrednim potomkiem węzła aplikacji.
Etykieta aplikacji Urodziny, widoczna w wierszu
stanowi nazwę widgetu wyświetlaną na liście dostępnych widgetów (rysunek 13.2). Jeżeli tworzymy po raz pierwszy definicję widgetu, upewnijmy się, że poniższa linijka zostanie dokładnie skopiowana:
Określenie and ro i d . appwidg e t . p ro v i d e r jest charakterystyczne dla Androida i powinno zostać umieszczone w kodzie, podobnie jak poniższy fragment: cintent- filter>
484
Android 2. Tworzenie aplikacji
Definicja aktywności konfiguracji nie różni się od standardowej aktywności - poza ko niecznością zadeklarowania możliwości jej odpowiedzi na działania A PPWI DGET_ CONFIGURE.
Definiowanie rozmiaru widgetu Chociaż w pliku manifeście zostaje zdefiniowany dostawca widgetu, szczegóły dotyczące je go układu graficznego umieszczone zostały w osobnym pliku XML. Do dodatkowych in formacji zaliczamy rozmiar widgetu, nazwę jego pliku układu graficznego, przedział czasu aktualizacji oraz nazwę składnika (lub klasy) aktywności konfiguracji. Taki dodatkowy plik XML jest wskazywany przez węzeł a n d ro i d : re s o u rce, widoczny w uprzednio omówionej definicji dostawcy widgetu (listing 13.5). W listingu 13.6 została przed stawiona zawartość pliku informacyjnego dostawcy widgetu (lres/xml!bday_appwidget_
provider.xml). Listing 13.6. Definicja widoku widgetu BDayWidget cappwidget - p rovider xmlns : and roid="htt p : //schema s . android. com/apk/res/android" android: minWidth="lSOdp" android: minHeight="120dp" android : updatePeriodMillis=" 4320000" android: initiallayout="@layout/bday_widget" and roid : configure="com . ai . and roid . BDayWidget . ConfigureBDayWidgetActivity" >
W pliku tym zostaje określona w pikselach szerokość i ""ysokość widgetu. Jednak Android zaokrągli te wymiary do rozmiaru najbliższej wielokrotności komórki. Obszar ekranu po czątkowego jest zorganizowany w macierz komórek: każda komórka stanowi b'7adrat o boku 74 dp (piksele niezależne od gęstości). W dokumentacji Androida można znaleźć zalecenie, aby \\')'miary tworzonych widgetów stanowiły wielokrotność tych komórek minus 2 piksele (margines zaokrąglenia rogów i tak dalej). Umieszczona jest tutaj także wartość częstotliwości wywoływania metody onUpdate ( ) . Za leca się, aby ta wartość nie przekraczała kilku razy na dobę. Wprowadzenie wartości O ozna cza całkowity brak wywoływania aktualizacji. Można ją wykorzystać w przypadku, gdy chcemy sami kontrolować aktualizacje za pomocą klasy Ala rmMan a g e r. Wartość atrybutu initiallayout wskazuje rzeczywisty układ graficzny widgetu (listing 13.7). Natomiast atrybut c o nfig u re określa klasę aktywności konfiguracji. Należy umieścić pełną nazwę tej klasy w tej definicji. Przyjrzyjmy się teraz właściwemu układowi graficznemu widgetu.
Pliki związane z układem graficznym widgetu Z poprzedniego paragrafu i listingu 13.6 wiemy, że układ graficzny widgetu jest skonfigu rowany w określonym pliku. Plik ten jest standardowym plikiem układu graficznego widoku, tworzonym w Androidzie.
Rozdział 1 3 • Widgety ekranu początkowego
485
Jednakże w celu ustandaryzowania procesu tworzenia widgetów Android opublikował ze staw wytycznych projektowania. Wytyczne te można znaleźć pod adresem:
http://developer.android.com/guidelpractices/ui_guidelines/widget_design.html Oprócz wytycznych w zasobie tym umieszczono zbiór teł widoków, usprawniających wygląd i działanie widgetów. W naszym przykładzie poszliśmy nieco inną ścieżką i wykorzystali śmy tradycyjny sposób implementowania układów graficznych, w których tłami są kształty.
Plik układu graficznego widgetu W listingu 13. 7 zaprezentowaliśmy treść pliku tworzącego układ graficzny widgetu, ukazany na rysunku 1 3.5.
Listing 1 3.7. Definicja układu graficznego widoku widgetu BDayWidget c?xml version=" l . O" encoding= " u t f - 8 " ?>
486
Android 2. Tworzenie aplikacji
android : background="@drawable/boxl" and roid : g ravity="center" />
Aby osiągnąć zamierzony efekt, układ graficzny wykorzystuje zagnieżdżone węzły L i n ea r '+Layout. Niektóre kontrolki używają również pliku definicji kształtu boxl.xml do zdefiniowania granic.
Plik kształtu tła widgetu Kod tej definicji kształtu został umieszczony w listingu 13.8 (plik ten powinien znajdować się w podkatalogu /res/drawable). Listing 1 3.8. Definicj a kształtu krawędzi
Taka metoda utworzenia układu graficznego jest przydatna nie tylko w przypadku widge tów, lecz także do tworzenia innych układów graficznych. Dobrze jest utworzyć aktywność i oddzielnie przetestować układy graficzne przed wprowadzeniem ich do widgetu (przynajmniej my tak zrobiliśmy). Utworzenie odpowiedniego wyglądu i działania widgetu kosztowało nas wiele prób. Bezpośrednie eksperymentowanie na widgetach może okazać się bardzo nużącą czynnością; po każdym uruchomieniu aplikacji należy kolejno usunąć, odinstalować, zain stalować i ponownie umieścić widgety na ekranie początkowym. Do tej pory omówione pliki stanowią pełne definicje XML wymagane przez typowy widget. Zobaczmy teraz, w jaki sposób będziemy reagować na wydarzenia cyklu życia widgetu po przez przebadanie klasy dostawcy widgetu.
Implementacja dostawcy widgetu Przy okazji omawiania architektury widgetu zajęliśmy się zadaniami klasy dostawcy v.'.id getu. Dostawca ten musi posiadać zaimplementowane następujące metody zwrotne odbior nika transmisji: • • • •
onUpdate ( ) onDelete ( ) onEnable ( ) onDisable ( )
Rozdział 1 3 • Widgety ekranu początkowego
487
Kod Java zaprezentowany w listingu 13.9 przedstawia implementację każdej z tych metod. Listing 1 3.9. Przykładowy dostawca widgetu: BDayWidgetProvider ///src!IBDayWidgetProvider.java public class BDayWidgetProvider extends AppWidgetProvider { private static fina! St ring tag = "BDayWidgetProvide r " ; public void onUpdate (Context context ,
AppWidgetManager appWidgetManage r , int [ ) appWidgeti d s ) { fina! int N = appWidgetid s . length ; for ( int i=O; i
i n t appWidgetld = appWidgetid s [ i J ; updateAppWidget ( contex t , appWidgetManager, appWidget id ) ;
} } public void onDeleted( Context context , int [ ] appWidgetids) {
fina! int N = appWidgetids . length; f o r ( int i=O; i
{
BDayWidgetModel bwm = BDayWidgetModel . retrieveModel( context, appWidgetid s [ i ] ) ; bwm. removePrefs ( context ) ; } }
@Over ride public void onReceive ( C ontext context, Intent intent) {
fina! String action = intent . getAction ( ) ; if (AppWidgetManage r . ACTION_APPWIDGET_DELETED . equa l s ( action ) ) { Bundle extras intent . getExt ras ( ) ; fina! int appWidgetid = ext ras . getlnt ( AppWidgetManage r . EXTRA_APPWIDGET_ I D , AppWidgetMan a ge r . INVALID_APPWIDGET _ ID ) ; =
if ( a ppWidgetid ! = AppWidgetManage r . INVALID_APPWIDGET_ID) { this . onDeleted ( context , new int [ ] { appWidgetid } ) ;
}
}
else { supe r . onReceive( context, intent ) ;
} } public void onEnabled( Context context) {
BDayWidgetModel . clea rAllPreferences ( context ) ; PackageManager pm = context . getPackageManage r ( ) ; p m . setComponentEnabledSet t i n g ( new ComponentName ( " com . ai . and roid . BDayWidget " , " . BDayWidgetProvide r " ) , PackageManage r . COMPONENT_ ENABLED_STATE_ENABLED , PackageManage r . DONT KILL_APP ) ;
488
Android 2. Tworzenie aplikacji
public void onDisabled ( Context context) {
BDayWidgetModel . clearAllPrefe rences ( context ) ; PackageManager pm = context . getPackageManag e r ( ) ; pm . setComponentEnabledSetting ( new ComponentName ( "com . a i . android . BDayWidget " , " . BDayWidgetProvider" ) , PackageManage r . COMPONENT_ENABLED_STATE_DISABLED , Pac kageManage r . DONT_KI LL_APP ) ;
private void updateAppWidget (Context context,
AppWidgetManager appWidgetManager, int appWidgetid) { BDayWidgetModel bwm = BDayWidgetModel . retrieveMode l ( context , appWidgeti d ) ; if ( bwm == n u l l ) { ret u r n ;
}
Configu reBDayWidgetActivity
. updateAppWidget ( context , appWidgetManage r , bwm ) ;
}
}
W paragrafie „Architektura widgetów ekranu początkowego" zostało omówione działanie każdej z tych metod. W naszym widgecie urodzinowym metody te korzystają z kolei z metod umieszczonych w klasie BDayWidgetModel. W śród tych metod są takie, jak removeP ref s ( ) , ret rieveP refs ( ) oraz c l e a rA l l P re fe ren c e s ( ) .
Klasa BDayWidgetModel służy do obudowania stanu naszych instancji widgetu (klasa ta zo stanie omówiona w następnym paragrafie). Aby zrozumieć tę klasę dostawcy widgetów, wy starczy wiedzieć, że korzystamy z klasy modelu do odczytywania danych wymaganych przez instancję widgetu. Dane te są przechowywane w preferencjach. Dlatego właśnie metody tej klasy noszą nazwy removeP refs ( ) , ret rieveP refs ( ) i clea rAllP references ( ) (stałoby się bardziej zrozumiałe, gdybyśmy zamiast wyrażenia P re f s (preferencje) wprowadzili wy rażenie Data (dane), w wyniku czego metody te nosiłyby nazwy odpowiednio: removeData ( ) , ret rieveData ( ) , clea rAllData ( ) ). Takie przekształcenie pełni jedynie rolę poglądową i nie znajdziemy metod zawierających przyrostek D a t a ( ) . Jak już stwierdziliśmy, metoda aktualizacji jest wywoływana dla wszystkich instancji wid getu. Za jej pomocą muszą zostać zaktualizowane wszystkie v.rystąpienia widgetu. Instancje te są przekazywane w postaci tabeli identyfikatorów. Metoda onUpdate ( ) zlokalizuje dla każdego atrybutu id odpowiedni model instancji widgetu i wywoła tę samą metodę, która jest używana przez aktywność konfiguratora (listing 13. 14) w celu wyświetlenia uzyskanego modelu widgetu. W metodzie onDelete ( ) utworzyliśmy obiekt BDayWidgetModel, a następnie kazaliśmy mu samoistnie usunąć się z magazynu przechov.rywanych preferencji. W przypadku metody onEna bled ( ) , która jest wywoływana tylko raz, podczas tworzenia pierwszej instancji, wyczyściliśmy wszystkie przechowywane dane modeli widgetu, zatem instancja ta rozpoczyna działanie z czystym kontem. Taka sama czynność jest przeprowa dzana w przypadku metody onDisabled ( ) , dzięki czemu pamięć po instancjach widgetów zostaje całkowicie oczyszczona.
Rozdział 1 3 • Widgety ekranu początkowego
489
W metodzie
onEnabled ( ) uruchamiamy dostawcę treści, który może teraz odbierać trans mitowane komunikaty. Metoda onDisabled ( ) wyłącza ten składnik, więc nie będzie on już wyszukiwał rozgłaszanych komunikatów.
'"Ymor• 1111111
•....·
Specjalnym przypadkiem jest metoda onRecei ve ( ) . Przed wprowadzeniem oprogramowania w wersji 1 .6 występował błąd uniemożliwiający wywołanie metody onDelete ( ) . Można było obejść ten problem poprzez jawne dostarczenie metody onRecei ve ( ) . Od wersji 1 .6 Androida metoda ta stała się zbyteczna; wystarczająca okazuje się ta sama metoda z bazowej klasy.
Dzięki implementacji modeli widgetów kod pozostaje czysty. Zajmiemy się teraz kwestią modeli widgetów i sposobem ich implementacji.
Implementacja modeli widgetów Czym jest model widgetu? Nie jest to pojęcie swoiste dla Androida. Osoby mające styczność z tradycyjnym programowaniem interfejsów Ul z pewnością znają pojęcie wzorca MVC (ang. Model-View-Control/er - Model-Widok-Kontroler). W naszym przypadku model przechowuje dane wymagane przez widok; widok jest odpowiedzialny za wyświetlanie; na tomiast kontroler pośredniczy w komunikacji pomiędzy modelem a widokiem. Chociaż Android nie wspiera takiego rozwiązania, wprowadziliśmy je w życie, aby uprościć programowanie widgetów. W tym podejściu każdy widok instancji widgetu będzie posiadał ekwiwalentną klasę Java, zwaną modelem widgetu. Model ten zawierać będzie wszystkie metody służące do zapewnienia danych wymaganych przez instancje widoku. Zaopatrzyliśmy te modele także w pewne podstawowe klasy, umożliwiające tym modelom samoistne ich zapisywanie i odczytywanie z trwałych magazynów, na przykład „współdzie lonych preferencji". Prześledzimy hierarchię klas modeli oraz pokażemy, w jaki sposób można wykorzystać współdzielone preferencje do przechowywania i odczytywania danych.
Interfejs modelu widgetu Rozpoczniemy od omówienia interfejsu zachowującego się jak kontrakt wobec modelu widgetu, dzięki czemu model ten może deklarować zapisywanie pól w trwałej bazie danych. Kontrakt ten definiuje również sposób konfigurowania pola podczas jego odczytu z bazy danych. W dodatku interfejs posiada metodę zwrotną init ( ) , która jest wywoływana pod czas odczytywania modelu z bazy danych, a przed przesłaniem tego modelu do klienta wy syłającego żądanie. Listing 1 3 . 1 O przedstawia kod źródłov.ry interfejsu kontraktu naszego widgetu. Listing 1 3.1 O. Zachowywanie stanu widgetu: kontrakt //nazwa pliku: src/. . ./IWidgetModelSaveContract.java public interface IWidgetModelSaveContract {
public void setValueFo rPref ( St ring key, String value ) ; public String getPrefname ( ) ;
//zwraca kluczowe pary wartości, które chcemy zachować
490
Android 2. Tworzenie aplikacji
public Map getP refsToSave( ) ;
//zostaje wywołany po odzyskaniu public void init ( ) ;
Ten interfejs jest zaprojektowany w taki sposób, że wywodząca się od niego abstrakcyjna klasa przeprowadzi implementację za pomocą określonego, trwałego magazynu. Wcześniej już wspomnieliśmy, że będziemy korzystać z tej funkcji współdzielonych preferencji An droida jako z trwałego magazynu. Jak wskazuje sama nazwa interfejsu, jest to kontrakt słu żący wyłącznie do zapisywania. Takie klienty jak BDayWidgetProvider nadal będą zależne od najbardziej wydzielonej klasy tego interfejsu posiadającej swoiste metody. Pamiętajmy, że w normalnej a plikacji wprowa dzamy nieco inną strukturę dziedziczenia: . "f ii ''li f ·· i za mi a st dziedziczenia będziemy prawdopodobnie wykorzystywać mechanizm • •f • · • -• • • • -
delegacji umożliwiający wielokrotne wykorzystywanie obiektów. Jednak hierarchia dziedziczenia będzie dobrze się sprawowała w naszym testowym widgecie ukazującym przykład modeli widgetów. ,
Rozważmy teraz abstrakcyjną implementację przechowującą pola danych widgetu w postaci współdzielonych preferencji.
Abstrakcyjna implementacja modelu widgetu Kod odpowiedzialny za interakcję z trwałym magazynem jest zaimplementowany w klasie APrefWidgetModel. Skrót Pref w nazwie klasy wywodzi się od wyrazu Preference (preferencja), ponieważ klasa ta wykorzystuje funkcję Androida Sha redPreferences do przechowywania danych modelu widgetu. Ponadto klasa ta reprezentuje koncepcję prostego widgetu. Pole i i d stanowi „identyfikator instancji" widgetu. Klasa ta zawsze wymaga obecności konstruktora przyjmującego argu ment w postaci identyfikatora instancji widgetu, dzięki czemu następuje dostosowanie do wymogów tego identyfikatora. Przyjrzyjmy się umieszczonemu w listingu 1 3. 1 1 kodowi źródłowemu tej klasy. Tłustym drukiem zostały zaznaczone najważniejsze jej metody. Listing 1 3.1 1 . Implementacja procesu zapisywania widgetu poprzez współdzielone preferencje //nazwa pliku: /src/. . ./APrefWidgetModel.java public abstract class AP refWidgetModel implements IWidgetModelSaveCont ract { private static String tag = "AWidgetModel " ; public int iid ; public APrefWidgetModel ( in t instanceid) { iid = instanceld;
} //abstrakcyjne metody
public abstract String getPrefname ( ) ; public abst ract void init ( ) ;
Rozdział 1 3 • Widgety ekranu początkowego
public Map getPrefsToSave ( ) { return null ; } public void savePreferences( Context context ) {
Map keyValuePairs if ( keyValuePairs == null ) { return;
getPrefsToSave ( ) ;
I/przechodzi do zapisywania wartości SharedPrefe rences. Editor prefs
context . getSharedPreferences ( getPrefname ( ) , O ) . ed it ( ) ; f o r ( String key : keyValuePairs . keySet ( ) ) { String value = keyValuePai rs . get ( key ) ; savePref ( p re f s , key,value ) ; } //ostatecznie zapisuje wartości prefs. commit ( ) ;
p rivate void savePre f ( S haredPreferences. Editor prefs,
St ring key, S t ring value) { String newkey = getSto redKeyForFieldName ( key ) ; prefs . putStrin g ( newkey , value ) ; }
private void removePre f ( SharedPreferences. Editor prefs, String key) {
String newkey = getSto redKeyForFieldName ( key ) ; prefs . remove ( newkey) ;
}
protected String getStoredKeyForFieldName (String fieldName ) { return fieldName + " _ " + iid ;
}
public static void clearAllPrefe rences( Context context, String prefname) {
Sha redPreferences prefs=contex t . g etSharedPreferences( prefname, O ) ; S h a redPreferences . Editor p refsEdit = p refs . ed i t ( ) ; prefsEdit . clea r ( ) ; prefsEdit. commit ( ) ;
} public boolean retrievePrefs( Context ctx) {
SharedPreferences p refs = ctx . getSha redPreferences ( getPrefname ( ) , O ) ; Map keyValuePairs = p refs . getAll ( ) ; boolean prefFound false; for ( St ring key: keyValuePairs . keySet ( ) ) { if ( isitMyPref ( key) == t rue ) { String value = ( S t ring ) keyValuePai rs . get ( key ) ; setValueFo rPref ( ke y , value ) ; prefFound = t ru e ; } } return p refFound; =
public void removePrefs ( Context context) {
Map keyValuePairs = getPrefsToSave ( ) ; if ( keyValuePairs == n u ll ) { ret u r n ;
491
492
Android 2. Tworzenie aplikacji
}
I/przech odzi do zapisywan ia wartości
SharedPreferences . Editor prefs context . getSharedPreferences ( g e t P refname ( ) , O) . ed it ( ) ; f o r ( S t ring key: keyValuePairs . keySet ( ) ) { removePref ( p refs , key ) ;
}
//ostatecznie zapisuje wartości pref s . commit ( ) ; }
private boolean i s ltMyPref ( Strin g keyname) {
if ( keyname. indexOf ( "_ " + i i d ) > O ) { return t rue; } return false;
}
public void setValueForPre f ( String key, String value) {
ret u r n ; }
Zobaczmy, w jaki sposób są implementowane kluczowe metody tej klasy. Rozpoczniemy od zapisania atrybutów modelu widgetu w pliku współdzielonych preferencji: public void savePreferences ( Context context) { Map keyValuePairs = getPrefsToSave ( ) ; if ( keyValuePairs == null ) { return; }
I/przechodzi do zapisywania wartości SharedPreferences . Editor prefs context . getSharedPreferences ( getP refname ( ) , O) . edit ( ) ; fo r ( St ri ng key: keyValuePa i rs . keySet ( ) ) { String value = keyValuePairs . get ( key ) ; savePref ( p re f s , key,value ) ;
I/ostatecznie zapisuje wartości prefs . commit ( ) ;
Metoda ta rozpoczyna od poproszenia pochodnych klas o Z\>\Tot odwzorowania par klucz i wartość, gdzie kluczami są atrybuty modelu, a wartościami - ciągi znaków reprezentujące wartości tych atrybutów. Metoda ta następnie każe obiektowi context przechować plik SharedPreferences poprzez metodę context . getSha redP references ( ) . Interfejs API wy maga unikatowej nazwy dla tego pakietu. Model pochodny jest odpowiedzialny za jej do starczenie. Po uzyskaniu współdzielonych preferencji zgodnie z dokumentacją Androida zażądamy otrzymania ich modyfikowalnej wersji. Następnie kolejno je zaktualizujemy. Po przeprowa dzeniu procesu aktualizacji wywołujemy metodę commi t ( ) , co spowoduje zapisanie prefe rencji.
Rozdział 1 3 • Widgety ekranu początkowego
493
Więcej informacji można uzyskać, przeglądając dokumentację dotyczącą interfejsów API klas Sha r edP refe ren ces i Sha redPrefe ren ces . Edi t o r. W zamieszczonym na końcu roz działu ustępie dotyczącym zasobów znajdują się adresy URL zawierające powyższe informa cje. Warto również zwrócić uwagę na fakt, że pliki współdzielonych preferencji są napisane w języku XML i są przechowywane w katalogu danych pakietu. Ponieważ do przechmvywania danych użyliśmy jednego pliku dla wszystkich instancji wid getu, potrzebny jest mechanizm rozróżniania nazw pól pomiędzy wieloma instancjami wid getu. Na przykład: jeśli posiadamy dwie instancje widgetu nazwane 1 i 2, wymagane będzie zastosowanie dwóch kluczy przechowujących atrybut Name, tak że będą istniały wartości name_ l oraz name_2. Takie przekształcenie przeprowadzamy w następującej metodzie: protected String getSto redKeyFo rFieldNam e ( St ring fieldName) { ret u rn fieldName + "_" + i i d ; } Klasa pochodna również wykorzystuje tę metodę do określenia aktualizowanych pół pod czas jej wywołania przez metodę setValue ( ) .
Implementacja modelu widgetu Urodziny Ostatecznie klasa będąca ostatnim potomkiem w tej hierarchii modeli widgetów jest odpo wiedzialna za rzeczywiste utrzymywanie wszystkich pól wymaganych przez widok. Klasy bazowe są jej potrzebne do przechowywania i odczytywania danych. Zaprojektowaliśmy tę klasę w taki sposób, że klienty korzystające z tych modeli mają bezpośrednio z nią do czy nienia, ponieważ jest to klasa najsilniej z nimi związana. Na przykład podczas pierwszego utworzenia instancji widgetu przez aktywność konfigura tora aktywność ta konkretyzuje jedną z takich klas, zapełnia ją wartościami i powoduje jej samoistne zapisanie się. Z powodu wymogów widoku klasa ta przechowuje trzy pola:
• name. Imię osoby. • bday. Data urodzin tej osoby. • u rl. Adres witryny, w której można dokonać zakupów. W dalszej kolejności klasa ta zawiera obliczony atrybut howManyDays, przedstawiający liczbę dni do urodzin danej osoby. Zobaczymy także, że obowiązkiem tej klasy jest wypełnienie kontraktu zapisywania. Potrzebne są do tego następujące metody: public void setValueForPref(String key, String value ) ; public St ring getPrefname ( ) ; public Map getPrefsToSave ( ) ; W listingu 1 3 . 1 2 został umieszczony kod przeprowadzający te wszystkie czynności.
Listing 1 3.12. BDayWidgetModel: implementacja modelu stanów //nazwa pliku: /src/. . ./BDayWidgetModel.java public class BDayWidgetModel extends APrefWidgetModel { private static String tag=" BDayWidgetModel " ;
494
Android 2. Tworzenie aplikacji
li Generuje niepowtarzalnq
nazw� służqcq do przechowywania daty private static St ring BDAY_WIDGET_PROVIDER_NAME= "com . a i . and roid . BDayWidget . BDayWidgetProvide r " ;
li Zmienne umożliwiajqce rysowanie widoku widgetu
private St ring name = "anon " ; private static String F_NAME = "name " ;
private String bday = " 1/1/200 1 " ; private static St ring F_BDAY "bday" ; =
private St ring url="http : / /www. google. com" ;
li Instrukcje /get/set konstruktora
public BDayWidgetModel(int instanceid ) { supe r ( instanceid ) ; } public BDayWidgetModel ( int instanceid , String inName, String inBday ) { s u p e r ( instanceid ) ; name=inName; bday=inBday; public void init ( ) { } public void setName ( S t ring inname) {name=inname; } public void setBday ( S t ring inbday) {bday=inbday; } public St ring getName ( ) { return name; } public String getBday ( ) { return bday; } public long howManyDays ( ) { t ry { return Utils . howfarinDays ( Utils . getDate ( this . bday) ) ; } catch ( ParseException x ) { return 20000; }
} //Implementacja kontraktu zapisywania
public void setValueForPref (String key, String valu e ) { if ( key . equa l s ( getStoredKeyForFieldName( BDayWidgetModel . F_NAM E ) ) ) { t h i s . name = value; return;
}
if ( key . equa l s ( getStoredKeyFo rFieldName ( BDayWidgetModel . F_BDAY ) ) ) { this . bday = value; ret u r n ; } } public String getPrefname ( ) { return BDayWidgetModel . BDAY_WIDGET_ PROVIDER_NAME; }
Rozdział 1 3 • Widgety ekranu początkowego
495
//zwraca pary wartości, które chcemy zachować
public Map getPrefsToSave ( ) { Map map = new HashMap ( ) ; map . put ( BDayWidgetModel . F_NAME, this . name ) ; ma p . put ( BDayWidgetModel . F_BDAY, this . bday ) ; return map;
}
public String toSt r i ng ( ) {
StringBuffer sbuf = new StringBuffe r ( ) ; s b u f . append ( " iid : " + iid ) ; s buf . append ( " name : " + name ) ; sbuf . append ( " bday : " + bday ) ; return sbuf . toString ( ) ; public static void clearAllPrefe rences( Context ctx ) {
AP refWidgetModel . clearAllPreferences ( ct x , BDayWidgetModel . BDAY_WIDGET_PROVIDER_ NAME ) ;
} public static BDayWidgetModel retrieveModel( Context ctx, int widget id ) {
BDayWidgetModel m = new BDayWidgetModel (widgetid ) ; boolean found = m . retrievePrefs ( ctx ) ; return found ? m : nu l l ;
Tak widać, w klasie tej zostały zastosowane pewne funkcje związane z datą. Zanim przej dziemy do omówienia implementacji aktywności konfiguracji, zademonstrujemy kody źró dłowe tych funkcji.
Kilka fun kcji przetwarzających datę W listingu 1 3 . 1 3 została ukazana klasa, której celem jest data. Pobiera ona ciąg znaków daty i sprawdza jego poprawność. Oblicza także różnicę pomiędzy bieżącym dniem a wprowa dzoną datą. Kod jest całkowicie zrozumiały. Umieściliśmy go ze względu na zachowanie ciągło ści opisu. Listing 1 3.13. Funkcje daty public class Utils { private static St ring tag = "Util s " ; public static Date getDat e ( String dateString)
t h rows ParseException { DateFormat a = getDateFo rmat ( ) ; Date date = a . parse( dateSt ring ) ; return da t e ;
}
public static S t ring test ( S t ring sdate ) { try {
Date d = getDate ( sdate ) ; DateFo rmat a = getDateForma t ( ) ; String s = a . format ( d ) ; return s ;
496 Android 2. Tworzenie aplikacji } c a t c h ( Exception x ) { return " p roblem z datą : " + sdate ; } public static DateFormat ge t Dat e Format ( ) {
SimpleDateFormat df = new SimpleDateFormat ( "MM/dd/yyyy " ) ; //DateFormat df DateFormat.getDatelnstance(DateFormat.SHORT); d f . setlenient ( false ) ; return d f ; =
} //poprawneformaty: l i112009, 1 111 112009, //niepoprawneformaty: 13/ 112009, 1132/2009 public static boolean validateDate (String dateSt rin g ) {
t ry { SimpleDateFormat df = new SimpleDateFo rmat ( " MM/dd/yyyy " ) ; d f . setlenient ( false ) ; Date date = d f . parse(dateString ) ; return t rue; } catc h ( Pa rseException x) { return false;
}
}
public static long howfarinDays ( Date date ) {
Calendar cal = Calenda r . getinstance ( ) ; Date today = cal. getTime ( ) ; long today_ms = today . getTime ( ) ; long t arget_ms = date . g etTime ( ) ; return ( t a rget_ms - today_m s ) / ( 1000 * 60 * 60 * 24) ; } }
Przyjrzyjmy się teraz omówionej wcześniej implementacji aktywności konfiguracji.
Implementacja aktywności konfiguracji widgetu W ustępie „Architektura widgetów ekranu początkowego" wyjaśniliśmy rolę i zadania ak tywności konfiguracji. Implementacja tej klasy aktywności w przypadku naszego przykła dowego widgetu nosi nazwę Configu reBDayWidgetActivity. Jej kod źródłowy został za prezentowany w listingu 13.14. Klasa ta zawiera imię jubilata oraz datę jego przyszłych urodzin. Zostaje przez nią następnie utworzony obiekt BDayWidgetModel, który jest przechowywany we współdzielonych preferen cjach. Umieszczona jest w niej również funkcja potrafiąca przenieść obiekt BDayWidgetModel do właściwego widoku widgetu. Listing 1 3.14. Implem e ntacj a a ktyw ności konfiguratora public class Configu reBDayWidgetActivity extends Activity { private static String t a g = "ConfigureBDayWidgetActivit y " ; private int mAppWidgetid = AppWidgetMan age r . INVALID_APPWIDGET_ I D ;
Rozdział 1 3 • Widgety ekranu początkowego
497
/** Wywoływane podczas pierwszego utworzenia aktywności. */ @Over ride public void onC reate (Bundle savedinstanceState) {
supe r . onC reate ( savedlnstanceState ) ; s etContentView (R . layout . edit_bday_widget ) ; setupButton ( ) ; Intent intent getlntent ( ) ; intent . getExt ra s ( ) ; Bundle extras if (extras 1 = n u l l ) { mAppWidgetid = extras . getlnt ( AppWidgetManager . EXTRA APPWIDGET_ I D , AppWidgetMana ge r . INVALID_APPWIDGET_ ID ) ; } } private void setupButton ( ) {
Button b = ( Butto n ) this . findViewByid ( R . id . bdw_button update_bday_widget ) ; b . setOnClicklistener( new Button. OnClicklistene r ( ) { public void onClick ( View v ) {
parentButtonClicked ( v ) ; }) ;
}
private void parentButtonClicked( View v ) {
St ring name = thi s . getName ( ) ; St ring date = this . getDate ( ) ; if ( Utils . validateDate (date) == fals e ) { t h i s . setDate ( "n ieprawidłowa data : " + date ) ; return; } AppWidgetManage r . INVALID_APPWIDGET_ ID ) { if ( this . mAppWidgetid ret u r n ; updateAppWidgetLoca l ( name , date ) ; Intent resultValue = new Intent ( ) ; resul tValue . putExt ra (AppWidgetManage r . EXTRA_APPWIDGET_ I D , mAppWidgetid) ; setResult ( RESULT OK, resultValue ) ; finish ( ) ; }
private String getName ( ) { EditText nameEdit = ( EditText ) this . findViewByid ( R . id . bdw_bday_name_id ) ;
S tring name = nameEdit . getTex t ( ) . toString ( ) ; return name;
} private String getDate ( ) { Edi tText dateEdit = ( Edi tText) this . f indVie1·1Byid ( R . i d . bdw_ bday date id ) ;
String dateString return dateString;
=
dateEdit. getText ( ) . toSt ring ( ) ;
} p r ivate void setDate(String erro rDate ) {
EditText dateEdit
=
( EditText) this . findViewByid ( R . id . bdw_bday_date_id) ;
498 Android 2. Tworzenie aplikacji dateEdit . setText ( " bląd " ) ; dateEdit . requestFocu s ( ) ; } private void updateAppWidgetLocal(String name, String dob ) { BDayWidgetModel m = new BDayWidgetModel(mAppWidgetid , name , dob ) ; updateAppWidget(this , AppWidgetMana g e r . getinstance(this ) , m ) ; m . savePreferences ( t his ) ; } public static void updateAppWidget ( Context context, AppWidgetManager appWidgetManager, BDayWidgetModel widgetMode l ) {
RemoteViews views = new RemoteViews ( context. getPackageName ( ) , R . layou t . bday_widget ) ; views . setTextVie�iText ( R . id . bdw_w_ name , widgetModel . g etName ( ) + " : " + widgetModel . iid ) ; views . setTextVie1·1Text ( R . id . bdw_w_ date , widgetMode l . getBday ( ) ) ; //aktualizuje nazwę
views . setTextViewText ( R . id . bdw w days , Long . toString (widgetMod e l . howManyDays ( ) ) ) ; Intent definelntent new Intent ( Intent . ACTION_VIEW, Uri . pa rse( "http : //wviw. google . com" ) ) ; Pendingintent pendingintent Pendingintent . getActivity ( context , O /* brak requestCode * / , defineintent , O /* brak flag * / ) ; views . setOnClickPendingintent ( R . id . bdw_w_button_buy, pendingintent ) ; =
=
li Informuje menedżer widgetu appWidgetManager . updateAppWidget(widgetModel . iid, views ) ;
}
Jeżeli przyjrzymy się kodowi metody updateAppWidgetlocal ( ) , stwierdzimy, że jest ona odpowiedzialna za tworzenie i przechowywanie modelu. Do jego wyświetlania służy nato miast funkcja updateAppWidget ( ) . Warto zwrócić uwagę, w jaki sposób wykorzystuje ona będącą w toku intencję do rejestrowania wywołania zwrotnego. Intencja ta bierze główną intencję, na przykład Intent definelntent = new Intent (Intent . ACTION_VIEW, U ri . parse ( " ht t p : //www . google . com" ) ) ;
i tworzy kolejną intencję w celu „uruchomienia aktywności". I na odwrót, trwająca intencja może zostać również v.rykorzystana do „uruchomienia usługi". Odnotujmy fakt, że funkcja ta działa z obiektami RemoteViews i AppWidgetManag e r. Zwróćmy uwagę na następujące ele menty tej funkcji:
Rozdział 1 3 • Widgety ekranu początkowego
499
• Uzyskanie widoku RemoteViews z układu graficznego. • Ustanowienie wartości tekstowych w widoku RemoteViews. • Zarejestrowanie trwającej intencji poprzez widok RemoteViews. • Wywołanie klasy AppWidgetManager w celu wysłania widoku RemoteViews do widgetu. • Zwrot wyniku koócowego.
••rer• 111111
-• • •· -
Fun kcj a statyczna udpateAppWidget m oże zostać wywołana z dowolnego miejsca, dopóki znamy ide ntyfikator widg etu Wynika z tego wniosek, że m ożna a kt ua l izować widget z dowolnego miejsca na urządzeniu oraz z dowolnego procesu, zarówno widocznego, jak i ni e widoczn ego . .
Istotne jest także, aby zakoóczyć aktywność w następujący sposób: Intent resultValue = new Intent ( ) ; resultValue . putExt ra ( AppWidgetManage r . EXTRA APPWIDGET_ID , mAppWidgetid ) ; setRe s u l t ( RESULT_OK, resultValue ) ; finish ( ) ;
Zaobserwujmy, jak przekazujemy identyfikator widgetu obiektowi wywołującemu. W ten sposób klasa AppWidgetMan a g e r uzyskuje informację, że aktywność konfiguratora została zakończona wobec instancji widgetu. Podsumujemy omawianie tematu konfiguracji widgetu prezentacją układu graficznego formularza tej aktywności, widoczną w listingu 13.15. Widok ten jest zupełnie nieskompli km.vany: posiada kilka pól tekstowych oraz kontrolek edycji, a także przycisk aktualizowania. Graficznie zostało to ukazane na rysunku 1 3.4.
Listing 1 3.15. Defi nicja układu graficznego dla aktywności konfiguratora
500
Android 2. Tworzenie aplikacji
android : layout_height="wrap_ content" android : text="Data urodzenia ( 9/1/2001 ) : " />
Na tym zakończymy temat implementowania przykładowego widgetu. Podczas wykonywania tego ćwiczenia zaprezentowaliśmy następujące czynności:
• definiowanie widgetu, • odpowiedź na wywołania zwrotne widgetu, • two rzenie aktywności konfiguracji widgetu, • zastosowanie klasy RernoteViews, • wprowadzenie struktury zarządzania stanami, • projektowanie przyjemnego dla oka układu graficznego widgetu. Przejdziemy teraz do udzielenia kilku wskazówek dotyczących widgetów.
Ogran iczenia i rozszerzenia widgetów Na pierwszy rzut oka widgety w Androidzie zdają się być bardzo prostymi obiektami. Po siadają one jednak kilka niestandardowych cech, z którymi należy się zaznajomić podczas ich tworzenia. Jeżeli nasz widget nie wymaga zarządzania stanami oraz ma być wywoływany co najwyżej kilka razy dziennie, nie powinien istnieć najmniejszy problem z jego napisaniem. Widget następnego poziomu wymaga zarządzania stanem, nie jest jednak \Vywoływany zbyt często. Do tej kategorii zalicza się omówiony w tym rozdziale widget Urodziny. Struktura zarządzania stanami okazuje się dla takich widgetów bardzo korzystna. W tym rozdziale pokazaliśmy podstawowy szkielet zarządzania stanami. Zakładamy, że będą dostępne bardziej skomplikowane struktury lub Czytelnik sam napisze bardziej złożony i elastyczny szkielet. Na kolejnym poziomie częstotliwość wywoływania widgetu jest rzędu sekund lub milise kund. W takim przypadku musimy samodzielnie stworzyć wywołania aktualizacji za pomocą klasy Ala rmManager. Będziemy również prawdopodobnie potrzebowali usługi częstego zarzą dzania stanami, niezależnej od trwałej struktury. Na przykład: jeżeli chcemy napisać widget Stoper, musimy wprowadzić zegar odmierzający co najmniej sekundowe przedziały oraz śledzić liczniki, w czym pomogą nam stany.
Rozdział 1 3 • Widgety ekranu początkowego
501
Kolejnym czynnikiem, który należy wziąć pod uwagę, jest brak mechanizmu pozwalającego klasie RemoteViews - od której jest zależna struktura widoku widgetu - na bezpośrednią edycję widgetu (a przynajmniej taki mechanizm nie został udokumentowany). Klasa Remote �views nakłada również ograniczenia na rodzaje wykorzystywanych widoków i układów gra ficznych. Nie posiadamy bezpośredniej kontroli nad widokami, jedynie pośrednią - po przez metody obecne w klasie RemoteViews. W oparciu o aktualne projekty i przeznaczenie widgetów wydaje się, że zalecane są widgety należące do pierwszej i drugiej kategorii. Istnieje spora doza prawdopodobieństwa, że struktura widgetów zostanie rozszerzona w kolejnych wersjach oprogramowania.
Zasoby Podczas przygotowywania materiałów do tego rozdziału odkryliśmy następujące przydatne zasoby. Zostały one uporządkowane pod względem ważności i przydatności. •
Oficjalna dokumentacja zestawu Android SDK dotycząca widgctów jest dostępna pod adresem http://developer.android.com/guide/topics/appwidgets/index.html.
•
Do zarządzania stanami wymagana jest znajomość interfejsu Sha redPrefe ren ce s . Informacje dotyczące tego interfejsu API znaleźć można na stronie
http://developer.android.com/reference/android!content/SharedPreferences.html. • Ze współdzielonymi preferencjami jest związany interfejs Sha redP refe ren ces . Edi tor. Można o nim poczytać pod adresem http://developer.android.com/reference/android!
content/SharedPreferences.Editor. html. •
Informacje dostępne pod poniższym łączem przydadzą się w procesie projektowania mitych dla oka układów graficznych widgetów: http://developer.android.com/
guide/practices/ui_guidelines/widget_design.html. •
Aby rysować i kontrolować widoki widgetu, musimy opanować interfejs RemoteViews. Informacje o nim są dostępne pod adresem http://developer.android.com/reference/
android/widget/RemoteViews.html. •
Same widgety są zarządzane przez klasę menedżera widgetów. Interfejs API tej klasy został omówiony na stronie
h ttp:I/developer.android. com/reference/android/appwidget/App WidgetMa n ager. h tml. •
Jeżeli musimy szybko zapożyczyć kod stanowiący podstawę widgetów, przydatne mogą się okazać informacje oraz użyteczne fragmenty kodu umieszczone przez jednego ze współautorów książki pod poniższym adresem: http://www.satyakomatineni.com/
akcldisplay?url=DisplayNoteIMPURL&reportld=3300&ownerUserld=satya. •
Następujące łącze skieruje nas do notatek z badań, wykorzystanych podczas pisania tego rozdziału: http://www.satyakomatineni. com/akc/
display?url=DisplayNote!MPURL&reportld=3299&ownerUserld=satya.
Podsumowanie Poznawanie możliwości dostarczonych przez widgety ekranu początkowego w Androidzie okazało się dla nas przyjemnym zajęciem. Widgety te stanowią nieskomplikowaną koncep cję, która może w znaczący sposób wpłynąć na wrażenia użytkownika.
502 Android 2. Tworzenie aplikacji Omówiliśmy teorię dotyczącą widgetów oraz zademonstrowaliśmy działający przykład, ułatwiający zrozumienie ich koncepcji. Wyjaśniliśmy konieczność stosowania modeli wid getów i zarządzania stanem widgetów. Mamy nadzieję, że Czytelnikom przyda się zapre zentowany przez nas kod zarządzania stanem podczas tworzenia własnych widgetów. Na końcu poruszyliśmy tematykę problemów projektowych oraz ograniczeń widgetów.
ROZDZIAŁ
14 Wyszukiwanie w Androidzie
W poprzednich dwóch rozdziałach zajmowaliśmy się mechanizmami związanymi ze stroną początkową systemu Android. W rozdziale 12. wyjaśniliśmy, w jaki sposób aktywne foldery są umieszczane na stronie początkowej oraz jak za pewniają szybki dostęp do zmieniających się danych w dostawcach treści. Roz dział 13. poświęciliśmy analizie widgetów ekranu początkowego, wyświetlają cych informacje wprost na stronie początkowej. Kontynuujemy opis takiej koncepcji informacji w zasięgu ręki, omawiając w tym rozdziale strul'turę wyszukiwania w Androidzie. Szkielet wyszukiwania jest w An droidzie niezmiernie rozbudowany. Chociaż wydaje się, że opcja wyszukiwania jest dostępna wyłącznie na ekranie początkowym urządzenia, jej zasięg można rozwinąć na aktywności różnorakich aplikacji. Rozpoczniemy od opisu funkcji wyszukiwania w Androidzie. Zaprezentujemy koncepcje przeszukiwania globalnego, propozycji ·wyszukiwania, przepisywania propozycji oraz przeszukiwania sieci WWW. Pokażemy, w jaki sposób umieszczać lub wykluczać lokalne aplikacje względem procesu przeszukiwania globalnego. Przy okazji tego omówienia podstaw wyszukiwania zilustrujemy również spo sób interakcji dostawców propozycji z procesem przeszukiwania globalnego. Następnie przeanalizujemy integrację aktywności naszej aplikacji z klawiszem wyszukiwania. Będziemy pracować z akty"vnościami, które nie są jawnie przy stosowane do wyszukiwania, a także przyjrzymy się aktywności wyłączającej możliwość wyszukiwania. Przyjrzymy się również mechanizmowi noszącemu nazwę type-to-search (napisz, aby szukać), dzięki któremu aktywności mają możliwość wywołania procesu wyszukiwania. Ponadto zademonstrujemy sposób jawnego wywoływania procesu wyszukiwania przez aktywność za pomocą menu. Podstawowym elementem, odpowiedzialnym za elastyczność wyszukiwania w An droidzie, jest tak zwany dostawca propozycji. Przyjrzymy się uważnie tej kon cepcji i stworzymy prostego dostawcę propozycji poprzez dziedziczenie po bazo wym dostawcy dostępnym w Androidzie. Często jednak będziemy musieli pisać dostawcę propozycji od podstaw. Będzie to nasze kolejne zagadnienie, które dotyczyć będzie zasadniczej części architektu ry wyszukiwania w Androidzie.
504
Android 2. Tworzenie aplikacji
Na koniec zajmiemy się dwoma zaawansowanymi tematami i pokażemy, w jaki sposób możemy wykorzystać klawisze działania dostępne w unądzeniu do wywoływania niestandardowych działań korzystających z propozycji v,ryszukiwania. Zastanowimy się także na sposobem przeka zania danych określonej aplikacji do wywołanego procesu wyszukiwania. Rozdział zamknie my zestawem adresów URL do zasobów referencyjnych.
Wrażenia z wyszukiwania w Androidzie Wyszukiwanie w Androidzie rozszerza możliwości zapewniane przez znany nam interne towy pasek wyszukiwania Google o zdolność przeszukiwania zarówno lokalnej zawartości urządzenia, jak i zewnętrznej zawartości umieszczonej w internecie. Mechanizm ten może być również wykorzystany do bezpośredniego wywoływania aplikacji z poziomu paska v,ry szukiwania na stronie początkowej. Android dokonuje tego poprzez wprowadzenie struktu ry wyszukiwania pozwalającej na współuczestniczenie w niej aplikacji lokalnych. Protokół przeszukiwania w Androidzie jest prosty. Dostępne jest pojedyncze pole wyszuki wania, w którym użytkownicy wpisują dane. Jest to prawdą zarówno w przypadku stosowania pola wyszukiwania globalnego na stronie początkowej, jak i przeszukiwania własnej aplika cji. Wykorzysty•vane jest to samo pole wyszukiwania. Tekst wprowadzany przez użytkownika jest pobierany przez system już w trakcie wpisyvva nia i przekazywany różnym aplikacjom, które zostały zarejestrowane do odpowiadania na proces wyszukiwania. Zareagują one poprzez zwrócenie zestawu odpowiedzi. Android zbie ra te odpowiedzi z różnych aplikacji i v.ryświetla je w postaci listy możliwych propozycji. Po kliknięciu jednej z tych odpowiedzi zostaje wywołana aplikacja przedkładająca wybraną propozycję. W tym sensie mamy do czynienia z wyszukiwaniem federacyjnym wewnątrz zbioru współuczestniczących aplikacji. Chociaż ogólna idea jest całkiem prosta, szczegóły protokołu są dosyć złożone. W dalszej części rozdziału będziemy je objaśniali na działających przykładach. W obecnym podroz dziale przyjrzymy się procesowi wyszukiwania z perspektywy użytkownika.
Badanie procesu przeszukiwania globalnego w Androidzie Nie da się ominąć wyszukiwania w Androidzie; zostało ono umieszczone w widocznym miejscu na stronie początkowej, co zostało ukazane na rysunku 14.1 (pole wyszukiwania zo stało umieszczone po lewej stronie ekranu i zawiera ono znak firmowy Google oraz ikonę lupy). Pole wyszukiwania nazywane jest także polem QSB (ang. Quick Search Box pole szybkiego wyszukiwania). -
W celu rozpoczęcia wyszukiwania możemy bezpośrednio pisać w polu QSB. Możemy także wywołać proces wyszukiwania, klikając odpowiedni klawisz działania. Klawisze działania stanowią zestaw przycisków widocznych na rysunku 14.1 po prawej stronie. Na interesują cym nas klawiszu został umieszczony symbol lupy. W tym rozdziale będziemy go nazywali klawiszem wyszukiwania. Lupa widoczna w polu QSB nosi nazwę ikony wyszukiwania. Podobnie jak w przypadku klawisza ekranu początkowego możemy kliknąć przycisk wyszu kiwania w dowolnym momencie, bez względu na uruchomioną aplikację. Jednak jeżeli apli kacja została umieszczona w głównym wątku, istnieje dzięki niej możliwość zawężenia \.\'Y szukiwania, czym się zajmiemy w dalszej części rozdziału. Takie zawężone wyszukiwanie nazywane jest wyszukiwaniem lokalnym. Bardziej ogólne, powszechne i niewyspecjalizowa ne wyszukiwanie nosi miano wyszukiwania globalnego.
Rozdział 14 • Wyszukiwanie w Androidzie
"ii1.l !Ml 4D
505
6:15
Rysunek 14.1. Strona początkowa Androida z widocznym polem QSB i klawiszem wyszukiwania Przyjrzyjmy się, w jaki sposób możemy zrobić użytek z pola QSB. Przechodzimy do tego pola, klikając bezpośrednio jego obszar albo klikając ikon ę wyszukiwania. Nie wpi sujmy jeszcze niczego w pol u QSB. W tym momencie Android b<;dzie wyświetlał ekran przypomi nający zrzut z rysunku 14.2.
- „•.
Spare Pans
�; �.h'.>}
Rysunek 14.2. Pole QSB - tryb propozycji zerowych
W zależności od wcześniej dokonywanych czynności obraz widoczny na rysunku 14.2 może wyglądać nieco inaczej, ponieważ Android na podstawie uprzednich działań stara się odgadnąć, czego szukamy. Taki tryb wyszukiwania, w którym nie został wprowadzony tekst w polu QSB, nosi nazwę trybu propozycji zerowych. Zależnie od wpisanego tekstu Android wyświetli
506 Android 2. Tworzenie aplikacji różną ilość propozycji. Zostaną one v.ryświetlone pod polem QSB w formie listy. Elementy tej listy często są nazywane propozycjami wyszukiwania. W miarę wpisywania kolejnych liter Android będzie dynamicznie aktualizował wyniki wyszukiwania. Jeżeli nie zostanie w polu wyszukiwania wpisany tekst, zostaną wyświetlone tak zwane propozycje zerowe. Na rysunku 14.2 widać, że aplikacja Spare Parts została uznana przez Android za używaną w przeszłości, zatem nadającą się na propozycję, mimo że żaden tekst nie został wpisany w polu wyszuki wania. Chociaż nie wprowadziliśmy tekstu w polu QSB, zostaje "'')'Świctlona klawiatura programowa. Jest ona widoczna na rysunku 14.2. Zobaczmy, co się stanie po rozpoczęciu wpisyw·ania danych (rysunek 14.3). Po wprowadzeniu znaku a w polu QSB Android wyszukuje propozycje rozpoczynające się od litery „a" lub zawie rające tę literę w jakiś inny sposób. Stwierdzimy, że Android przeszukał już lokalnie zainstalo wane aplikacje rozpoczynające się na literę „a" i teraz oferuje przeszukanie sieci WWW. Skorzystamy teraz z przycisku przesunięcia kursora ·w dół w celu zaznaczenia pienvszej propozycji. Widok ten został ukazany na rysunku 14.3. tmfiilt!ml � 6:35
�f
.
ł Aparat ,,,
a. O.. �
] Q.
'
Amazon
allegro allegro.pl alegro alegro pł
(
ii"':IC'"
Rysunek 14.3. Propozycje wyszukiwania Zauważmy, że pierwsza propozycja została podświetlona, a uwaga przeszła z pola QSB na tę zaznaczoną propozycję. Obszar ekranu został również powiększony poprzez schowanie klawiatury programowej, gdyż nie będziemy z niej korzystać podczas przeglądania listy propozycji. Dzięki temu widać równocześnie więcej propozycji na ekranie. Przyjrzyjmy się jednak ponownie propozycjom. Android pobiera tekst wpisany w polu wy szukiwania i wyszukuje obiekty znane jako dostawcy propozycji. Android wywołuje asyn chronicznie kaidego dostawcę propozycji w celu uzyskania pasujących propozycji, przybierają cych postać zbioru krotek. Android oczekuje, że te krotki (zwane propozycjami wyszukiwania) będą pasować do zestawu predefiniowanych kolumn (kolumny propozycji). W miarę prze szukiwania tych znanych kolumn Android będzie rysował listę propozycji. Po zmianie tek stu wpisanego w polu QSB Android przeprowadzi cały proces od początku.
• •· ru n • • • • •· · -
Zbiór propozycji wyszukiwania zwany jest także kursorem propozycji. Wynika to z faktu, że dostawca treści reprezentujący dostawcę propozycji zwraca obiekt cu rso r.
Rozdział 1 4 • Wyszukiwanie w Androidzie
507
Jeżeli w tym momencie przeniesiemy się do pola QSB, zostanie ponownie wyświetlona kla wiatura programowa. Kolejną rzeczą z rysunku 14.3 wartą odnotowania jest zależność po między podświetloną propozycją a tekstem wyszukiwania w polu QSB. Tekst wyszukiwania ciągle składa się wyłącznie z litery „a", mimo że został zaznaczony konkretny element, w na szym wypadku aplikacja Aparat. Jednak nic zawsze tak jest, co widać na rysunku 14.4, który przedstawia zaznaczenie propozycji wskazującej adres sklepu Amazon.
Lhttp://\�az�� I °' � Aparat
...... 1<;.a Amazon V \ W \ 'ł.arrt . i\ zon .corr
Q
allegro
a
allegro.pl
a
alegro
c
a egro pl
o
'j\l;)(; t
Rysunek 1 4.4. Przepisanie propozycji Zauważmy, że wstawiona przez nas litera „a" została zastąpiona przez pełny adres URL, w któ rym znajduje się ta samogłoska. Możemy teraz kliknąć ikonę wyszukiwania w polu QSB, aby przejść na stronę Amazon, lub zwyczajnie kliknąć podświetloną propozycję. W obydwu przypadkach skutek będzie identyczny.
swrr=
Taki proces modyfikowania tekstu wyszukiwania w oparciu o zaznaczoną propozycję · · propozycp. nosi· nazwę przepisywania „
Nieco później zajmiemy się dokładniej procesem przepisywania propozycji, w skrócie jed nak Android wykorzystuje jedną z kolumn kursora propozycji do wyszukiwania tego tekstu. Jeżeli taka kolumna istnieje, zostanie przepisany tekst wyszukiwania, w przeciwnym razie pozostanie on niezmieniony. Jeżeli propozycja nie zostanie przepisana, istnieją dwie możliwości. Jeżeli klikniemy ikonę wyszukiwania w polu QSB, niezależnie od tego, co zostało zaznaczone, Android przystąpi do przeszukiwania w bazie Google. Jeżeli bezpośrednio klikniemy element propozycji, w apli kacji, która wygenerowała daną propozycję, zostanie wywołana aktywność wyszukiwania. Ak tywność ta jest odpowiedzialna za wyświetlenie wyników wyszukiwania. Na rysunku 14.5 widać przykład bezpośredniego wywoływania propozycji. W naszym wy padku takim przykładem jest aplikacja APIDemos. Po kliknięciu propozycji aplikacja ta zo stanie bezpośrednio wywołana. Przebieg tego procesu jest dosyć skomplikowany i zajmiemy się jego omówieniem w dalszej części rozdziału (w ustępie „Implementacja niestandardowego dostawcy propozycji").
508
Android 2. Tworzenie aplikacji
(.,,.
allegro.pl aleg ro
avast
Szukaj w mternecie " h.�;!.łH'>7d.)j
Rysunek 14.5. Wywoływanie aplikacji za pomocą wyszukiwania
Na rysunku 14.6 zaprezentowano, co się stanie po kliknięciu ikony wyszukiwania w przy padku wprowadzenia litery „a" w polu QBS. 1@!1111 © 7:07 �
E1J http://www.google.co„.
„. - --. - -
wttryny
Q.
c�
:nt?-r etnW \ s;.1epSJ.>OZ\'\'.'G", wu-;;a•·;a · A pl.
...
pr,oste Z(li.:UD\spozywcze
ZaltO 1 alfat>.tOw r. ;, ntm opan y{I'\. w tym .i l f.)btt.J pol9:1ego. Oz r�cz� 7Vlfkle w d .l nym Jt! yku .... )liJ(>�: (> -„_., l •
-
·w1tan'l1rd A Wik1pedl-l wolna e1'C)iC!o ped•a iium1na ' t, l A lb :orcz.3 n.>tv:.l irupy
-
-
Rysunek 14.6. Przeszukiwanie internetu
Skoro już zapoznaliśmy się z techniką wyszukiwania za pomocą pola QSB, przejdziemy do omówienia sposobu włączania i wyłączania udziału określonych aplikacji w procesie współ udziału w przeszukiwaniu globalnym.
Włączanie dostawców propozycji do procesu wyszukiwania globalnego Jak już stwierdziliśmy, aplikacje posługują się dostawcami propozycji w celu odpowiedzi na proces wyszukiwania. Chociaż aplikacja może posiadać infrastrukturę niezbędną do odpowie dzi na wyszukiwanie, nie oznacza to wcale, że jej propozycje będą automatycznie wyświetlane
Rozdział 1 4 • Wyszukiwanie w Androidzie
509
w polu QSB. Użytkownik musi zezwolić dostawcy propozycji na udział w tym procesie. Poniższe rysunki przedstawiają kolejne etapy włączania lub wyłączania posiadanych do stawców propozycji. Rozpocznijmy od ekranu ustawiei'l Androida (rysunek 14.7).
Rysunek 1 4.7. Lokalizowanie aplikacji odpowiedzialnej za ustawienia Dostęp do tego widoku uzyskujemy poprzez kliknięcie strzałki wyświetlającej aplikacje, umiesz czonej w dolnej części ekranu urządzenia (ekran początkowy został pokazany na rysunku 14.l). Wyszukajmy tam aplikację Ustawienia, widoczną na rysunku 14.7, i przejdźmy do niej. Zostanie wyświetlona strona z ustawieniami Androida, przypominająca ekran z rysunku 14.8.
Rysunek 1 4.8. Wyszukiwanie ustawień aplikacji Ustawienia wyszukiwania
51 O
Android 2. Tworzenie aplikacji
Spośród różnorodności ustawień Androida wybieramy opcję Szukaj. Zostanie uruchomiona aplikacja Ustawienia wyszukiwania, zilustrowana na rysunku 14.9.
Rysunek 1 4.9. Aplikacja Ustawienia wyszukiwania Znajdujemy w tej aktywności zakładkę Okno szybkiego wyszukiwania i wybieramy opcję Wyszukiwane elementy (Wybierz elementy do wyszukania w telefonie). Ukaże się zaprezen towana na rysunku 14.10 lista dostępnych dostawców propozycji.
Rysunek 14.1 O. Aplikacja wyświetlająca wyłączonego dostawcę propozycji
Prosty dostawca propozycji wyszukiwania to jeden ze stworzonych przez nas dostawców propozycji (jego kod został umieszczony w dalszej części rozdziału). Nowy dostawca propo zycji nie jest domyślnie zaznaczony. Kliknijmy go, aby został dołączony do procesu wyszu kiwania. Po jego włączeniu widok tej strony ulegnie zmianie na widoczny na rysunku 14. 1 1 , na którym el em en l ten jest zaznaczony.
Rozdział 1 4 • Wyszukiwanie w Androidzie
51 1
fd8ill e 1:3s
Rysunek 14.1 1 . Aplikacja włączonego dostawcy propozycji wyszukiwania
Interakcja pomiędzy polem QSB a dostawcą propozycji Prześledzimy teraz sposób wykorzystywania propozycji uzyskanych od dostawcy przez pole QSB. Zazwyczaj propozycja od nowego dostawcy zostaje v.ryświetlona jako element Więcej wynikóv1... na liście propozycji (rysunek 1 4. 1 2).
1t Aparat
.
,
'> Am
Q.
allegro
I
allegro.01
Rysunek 14.12. Dodatkowe wyniki uzyskane od nowych dostawców propozycji Zwróćmy uwagę na sposób, w jaki Android grupuje propozycje od większej ilości aplikacji w postaci pojedynczego elementu podsumowania. Po jego kliknięciu lista \�ryników zostanie rozwinięta, tak jak zostało pokazane na rysunku 14.13.
512
Android 2. Tworzenie aplikacji
[J�!llll @ 9:23
-_J O.. Q
allegro
(,
allegro.pl
('
Szukaj w mternecie 1',J5!,:; �a
i7ł Pro.s!y (lostaw
..•
'
Rysunek 14.13. Dodatkowe wyniki pochodzące od określonego dostawcy propozycji Zauważmy, że propozycje pochodzące z określonych aplikacji nadal są pogrupowane w po jedynczych elementach listy, teraz jednak przynajmniej wyniki poszczególnych ap likacj i są od siebie oddzielone. Po kliknięciu którejś z tych aplikacji Android wyświetli ekran zawężo nego wyszukiwania, w którym widoczne będą wyłącznie propozycje danej aplikacj i . Na ry sunku 1 4 . 1 4 został zilustrowany przykład.
Demonstraqa
"J l�
....
t!ll fid !!l'll @ 10:42
ys..:u : i
o..
abed
Rysunek 14.14. Zawężone wyszukiwanie za pomocą dostawcy propozycji W tym momencie mamy do czynienia nie z v.ryszukiwaniem globalnym, lecz lokalnym, przeznaczonym dla aplikacji generującej tę propozycję.
"•i"lilffil"'ij'§il"•
Więcej uwagi poświęcimy tym ekranom w ustępach „Implementacja prostego dostawcy propozycji" oraz „Implementacja niestandardowego dostawcy propozycji".
Rozdział 1 4 • Wyszukiwanie w Androidzie
S1 3
Na tym poziomie wyszukiwania (ściśle lokalnym) kliknięcie ikony wyszukiwania spowoduje przetworzenie wprowadzonego tekstu i przeniesienie nas do aktyw·ności wyszukiwania zi dentyfikowanej przez tę aplikację, nie będzie natomiast przeszukiwana sieć WWW. Jeżeli natomiast klikniemy propozycję w trybie lokalnego przeszukiwania, zostaniemy przeniesie ni do wyspecjalizowanej aktywności wyszukiwania przebywającej wewnątrz tej aplikacji. Dotychczas zaprezentowaliśmy wysokopoziomowy przegląd działania procesu wyszukiwania w Androidzie. Teraz rozwiniemy dalej ten temat i zademonstrujemy działanie v.ryszukiwa nia na przykładach. Rozpoczniemy od przeanalizowania interakcji prostych aktywności z pro cesem ""yszukiwania.
Intera kcja aktywności z klawiszem wyszukiwania Co się stanie po wciśnięciu klawisza wyszukiwania w przypadku, gdy n a pierwszym planie znajduje się aktywność? Odpowiedź zależy od rodzaju tej aktywności. Prześledzimy zacho wanie następujących typów aktywności: • zwykła aktywność niezwiązana z wyszukiwaniem, •
aktywność jawnie wyłączająca wyszukiwanie,
• aktywność jawnie wywołująca v.ryszukiwanie globalne, • aktywność określająca wyszukiwanie lokalne.
Przeanalizujemy te opcje na przykładach zawierających następujące pliki (po omówieniu każde go z nich zaprezentujemy zrzuty ekranu, ukazujące koncepcje tej aplikacji): •
RegularActivity.java
• NoSearchActivity.java • Search!nvokerActivity.java • Loca/SearchEnabledActivity.java • SearchActivity.java
Poza ostatnią aktywnością (SearchActivity.java) każda z pozostałych reprezentuje po jed nym rodzaju z wymienionych wcześniej aktywności. Plik SearchActivity.java jest wymagany przez aktywność LocalSearchEnabledActivity. Każda aktywność, w tym SearchActivity, za wiera prosty układ graficzny z widok.iem tekstowym. Obsługiwane są następujące pliki układu graficznego: •
res/layout/main.xml (dla aktywności RegularActivity)
• res/layout/no_search_activity.xml •
res/layout/search_invoker_activity.xml • res!layout!local_search_enabled_activity.xml • res/layout/search_activity.xml
Dwa następne plik.i definiują te aktywności w Androidzie oraz przeszukują metadane w przy padku aktywności obsługującej wyszukiwanie lokalne: •
manifest.xml • xml/searchable.xml
5 1 4 Android 2 . Tworzenie aplikacji W kolejnym pliku zostały umieszczone dane tekstowe każdego układu graficznego w postaci ciągu znaków: • res/values/strings.xml Wymienione poniżej dwa pliki dostarczają menu niezbędne do wywołania w razie potrzeby tych aktywności oraz wyszukiwania globalnego: • res/menu/main_menu.xml • res/menu/search_invoker_menu.xml
Zbadamy teraz interakcję pomiędzy tymi aktywnościami a klawiszem wyszukiwania po przez metodyczny przegląd kodu źródłowego tych plików pod kątem rodzajów aktywności. Rozpocznijmy od przeanalizowania zachowania klawisza wyszukiwania w obecności stan dardowej aktywności Androida.
Zachowanie klawisza wyszukiwania w obecności standardowej aktywności Aby sprawdzić, co się dzieje w przypadku, gdy niezwiązana z wyszukiwaniem aktywność znajduje się na pierwszym planie, zademonstrujemy jej przykład. Listing 14.1 przedstawia kod źródłowy Java aktywności Regula rActivi t y . Listing 14.1 . Kod źródłowy standardowej aktywności //nazwa pliku: RegułarActivity.java public c l a s s Regula rActivity extends Activity { private final String tag = "Reg u l a rActivit y " ; @Over ride
public void onCreate(Bundle savedinstanceState) { s u pe r . onCreate( savedinstanceSta t e ) ; setConte ntView ( R . layout . ma in ) ;
} @Over ride
public boolean onCreateOptionsMenu( Menu menu) {
//wywołuje nadrzędną klasę w celu dołączenia menu systemowych supe r . on C reateOptionsMen u (menu ) ; Menulnflater inflater = g etMen u i n f l a t e r ( ) inflater. inflate ( R . menu . main_men u , menu ) ; return t r u e ;
;
//z aktywnosci
} @Over ride
public boolean onOptionsitemSelected(Menuitem item) { appendMenui temTex t ( item ) ; R . id . menu elea r ) { if ( item. getltemld ( ) this . emptyText ( ) ; return t r u e ; }
Rozdział 14 • Wyszukiwanie w Androidzie
51 5
if ( item. getltemld ( } R . id . mid_no_sea rc h } { t his . invokeNoSearchActivit y ( } ; return t rue; ==
}
if ( item . getitemid ( ) == R . id . mid_local_sea rch ) { this . invokelocalSearchActivit y ( ) ; return t ru e ;
}
if ( item . getitemid ( ) == R . i d . mid_invoke_sea rch} this . invokeSearchinvokerActivit y ( ) ; return t rue;
}
return t rue;
private TextView getTextView ( ) {
return (TextView ) this . findViewByi d ( R . id . textl ) ;
private void appendMenuitemText (Menuitem menuitem) {
String title = menuitem . getTitle ( ) . toString ( } ; TextView tv = getTextView ( ) ; tv . setText ( t v . getText ( ) + " \ n " + title ) ; } private void emptyText ( ) { TextView t v = getTextView( ) ; tv . setText ( " " } ; }
private void invokeNoSearchActivity ( ) { Intent intent = new Intent ( this , NoSearchActivity . class ) ; sta rtActivity(intent ) ;
}
private void invokeSearchinvoke rActivity ( ) {
Intent intent = new Intent ( t h i s , SearchinvokerActivity . class ) ; startActivi t y ( intent ) ; } private void invokelocalSearchActivity ( ) { Intent intent = new Intent ( th i s , LocalSearchEnabledActivity . class ) ; s t a rtActivity ( i ntent ) ;
}
Zadanie tego kodu polega na odgrywaniu roli prostej aktywności, niezwiązanej z wyszuki waniem. Jednak w tym przykładzie aktywność ta jest również sterownikiem wywołującym pozostałe testowane przez nas aktywności. Dlatego widać w kodzie dodatkowe elementy menu reprezentujące te aktywności. Każda funkcja rozpoczynająca się od instrukcji invoke„. zawiera kod pozwalający na uruchomienie pozostałych rodzajów aktywności.
5 1 6 Android 2 . Tworzenie aplikacji Spójrzmy na plik manifest, aby dowiedzieć się, w jaki sposób ta aktywność jest definiowana (listing 14.2). Widoczne są również definicji pozostałych aktywności, zostaną one jednak omówione w dalszej c zęści rozdziału. Listing
14.2.
Interakcja aktywność a klawisz wyszukiwa ni a : plik manifest
!!nazwa pliku: manifest.xml cactivity android : name=" . RegularActivity" android : label= " Interakcja aktywność/OSB : Standa rdowa aktywn o ś ć " > cinten t - f ilter> c/ ac t iv i t y> cactivity android : name=" . NoSearchActivity" android : label=" Interakcja aktywność/OSB :Wylączone 1·1yszukiwanie"> cactivity android : name=" . SearchinvokerActivity" android : label=" Interakcja a ktywność/OS B : Wywołanie wyszu kiwa n i a " > cactivity android: name=". LocalSearchEnabledActivity" and ro id : label=" Interakcj a aktywność/OSB : Wyszuki1�anie lokalne"> cactivity and roid : name=" . SearchActivity" android : label=" Interakcj a a ktywność/OS B : Wyniki wyszukiwania"> cintent - filter> ccategory android : name=" an d roid . intent . catego ry . DEFAULT" />
android:value= "*"I>
-->
Zauważmy, że klasa Regula rActi vi ty jest zdefiniowana jako główna aktywność tego projektu i nie posiada innych parametrów związanych z "vyszukiwaniem.
Rozdział 1 4 • Wyszukiwanie w Androidzie
S1 7
Plik układu graficznego tej aktywności został ukazany w listingu 14.3. Listing 14.3. Plik układu gra ficzneg o stand a rdowej aktywnośc i //nazwa pliku: layout/main.xml
Po wyświetleniu aktywności widoczny będzie układ graficzny przedstawiony na rysunku 14.15.
Rysunek 14.15. Interakcja standardowa aktywn ość - wyszukiwanie W listingu 14.4 została zaprezentowana zawartość pliku strings.xml przechowującego tekst wyświetlany na rysunku 14.15. Listing 14.4. Interakcja aktywność - klawisz wyszukiwania: plik strings.xml //nazwa pliku: /res/values/strings.xml c?xml version=" l . O" encoding= " u t f - 8 " ?> < !-
518
Android 2 . Tworzenie aplikacji
* regular_activity_prompt ************************************************** --
>
Jest to przykłado�1a aplikac j a , w której testowana jest interakcja pomiędzy polem OSB i klawiszem wyszukiwania a aktywnością. Zostały w niej zawarte cztery aktywności, włącznie z niniej szą. Obecnie j est u ruchomiona standa rdowa aktywność. Dostęp do pozostałych t rzech aktywności uzyskujemy poprzez menu . \n\n Jest to standa rdowa aktywność, nieposiadająca funkcji związanych z wyszukiwaniem. Jeżeli klikniemy teraz klawisz wyszukiwania, zostanie wywołane przeszukiwanie globalne. \n \nPozostałe aktywności t o : \ n \ n l ) Aktywność braku wyszukiwania : aktywno ś ć , w której wyszukiwanie zostało wyłączone, \n2) Wywołanie wyszukiwania : prog ramowe wywołanie wyszuki�1ania globalnego , \n3 ) Aktywność 1vyszukiwania lokalnego : Przywołuj e wyszukiwania lokalne . \n \nTutaj będą poj awiały s i ę informacje o błędach .
w t e j aktywności metoda onSearchRequested
zwraca wartość false . Przycisk wyszukiwania będzie teraz ignorowany. \n \nMożna kliknąć przycisk powrotu , aby uzyskać dostęp do poprzedniej aktywności i wybrać w menu inną a ktywnoś ć .
< !--
************************************************** * searcl1_activity_prompt **************************************************
-->
Jest to tak zwana aktywność wyszukiwania lub aktywność wyników wyszukiwania . Ta aktywność j est wywoływana podczas wciśnięcia klawisza wyszukiwania w t rakcie używania tej aktywności przez inną akty1vność w roli aktywności wyników wyszuki1vania. \n\n Zazwyczaj możemy uzyskać z inte n c j i ciąg znaków kwerendy, aby dowiedzieć się, czym j est ta kwerenda .
Rozdział 1 4 • Wyszukiwanie w Androidzie
519
- ->
W tej aktywności element menu wyszukiwania j e st sto sowany do wywo iania domyślnego wyszukiwania . W tym p rzypadku nie zostaio dla tej aktywn o ś c i określone wyszukiwanie lokalne, więc zosta j e wywalane wyszukiwanie globalne. Kliknięcie przycisku menu s powod u j e wyświetlenie menu „wyszukiwa n ia " . Po jego kliknięciu zostanie u ru c homione wyszu kiwanie globalne.
* local_search_enabled_activity_prompt --> J e s t t o bardzo prosta aktywnoś ć , d l a której w pliku manifeście zostaia wskazana powiązana aktywność wyszukiwania . W ten sposób po wciśnięciu klawisza wyszukiwania z o s t a j e wyświetlone wyszukiwanie lokalne. \n\n Jego lokalność j es t widoczna w etykiecie pola QSB o r a z w j ego podpowied zi . Obydwa te elementy pochodzą z metadanych wyszukiwa n i a . \n\n Po kliknięciu ikony kwerendy zostaniemy p rzeniesieni d o aktywności 1�y s zukiwania lokalnego.
Przykładowa aplikacja wyszukiwania --> Demon s t ra c j a wyszukiwania lokalnego Podpowiedż do demo n s t r a c j i wyszukiwania lokalnego
Podobnie jak w przypadku pliku manifestu plik strings.xml jest używany przez wszystkie aktyw ności tego projektu. Możemy zauważyć, że obecna w tym pliku stała typu s t r i n g regula r_ -.activi ty wskazuje tekst widoczny na ekranie aktywności standardowej. Listing 14.5 prezentuje plik XML menu stosowanego w tej standardowej aktywności. Menu to zostało zilustrowane na rysunku 14.16.
Listing 14.S. Plik menu standardowej aktywności //nazwa pliku: /res/menu/rnain_rnenu.xrnl
520
Android 2. Tworzenie aplikacji
Aktywoo� braku v.ys.zuk1wtlflta
Aktywność wys.zukhvt1nfa lokalnego
Rysunek 14.16. Uzys kiwanie dostępu do pozostałych a ktyw ności testowych
Po utworzeniu tych plików możemy skompilować i przetestować tę aktyw·ność (łub pocze kać do momentu zakończenia omawiania wszystkich rodzajów aktywności tego projektu). Jeżeli chcemy dokonać teraz kompilacji, musimy w pliku manifeście oznaczyć komentarzem wszystkie pozostałe aktywności (obecnie nie posiadamy jeszcze do nich kodu źródłowego). Po uruchomieniu tej aktywności (powinna wyglądać jak na rysunku 14.15) kliknijmy kla wisz wyszukiwania (jest on widoczny na rysunku 14.1). W odpowiedzi zostanie uruchomio ne wyszukiwanie globalne. Będzie ono wyglądało tak, jak przedstawione na rysunku 14.2.
flll''fililliflili"fti''llll"''IRI
W przypadku aktywności niezwiązanej z wyszukiwaniem wciśnięcie klawisza wyszukiwania
powoduje wywoła n ie wyszuki wa n ia globalnego.
Zachowanie aktywności wyłączającej wyszukiwanie Jak zostało omówione w poprzednim paragrafie, wciśnięcie klawisza wyszukiwania w obec ności aktywności w żaden sposób niezwiązanej z przeszukiwaniem powoduje wywołanie wyszukiwania globalnego. Jednak aktywność posiada możliwość wyłączenia wyszukiwania poprzez zwrócenie wartości false z metody zwrotnej onSea rchRequested ( ) danej klasy ak tywności. W listingu 14.6 został przedstawiony kod źródłowy takiej aktywności.
Rozdział 1 4 • Wyszukiwanie w Androidzie
521
listing 14.6. Aktywność wyłączająca wyszukiwanie //nazwa pliku: NoSearchActivity.java pub li c class NoSearchActivity extends Acti v it y
{
@Ove r ride protected void onC reate( Bundle savedinstanceState) s u pe r . o n C reate ( s avedinstanceStat e ) ; setContentView ( R . layout . no_search_activit y ) ; return;
{
} @Over ride
public boolean onSearchRequested ( ) { return false;
}
}
Listing 14.7 prezentuje plik układu graficznego tej aktywności
.
listing 14.7. Plik XML NoSearchActivity //nazwa pliku: layout/no_search_activity.xml c?xml version= " l . 0" en codin g = " u t f B " ?> -
and roid : layout_height= " fill_parent " >
and roid : layout_width="fill_parent" and roid : layout_height="wrap_content" and ro id : text="@st ring/no sea rch a ct ivit y_ p rompt " />
Aktywność NoSea rchActivity możemy wywołać, klikając element menu Aktywność braku wyszukiwania, widoczny na rysunku 14. 1 6. Po jego kliknięciu zostanie \vyświetl ony ekran zilustrowan y na rysunku 14.17. Teraz wciśnięcie klawisza v.ryszukiwania (widocznego na rysun ku 14. 1 ) nie spowoduje żadnej reakcji. Wciśnięc ie tego przycisku nie zostanie odnotowane. ,
W przypadku obecności aktywności wyłączającej wyszukiwanie kliknięcie klawisza
flml w n E'7l lra ll l wyszukiwania spowoduje zablokowanie wywoływania wyszukiwania, także globalnego. • 11••-1 •
Wywoływanie wyszukiwania za pomocą menu Oprócz odpowiedzi na klawisz wyszukiwania aktywność może również w jawny sposób v.rywoływać ·wyszukiwanie za pomocą elementu menu wyszukiwania. Lis ting 14.8 ukawje kod źródłowy przykładowej aktywności posiadającej taką możliwość.
522 Android 2. Tworzenie aplikacji
Rysunek 14.17. Aktywność wyłączonego wyszukiwania Listing 14.8. Aktywność SearchlnvokerActivity //nazwa pliku: SearchlnvokerActivity.java public class SearchinvokerActivity extends Activity { @Over ride public void o n C reate ( B undle savedinstanceState) { s u pe r . on C reat e ( savedinstanceState ) ; setContentView( R . layout . sea rch_invoke r_activi t y ) ;
@Over ride public boolean onC reateOptionsMenu ( Menu men u ) { s u pe r . onC reateOptionsMen u ( men u ) ; Menuinflater inflater = getMenuinflat e r ( ) ; inflate r . inflate ( R . menu . se a rch_invoker_menu , men u ) ; return t rue ;
} @Over ride public boolean onOptionsitemSelected (Menuitem item) { appendMenu itemText ( it em ) ; if ( item. getitemid ( ) R . id . mid_si_clear) { this . emptyText ( ) ; return t rue ;
} if ( item . getitemid ( ) {
this . invokeSearch ( ) ;
R . id . mid s i sea rch )
Rozdział 14 • Wyszukiwanie w Androidzie
523
return t r u e ;
} return t rue;
p rivate TextView getTextView ( )
{ retu rn (Textview) th i s . f i ndViewByld ( R . id . text l ) ;
} private void a p pendMenui temText ( Menuitem menultem) { St ring title = menuitem . getTitl e ( ) . toString ( ) ; TextView t v = getTextView ( ) ; tv . setText ( t v . getText ( ) + " \ n " + title ) ; }
private void emptyText ( ) { Textview tv = getTextView ( ) ; t v . setText ( " " ) ; }
private void invokeSearch ( ) { this. onSearchRequested ( ) ;
} }
Najważniejsze fragmenty kodu zaznaczone zostały tłustym drukiem. Zauważmy, w jaki sposób identyfikator menu ( R . i d . mid_si_ s e a r c h ) wywołuje funkcję i n v o keSe a r c h, która z kolei wywołuje metodę on Se a rchRequested ( ) . Metoda ta przywołuje proces wyszukiwania.
W listin gu 14.9 przedstawiono układ graficzny tej aktywności. Listing 1 4.9. Plik XML układu graficznego aktywności Sea rchlnvokerActiv ity //nazwa pliku: layout/search_invoker_activity.xml
Z kolei listing 14. 1 0 zawiera kod XML menu tej aktywności.
524 Android 2. Tworzenie aplikacji Listing 1 4.1 O. Plik XML menu aktywności SearchlnvokerActivity //nazwa pliku :menu/search_invoker_rnenu.xml
cactivity android : name=" . Sea rchActivity" android : label= " S S S P : Aktywność wyszukiwania" a n d roid : la unchMode= " singleTop"> ccategory a n d roid : name="and roid . intent . catego ry. DEFAULT" />
="com . ai . android . search . s implesp . SimpleSuggestionProvider" /> c/application> cuses - s d k a n d roid : minSdkVersion= " 4 " />
Zwróćmy uwagę na uprawnienie prostego dostawcy propozycji w pliku kodu źródłowego (li sting 4.15) oraz w pliku manifeście (listing 4.16). W obydwu przypadkach jego wartość jest następująca: com . ai . android . se a r c h . simples p . SimpleSuggestionProvider
Rozdział 1 4 • Wyszukiwanie w Androidzie
535
Pozostałymi sekcjami pliku manifestu zajmiemy się po omówieniu innych aspektów prostego dostawcy propozycji.
Aktywność wyszukiwania dostępna w prostym dostawcy propozycji Jak już wspomnieliśmy, na implementację dostawcy propozycji składają się dwa elementy: właściwy dostawca treści i aktywność wyszukiwania, reagująca na propozycje generowane przez tego dostawcę. Powyżej omówiliśmy implementację dostawcy propozycji. Zajmiemy się teraz badaniem aktywności wyszukiwania. Analizę przeprowadzimy podobnie, jak w przypadku dostawcy treści. Rozpoczniemy od ogólne go omówienia zadań stojących przez aktywnością wyszukiwania. Następnie zaprezentujemy pełny kod źródłowy tej aktywności, dzięki czemu wyjaśnimy, w jaki sposób te zadania są spełniane.
Zadania prostej aktywności wyszukiwania Aktywność wyszukiwania jest wywoływana przez system za pomocą ciągu znaków stano wiącego kwerendę. Aktywność ta musi z kolei odczytywać tę kwerendę z intencji i wykony wać odpowiednie czynności. Ponieważ mamy do czynienia z aktywnością, istnieje możliwość jej wr.vołania za pomocą innych intencji lub działań. Z tego powodu dobrym zwyczajem jest sprawdzanie intencji wy„..vołującej tę aktywność. W naszym przypadku działaniem wywołującym naszą aktyw ność jest ACTION_SEARCH. W pewnych okolicznościach aktywność wyszukiwania może zostać samoistnie wywołana. W przypadku dużego prawdopodobieństwa wystąpienia tego zjawiska powinniśmy zdefi niować tryb s i n g leTop uruchamiania tej aktywności. Aktywność pov,,jnna również posia dać zdolność uruchamiania metody onNewintent ( ) . Ta kwestia zostanie poruszona w para grafie „Metody onCreate() i onNewlntent()''. Każda czynność wykonywana na ciągu znaków kwerendy zostanie zapisana w dzienniku. Po zapisaniu kwerendy w dzienniku musimy ją zapisać w dostawcy Sea rchRecentSuggestions '+ P rovider, aby w przypadku kolejnych wyszukiwań była wyświetlana jako jedna z propozycji. Spójrzmy teraz na kod źródłowy klasy aktywności wyszukiwania.
Pełny kod źródłowy aktywności wyszukiwania W listingu 14.17 został przedstawiony kod źródłowy klasy Sea rchActivity. Listing 14.17. Aktywność wyszukiwania dla dostawcy SimpleSuggestionProvider //nazwa pliku: SearchActivity.java public class Sea rchActivity extends Activity { p rivate final static String tag ="Sea rchActivit y " ; @Over ride
protected void onCreate(Bundle savedinstanceState) { supe r . onCreate ( savedinstan ceStat e ) ; Log . d ( ta g , "Jestem tworzona " ) ;
536 Android 2. Tworzenie aplikacji /Iw przeciwnym wypadku wykonuje tę czynność setContentView( R . layout . l ayout_t e st_ s ea rch_ activity ) ;
//this.setDefaultKeyMode(Activity.DEFA ULT_KEYS_SEARCH_GLOBA L); this . setDefaultKeyMode (Activity . DEFAULT_KEYS_SEARCH_LOCAL ) ; li umieszcza tutaj i przetwarza kwerendę wyszukiwania final Intent que r y inte n t fina! String queryAction
=
=
g et i n t e nt ( ) ; queryintent . getAction ( ) ;
if ( In tent . ACTION_ S EARCH . eq u a ls ( qu e ryAct ion ) ) { Log . d ( t ag , " n owa intencj a dla wyszu kiwan ia" ) ; this . doSearchQuery ( queryinten t ) ; else { Log . d ( t a g , " nowa inten c j a NIE dla wyszukiv1ania " ) ;
}
ret u r n ;
} @Ove rride
public void onNewintent( final Intent newintent) { s u pe r . onNewi ntent ( newinten t ) ; Log . d ( t a g , " n owa intencja mnie wywołu j ąca" ) ; li umieszcza tutaj i przetwarza
kwerendę wyszukiwania
final Intent queryintent = getintent { ) ; final String q u e ryAction queryintent . getAction ( ) ; if ( I ntent . ACTION_SEARCH . equal s ( q u e ryAction ) ) =
{
t h i s . doSearc hQuery { q ue ryintent ) ; Log . d { t ag , " n owa inten c j a dla wyszukiwania " ) ;
}
else { Log . d { t a g , " n owa intencja N I E dla wyszukiwania " ) ;
} private void doSearchQuery( f inal Intent queryintent)
{
final String q u erySt ri ng
=
queryintent . getStringExtra{ SearchManager . QUERY ) ;
li Zapisuje ciąg znaków kwerendy w bieżącym dostawcy propozycji kwerend. SearchRecentSuggestions suggestions
=
new SearchRecentSuggestions ( this ,
SimpleSuggestionP rovide r . AUTHORITY, SimpleSuggestionProvide r . MODE ) ; suggestion s . saveRecentQuery ( querySt ring, n u l l ) ;
}
Sprawdźmy, w jaki sposób aktywność wyszukiwania sprawdza działanie i odczytuje ciąg zna ków kwerendy.
Rozdział 1 4 • Wyszukiwanie w Androidzie
537
Sprawdzanie działania i odczytywanie kwerendy Kod aktywności wyszukiwania sprawdza vvywołujące ją działanie poprzez przeanalizowanie intencji wywołującej i porównanie jej z c o n s t a nt i n t e n t . AeTION_SEARCH. Jeżeli mamy do czynienia z takim samym działaniem, zostaje wywołana funkcja doSea r c h Q u e ry ( ) . Za pomocą tej funkcji aktywność wyszukiwania odczytuje ciąg znaków kwerendy, używając dodatkowej intencji. Służy do tego celu kod: fina! S t ring queryString = queryinten t . getStringExt r a ( SearchManage r . QUERY ) ;
Zauważmy, że ta dodatkowa intencja została zdefiniowana jako Sea rchManage r . QUERY. W tym rozdziale znajduje się wiele takich elementów dodatkowych, zdefiniowanych w odniesieniu do interfejsu API SearchManager (adres do materiałów dotyczących tego odniesienia został umieszczony w ustępie „Zasoby").
Metody onCreate() i onNewlntent() Aktywność wyszukiwania zostaje uruchomiona przez system po wpisaniu przez użytkowni ka tekstu w polu wyszukiwania i kliknięciu propozycji łub ikony wyszukiwania. Zostaje utworzona aktywność wyszukiwania i wywołana jej metoda one reate ( ) . Intencja przeka zana tej metodzie ustanowi działanie AeTI ON_ S EAReH. Czasem zdarza się, że aktywność nie zostaje utworzona, lecz są przekazywane nowe kryteria wy szukiwania poprzez metodę onNewintenet ( ) . jak to się dzieje? Metoda zwrotna onNewintent ( ) jest ściśle związana z trybem uruchamiania aktywności. Przyglądając się listingowi 14.16, może my stwierdzić, że aktywność V.'}'SZukiwania otrzymuje wartość singleTop w pliku manifeście. Aktywność skonfigurowana w trybie s i n gleTop informuje system, aby nie tworzyć nowej aktywności, jeżeli przebywa ona na wierzchu stosu. W takim przypadku zamiast metody one reate ( ) zostaje wywołana metoda onNewin t e n t ( ) . Właśnie dlatego w kodzie źródłowym aktywności (listing 14.17) intencja jest sprawdzana w dwóch miejscach.
Jak przetestować metodę onNewlntent() Po zaimplementowaniu metody onNewintent ( ) stwierdzimy, że jest ona wywoływana w nie standardm-.'Y sposób. Rodzi się następujące pytanie: kiedy aktywność wyszukiwania znajduje się na wierzchu stosu? To się zazwyczaj nie zdarza. Wyjaśnijmy, dlaczego. Załóżmy, że aktywność A wywołuje proces wyszukiwania, wskutek czego pojawia się aktywność wyszukiwania B. Aktywność B powoduje wyświetlenie wyni ków, a użytkownik wraca do poprzedniego ekranu za pomocą przycisku powrotu. W tym czasie nasza aktywność B, stanowiąca naszą aktywność wyszukiwania, ustępuje miejsca na szczycie stosu aktywności A. Użytkownik może ewentualnie wcisnąć przycisk powrotu do ekranu początkowego i skorzystać z wyszukiwania globalnego, co spowoduje umieszczenie aktywności ekranu początkowego na wierzchu stosu. Aktywność wyszukiwania można umieścić na wierzchu stosu w następujący sposób: po wiedzmy, że z powodu wyszukiwania wynikiem aktywności A jest aktywność B. Jeżeli ak tywność B definiuje tryb type-to-search, to po przejściu do tej aktywności zostanie ona po nownie wywołana, z nowymi kryteriami. Listing 14.17 ukazuje sposób konfiguracji trybu type-to-search. Oto odpowiedni fragment kodu: this . setDefault KeyMod e ( A ctivity . DEFAULT KEYS_ S EARCH_ LOCAL ) ;
538
Android 2. Tworzenie aplikacji
Zapisywanie kwerendy za pomocą dostawcy Search RecentSuggesti onsProvider Stwierdziliśmy istnienie konieczności zachowywania napotkanych kwerend przez aktywność wyszukiwania w celu wyświetlania ich w formie propozycji. Poniżej przedstawiamy odpowie dzialny za tę czynność segment kodu: final String queryString = queryintent . g etStringExtra( SearchManage r . QUERY) ;
li Zapisuje ciąg znaków kwerendy w bieżącym dostawcy propozycji kwerend.
SearchRecentSuggestions suggestions = new SearchRecentSuggestion s ( t h i s , SimpleSuggestionProvid e r . AUTHORITY, SimpleSuggestionProvid e r . MODE ) ; suggestio ns . saveRecentQ u e r y ( q u e ryString, null ) ;
Powyższy kod udowadnia, że Android przekazuje informację kwerendy
w
postaci parametru
EXTRA poprzez intencję.
Wprowadzoną kwerendę można zachować za pomocą dostawcy Sea rchRecentSuggestions '+Provider poprzez utworzenie i zachowanie nowego obiektu propozycji. Ponieważ skorzy staliśmy z trybu zachowywania jednej linijki kwerendy, wartość drugiego argumentu meto dy saveRecentQuery wynosi n u l l . Zajmiemy się teraz definicją metadanych wyszukiwania, łączącą aktywność wyszukiwania z dostawcą propozycji wyszukiwania.
Metadane wyszukiwania Definicja wyszukiwania w Androidzie rozpoczyna się od aktywności wyszukiwania. Jest ona definiowana w pliku manifeście jako pierwsza. Częścią jej definicji jesl określenie miejsca przebywania pliku XML metadanych wyszukiwania (listing 14.16). Listing 14. 1 8 przedstawia plik metadanych wyszukiwania wykorzystywany przez naszą apli kację. Listing 14.18. Metadane wyszukiwania dla dostawcy SimpleSuggestionProvider //nazwa pliku: searchable.xml
android : includeinGlobalSearch="true" and roid : sea rchSuggestAuthority= " c om . ai . and roid . se a r c h . simplesp. SimpleSuggestionProvider" android : s ea rchSuggestSelection=" ? " />
W powyższym listingu widoczne są trzy atrybuty dotyczące dostawcy propozycji. Omówimy je po kolei.
Rozdział 1 4 • Wyszukiwanie w Androidzie
539
Pierwszy atrybut, includeinGlobalSearch, każe Androidowi używać tego dostawcy jako jednego ze źródeł globalnego pola QSB. Atrybut searchSuggestAuthori ty wskazuje zdefiniowane w pliku manifeście uprawnienie tego dostawcy propozycji (listing 14.16). Trzeci atrybut, searchSuggestSelection, przyjmuje zawsze wartość ? w przypadku, gdy wy wodzi się z poprzedniego dostawcy propozycji wyszukiwania. Taki ciąg znaków jest przeka7.y wany dostawcy propozycji jako wartość selection metody q u e r y dostawcy treści. Zazwy czaj w ten sposób jest reprezentowana klauzula where przechodząca do instrukcji wyboru. Android przekazuje następnie kwerendę jako pierwszy wpis w tablicy argumentów wyboru, obecnej w metodzie kwerendy danego dostawcy treści. Ponieważ kod przeprowadzający te czynności jest ukryty w poprzednim dostawcy propozycji "''Yszukiwania, nie mamy możli wości przedstawienia sposobu używania tych argumentów w metodzie q u e r y dostawcy tre ści. Szczegółowo zajmiemy się tym w następnym paragrafie . . . Na tym zakończymy omawianie pisania aktywności wyszukiwania dla prostego dostawcy pro pozycji. Skoro już zapoznaliśmy Czytelników z koncepcjami dostawcy propozycji wyszu kiwania i aktywności wyszukiwania, zajmijmy się aktywnością wywołania wyszukiwania, która posłuży nam jako główny punkt wejścia dla tej aplikacji, umożliwiający nam przete stowanie '�ryszukiwania lokalnego.
Aktywność wywołania wyszukiwania Chociaż aktywność ta nie jest niezbędna do działania dostawcy propozycji, pozwala nam na wywołanie wyszukiwania lokalnego w czasie, gdy jest obsługiwana na pierwszym planie. Li sting 14.19 przedstawia kod źródłowy takiej aktywności 'vywołania wyszukiwania. Listing 14.19. Dostawca SimpleSuggestionProvider: główna a ktywność public class SimpleMainActivity extends Activity { @Over ride public void onCreate(Bundle savedinstanceState) { s u pe r . onCreat e ( s avedinstanceState ) ; se t ContentView ( R . layout . m a i n ) ; } }
Jeśli spojrzymy na definicję tej aktywności w pliku manifeście (listing 14.16), stwierdzimy, że klasa SearchActivity nie została jawnie zdefiniowana dla tej aktywności jako domyślne wyszukiwane lokalne. Wynika to z faktu, że tę specyfikację zastosowaliśmy na poziomie aplikacji, a nie aktywności, wprowadzając w pliku manifeście następujący fragment kodu:
Zwróćmy uwagę, że powyższy fragment został umieszczony w pliku manifeście poza ak tywnościami (listing 14.16). Dzięki tej specyfikacji Android „wie", że dla wszystkich aktyw ności zawartych w tej aplikacji, w tym dla samej aktywności Sea rchActi v i ty, domyślną aktywnością jest SearchActivity. Można wykorzystać tę wiedzę do wywołania metody onNewintent ( ) , klikając przycisk wyszukiwania podczas sprawdzania wyników w klasie Search
540
Android ·2. Tworzenie aplikacji
przypadku zdefiniowania domyślnego wyszukiwania jedynie na poziomie prostej aktywności wywołania \-vyszukiwania, a nie na poziomie całej aplikacji, wspomniana technika nie ma prawa bytu.
'-+Activity. W
Poniżej przedstawiamy prosty układ graficzny głównej aktywności V.'}'\Vołania wyszukania: //nazwa pliku: /res/layout/main.xml c?xml version=" l . O " encoding= " ut f - 8 " ?>
Zaprezentujemy teraz plik strings.xml współpracujący z plikiem układu graficznego oraz resztą aplikacji: c?xml version= " l . O " encoding= " u t f - 8 " ?>
Jest to prosta aktywnoś ć . Kliknijmy klawisz wyszukiwa n i a , a b y wywołać wyszukiwanie lokalne. \n\n Dostawca p ropozycji będzie również brał udział w wyszukiwaniu globalnym. Jeżeli u ru c homimy tę aplikację z poziomu wyszu kiwania globalnego, nie uj rzymy tego widoku, lecz zostaniemy przeniesieni bezpośrednio d o widoku sea rchactivity .
Pro s t y dostawca propozycji
Demonstracja wyszukiwania lokalnego Podpowiedż wyszu kiwania lokalnego
Wrażenia użytkownika podczas korzystania z prostego dostawcy propozycji Po uruchomieniu aplikacji ujrzymy jej ekran początko\"')' \"')'glądający tak, jak na rysunku 14.23 (jest to nasza aktywność "''}'\Vołania \"')'Szukiwania).
Rozdział 1 4 • Wyszukiwanie w Androidzie
541
Rysunek 14.23. Prosty dostawca propozycji: główna aktywność (dostosowana do wyszukiwania lokalnego) Jeżeli klikniemy klawisz wyszukiwania w trakcie działania aktywności na pierwszym planie, zostanie wywołany proces wyszukiwania lokalnego, widoczny na rysunku 14.24. 'ttl !Ml © 6:51 Demonsuaqa \\'fSl'-lklwJnJa lokalnego
Rysunek 1 4.24. Prosty dostawca propozycji: pole wyszukiwania lokalnego >la rysunku 14.24 nie widać żadnych propozycji, ponieważ jak na razie niczego nie szukali śmy. Natomiast na podstawie etykiety i podpowiedzi, zdefiniowanych w pliku XJ'v1L meta danych wyszukiwania, dowiadujemy się, że mamy do czynienia z wyszukiwaniem lokalnym. Przejdźmy do następnego etapu i poszukajmy ciągu znaków testl. Zostaniemy przeniesieni do ekranu Aktywność wyszukiwania, przedstawionego na rysunku 14.25.
542
Android 2. Tworzenie aplikacji
Rysunek 1 4.25. Prosty dostawca propozycji: aktywność wyświet lająca wyniki wyszukiwania lokalnego
Jak wynika z widocznego w listingu 14. 1 7 kodu źródłowego klasy SearchActivity, nie vry czynia ona cudów na ekranie, jednak niedostrzegalnie zachowuje kwerendy w bazie danych. Jeśli teraz wrócimy do głównego ekranu (wciskając klawisz cofania) i ponownie wywołamy wyszukiwanie, ujrzymy ekran, widoczny na rysunku 14.26, na którym propozycje wyszuki wania zostały zapisane wcześniejszym wpisem kwerendy. �!!li• 8:32 Oemons1raqa wys.zuk1wdn1a 1oka!nego
l
Rysunek 1 4.26. P rosty dostawca propozycji: wprowadzona propozycja lokalna
Nadarza się teraz dobra okazja na wywołanie metody onNewi n t e n t ( ) . Na ekranie aktywno ści wyszukiwania (rysunek 14.24) możemy wpisać na przykład literę t i zostanie wywołane wyszukiwanie za pomocą trybu type-to-search, a wywołanie metody onNewintent ( ) zosta nie zapisane w pliku dziennika.
Rozdział 14 • Wyszukiwanie w Androidzie
543
Zobaczmy co należy zrobić, aby nasze propozycje zostały wyświetlone w polu wyszukiwania globalnego Ponieważ włączyliśmy obiekt i n c ludeinGlobalSearch, propozycje te powinny być widoczne również w przypadku wyszukiwania globalnego. Musimy jednąk przedtem zezwolić tej aplik acj i na obsługę glob al nych propozycji wyszukiwania, co zostało zaprezen towane na rysunku 14.27. ,
.
Rysunek 14.27. Włączanie prostego dostawcy propozycji wyszukiwania
�a początku rozdziału podaliśm y inst ru kcję dotarcia do tego ekranu. Po zaznacze ni u na szego dostawcy wyszukiwanie globalne, ukazane na rysu nku 14.28, będzie współpracowało tym dostawcą propozycji.
z.
t!ll � lllll fi 7:45
_J ą.
łysunek 1 4.28. Więcej wyników: prosty dostawca propozycji
544
Android 2. Tworzenie aplikacji
Podczas wyszukiwania konkretnego elementu w polu wyszukiwania globalnego (zostało to omówione w ustępie „Wrażenia użytkownika podczas korzystania z prostego dostawcy propozycji") pojawi się pole wyszukiwania lokalnego, przedstawione na rysunku 14.29. llil'idllm© 10:42 Oemon'itracj,1 wyszuklwanld lokdlnego
'") t�
1est2 test1
Rysunek 14.29. Przejście do wyszukiwania lokalnego: prosty dostawca propozycji Na tym zakończymy analizę poświęconą prostym dostawcom propozycji. Dowiedzieliśmy się, w jaki sposób można wykorzystać wbudowaną klasę RecentSearchSuggestionProvider do zapamiętywania zapytań swoistych dla danej aplikacji. Korzystając z tej techniki, możemy nawet wprowadzać propozycje lokalne do kontekstu globalnego. Jednak to proste ćwiczenie nie przedstawia sposobu pisania dostawców propozycji od pod staw. Co ważniejsze, nie posiadamy jeszcze najmniejszego pojęcia na temat sposobu zwracania zestawu propozycji przez dostawcę oraz nie wiemy, jakie kolumny są dostępne w takim ze stawie. Aby zrozumieć te i inne rzeczy, musimy zaimplementować od podstaw własnego dostawcę propozycji.
I m plementacja niestandardowego dostawcy propozycji Technologia v.ryszukiwania w Androidzie jest zbyt elastyczna, aby zwalczyć pokusę dosto sowania jej do własnych potrzeb. Ponieważ w poprzednim paragrafie korzystaliśmy z wbu dowanego dostawcy propozycji, wiele funkcji w klasie Sea rchRecentSugg e s t io n s P rovider zostało ukrytych i nieomówionych. Przeanalizujemy te pominięte szczegóły, implementując niestandardowego dostawcę treści, nazwanego Sugg e s t U r l P rovider. Rozpoczniemy od wyjaśnienia mechanizmu działania tego dostawcy. 1 astępnie podamy listę p lików potrzebnych do jego implementacji. Lista ta powinna dać nam ogólne pojęcie na te mat procesu budowania własnego dostawcy propozycji. Jak już kilkakrotnie wspominaliśmy, podczas tworzenia dostawcy propozycji implementu jemy dwa główne elementy: właściwego dostawcę propozycji oraz powiązaną z nim aktywność wyszukiwania. Podobnie jak w przypadku omawiania prostego dostawcy propozycji przyjrzymy się obydwu składnikom pod kątem ich zadań oraz sposobu implementacji tych zadań.
Rozdział 14 • Wyszukiwanie w Androidzie
545
· ,. przypadku obiektu dostawcy propozycji poznamy rodzaje adresów URL stosowane do '')woływania dostawcy propozycji, sposoby przekazywania fragmentów tekstu do tego do stawcy, listę kolumn zwracanych przez dostawcę oraz metody przekazywania informacji od dostawcy propozycji do aktywności wyszukiwania. \V
implementacji aktywności wyszukiwania zobaczymy, w jaki sposób aktywność ta jest wywo �wana oraz jakie działania związane z wyszukiwaniem są do niej przekazywane. Pokażemy :netodę odczytywania przez tę aktywność wartości z intencji ją wywołującej.
�a koniec pokażemy zastosowanie utworzonej aplikacji. Zaczynajmy.
Planowanie niestandardowego dostawcy propozycji :\'asz niestandardowy dostawca propozycji będzie nosił nazwę SuggestURLProvider. Głównym .::elem tego dostawcy jest monitorowanie tekstu wpisywanego w polu QSB. Jeżeli kwerendę ,·yszukiwania stanowi tekst wyglądający jak na przykład g reat . m [sufiks . m jest skrótem od · ·yrazu „znaczenie" (ang. meaning)], dostawca zinterpretuje pienvszą część zapytania jako >!owo i zaproponuje wywołanie internetowego adresu URL. :Jla każdego wyrazu są proponowane dwa adresy URL. Pierwszy adres pozwala użytkowniowi na wyszukanie słowa za pomocą witryny http://www.thefreedictionary.com, drugi kie -:.ie nas na stronę http://www.google.com. Wybranie jednej z propozycji przenosi użytkow-.�'.za bezpośrednio na te witryny. Jeżeli użytkownik kliknie ikonę wyszukiwania w polu �SB, aktywność wyszukiwania utworzy v.rpis tej kwerendy w dzienniku, znajdującym się prostym układzie graficznym tej aktywności. Stanie się to bardziej zrozumiałe po zapre -entowaniu odpowiednich zrzutów ekranu. ::irz}�rzyjmy się liście plików tworzących nasz projekt.
Pliki wymagane do implementacji projektu SuggestU RLProvider ·ak wcześniej stwierdziliśmy, dwoma głównymi plikami są SearchActivity.java i Suggest :·r/Provider.java. Jednak do poprawnego działania projektu wymagana będzie implementa .:-•a kilku dodatkowych plików. Poniżej przedstawiamy listę tych plików wraz z ich krótkim vpisem. W dalszej części rozdziału zamieściliśmy kod źródłowy każdego z wymienionych :u plików. • SuggestUrlProvider.java. W tym pliku zostaje zaimplementowany protokół niestandardowego dostawcy propozycji. W naszym przypadku dostawca ten tłumaczy ciągi znaków kwerend na słowa i zwraca parę propozycji za pomocą kursora propozycji. • SearchActivity.java. Aktywność ta jest odpowiedzialna za odbieranie kwerend lub propozycji dostarczanych przez dostawcę propozycji. Definicja aktywności SearchActivity ma również za zadanie powiązanie dostawcy propozycji z tą aktywnością. • layout/layout_search_activity.xml. Ten plik układu graficznego jest używany opcjonalnie przez klasę Sea rchAct i vi ty. W naszym przykładzie służy on do umieszczenia wysłanej kwerendy we wpisie dziennika.
546 Android 2. Tworzenie aplikacji • values/strings.xml. Zawiera definicje ciągów znaków układu graficznego, tytuł i podpowiedź wyszukiwania lokalnego, itp. • xml/searchable.xml. Plik XML metadanych wyszukiwania łączący klasę SearchActivity, dostawcę propozycji i pole QSB. • manifest.xml. Plik manifest aplikacji, w którym są definiowane aktywność wyszukiwania i dostawca propozycji. Tutaj również określamy, że klasa Sea rchActivity będzie v.rywoływana wobec naszej aplikacji jako wyszukiwanie lokalne. Spośród wymienionych plików kluczową rolę spełniają klasy SuggestUrlProvider i Search '+Activity. Zaczniemy najpierw od analizy dostawcy SuggestU rlProvider.
Implementacja klasy SuggestUrlProvider W przypadku naszego projektu niestandardowego dostawcy propozycji klasa SuggestUrl '+Provider jest odpowiedzialna za implementację protokołu dostawcy propozycji. Badanie implementacji tej klasy rozpoczniemy od analizy jej zadań.
Zadania dostawcy propozycji Dostawca propozycji jest wywoływany przez proces wyszukiwania w Androidzie za pomocą identyfikatora URI określającego dostawcę oraz dodatkowego argumentu, przekazującego kwerendę. W Androidzie do wywoływania dostawcy propozycji wykorzystywane są dwa rodzaje iden tyfikatorów URI. Pierwszy z nich nosi nazwę identyfikatora URI wyszukiwania i służy do zbierania zestawu propozycji. Odpowiedzią musi być co najmniej jeden wiersz, zawierający zbiór znanych kolumn. Drugi identyfikator URI nosi nazwę identyfikatora URI propozycji i służy do aktualizm.va nia propozycji przechowywanej w pamięci podręcznej. Odpowiedzią musi być pojedynczy wiersz składający się z grupy znanych kolumn. Dostawca propozycji musi również określić w metadanych wyszukiwania sposób otrzymywa nia fragmentu kwerendy wyszukiwania. Można tego dokonać za pomocą argumentu select metody q ue ry lub za pomocą ostatniego segmentu samego identyfikatora URI (również przekazywanego w postaci jednego z argumentów do metody kwerendy danego dostawcy). Dostępna jest duża liczba kolumn dla dostawcy treści, z których każda uruchamia określone działanie wyszukiwania. Dostawca musi najpierw określić zbiór zwracanych kolumn: • Wykorzystaj jedną z kolumn do kontrolowania sposobu zapisu w pamięci podręcznej propozycji zwracanych procesowi wyszukiwania w Androidzie. • Zastosuj kolumny, jeżeli chcesz, aby tekst propozycji został przepisany do pola kwerendy. • Użyj kolumny w przypadku konieczności bezpośredniego wywołania działania, zamiast pokazywać zestaw wyników po kliknięciu propozycji przez użytkownika.
Kod źródłowy dostawcy SuggestUrlProvider Listing 14.20 przedstawia kod źródłowy klasy SuggestUrlProvider. W dalszej części roz działu, podczas omawiania każdego z zadań dostawcy, będą szczegółowo analizowane po szczególne fragmenty kodu.
Rozdział 1 4 • Wyszukiwanie w Androidzie
Listing 1 4.20. Kod źródłowy nie sta nd a rdowego dostawcy propozycji public class SuggestUrlProvider extends ContentProvider
{
p rivate static final String tag = "SuggestUrlP rovide r " ; public static String AUTHORITY =
"com . ai . android . search . custom . s uggestu rlprovider" ; p rivate static final i n t SEARCH_SUGGEST = O ; private static final i n t SHORTCUT_ REFRESH = l ; private static final UriMatcher sURIMatcher buildUriMatch e r ( ) ; =
private static finał String [ ] COLUMNS { "_id " , li mu s i zawrzeć tę kolumnę SearchManage r . SUGGEST_COLUMN_TEXT 1 , =
Searc hManag e r . SUGGEST_ COLUMN_ TEXT_ 2 , SearchManage r . SUGGEST_COLUMN_INTENT_DATA, SearchManag e r . SUGGEST_COLUMN_INTENT_ACTION, SearchManage r . SUGGEST_COLUMN_SHORTCUT_ID
}; private static UriMatcher buildUriMatche r ( ) {
UriMatcher matcher = new U riMatche r ( U riMatche r . NO MATCH) ; matche r . addURI ( AUTHORITY, SearchMa nag e r . SUGGEST_URI_PATH_QUERY, SEARCH_ SUGGEST ) ; mat c he r . addURI ( AUTHORITY, SearchManage r . SUGGEST_ URI_ PATH_ QUERY + li/*" , SEARCH_SUGGEST ) ; match e r . addURI( AUTHORITY, SearchMana ge r . SUGGEST_URI_PATH_SHORTCUT, SHORTCUT_REFRESH ) ; matc h e r . addURI ( AUTHORITY, SearchManager . SUGGEST_ URI_ PATH_ SHORTCUT + " /* " ' SHORTCUT_REFRESH ) ; return mat c h e r ;
@Over ride
public boolean onC reate ( ) { /lnie robimy tu nic szczególnego
Log . d ( ta g , ·�1ywolana metoda one reate " ) ; return t rue;
} @Over ride
public Cursor query(Uri uri, String [ ] projection, String selectio n , String [ ] selectionArgs, String sortOrder) {
Log . d ( ta g , " kwerenda wywalana za pomocą identyfikatora u ri : " + u ri ) ; Log . d ( ta g , "wybó r : " + selectio n ) ; String query selectionArg s [ O ] ; Log . d ( ta g , " kwerenda : " + query ) ; =
547
548
Android 2. Tworzenie aplikacji
switch ( s URIMatche r . match ( u ri ) ) {
case SEARCH_SUGGEST:
Log . d ( ta g , "1vywo łana propozycja wyszukiwa n i a " ) ; return getSuggestio n s ( q u e ry ) ;
case SHORTCUT_REFRESH : Log . d ( ta g , "wywołane odświeżenie skrót u " ) ; return n u l l ; default: t h row new IllegalArgumentExcept i o n ( "Nieznany adres URL " + u ri ) ; } }
private Cursor getSuggestions ( String query)
{
if ( q u e ry == n u l l ) return null ; String word = getWord ( q u e ry ) ; if ( wo rd == n u l l ) return n u l l ; Log . d ( tag , " kwerenda przekracza długość 3 lite r " ) ; MatrixCu r s o r c u r s o r = new MatrixCu rso r ( COLUMNS J ;
l/cursor.addRow(createRow(query, "rowl )); "
c u r s o r . addRow( c reateRowl ( wo rd ) ) ; curso r . addRow ( c reateRow2 (word ) ) ; return c u r s o r ;
private Object [ J createRowl (String query) { return columnValuesOfQuery ( qu e r y , " a n d roid . i ntent . a ction . VI EW" ,
"http: llwww. t hefreediction a ry . coml"
+
query,
"Wyszukaj na stronie f reedictionary . com wyra z " , q u e ry ) ;
} p rivate Object [ ] c reateRow2 ( S t ring query) { return columnValuesOfQue r y ( q u e r y , "and roid . intent . a ctio n . VIEW" ,
"http: llwww .google. com/search?hl=en&source=hp&q=define%3A/" +
que ry , "wyszukaj na stronie google . com wyraz " , query) ;
} private Object [ ] columnValuesOfQuery (String query, String String St ring String
intentAct i o n , u rl , text l, text2)
{ return n e w String [ ] { query, li _id text l , /l textl text2, li text2
url,
li intent_data (umieszczony po kliknięciu elementu)
Rozdział 14 • Wyszukiwanie w Androidzie
549
intentActi o n , !!działanie Sea rchManage r . SUGGEST_ NEVER_MAKE_ SHORTCUT };
p rivate Cursor refreshShortcut (String shortcut!d, String [ ] projection) { return n u l l ;
} public String getTyp e ( U ri uri) { switch ( sURIMatche r . match ( u r i ) ) { c a s e SEARCH_SUGGEST: return SearchManage r . S UGGEST_MIME_TYP E ; c a s e SHORTCUT_REFRESH: return SearchManage r . SHORTCUT_MIME_TY P E ; default : t h row new IllegalArg umentException ( "Nieznany a d r e s URL
"
+ u ri ) ;
} } public U ri insert ( U ri u r i , ContentValues values) t h row new UnsupportedOperation Exception ( ) ; } public i n t delete ( U ri u ri , String selection , St ring [ ] selectionArgs) { t h row new Unsuppo rtedOperationException ( ) ; }
public int update(Uri u r i , ContentValues values, String selection, Strin g [ ] selectionArgs) { t h row new UnsupportedOperationException ( ) ; }
p rivate String getWo r d ( S t ring query)
{
int dotlndex = q u e r y . indexOf ( ' . ' ) ; i f (dotlndex < O ) return n u l l ; r e t u r n query . s ubstring ( O , dotlndex ) ;
}
Identyfikatory URI dostawcy propozycji Po zaprezentowaniu pełnego kodu źródłowego niestandardowego dostawcy propozycji czas się przyjrzeć, w jaki sposób fragmenty tego kodu spełniają zadania związane z identyfikato rami URI.
Najpie rw zobaczmy, jaki jest format identyfikatorów URI wykorzystywanych do wywoły wania dostawcy propozycji. Jeżeli nasz dostawca propozycji posiada uprawnienie com . ai . android . s earch . custom . s u g g e s t u rlprovider
to Android będzie wysyłał dwa możliwe identyfikatory URI. P ierwszy typ tego identyfikatora nosi nazwę identyfikatora URI wyszukiwania. Wygląda on następująco:
550
Android 2. Tworzenie aplikacji
conten t : //com . a i . and roid . se a r c h . suggestu rlprovider/sea rch_suggest_query
albo content : //com . a i . android . search. suggesturlp rovider/sea rch_suggest_query/
Identyfikator tego typu jest wydawany na początku procesu wpisywania tekstu w polu QSB. W jednej z odmian tego identyfikatora kwerenda jest przekazywana na jego końcu jako do datkowy element (segment ścieżki). Możliwość dołączania kwerendy jako segmentu ścieżki jest definiowana w pliku metadanych wyszukiwania searchable.xml. Powrócimy do tego te matu podczas szczegółowego omówienia metadanych wyszukiwania. Drugi rodzaj identyfikatora URI jest przeznaczony dla dostawcy propozycji i jest związany ze skrótami wyszukiwania. Skróty wyszukiwania w Androidzie są propozycjami (rysunek 14.3), które system postanawia przechowywać w pamięci podręcznej, zamiast wywoływania dostawcy propozycji w celu uzyskania nowej treści. Tematykę skrótów wyszukiwania poruszymy pod czas analizowania kolumn propozycji. Na razie wystarczy wiedzieć, że drugi rodzaj identyfikato ra propozycji przybiera następujące kształty: content : // com . ai . a nd roid . sea rch . s uggest u rlprovider/search_suggest_sho rtcut
lub content : //com. ai . an d roid . search . suggestu rlp rovider/sea rch_suggest_sho rtcut/
Identyfikator tego typu jest wydawany przez system podczas próby określenia ważności skrótów przechowywanych w pamięci podręcznej. Taki identyfikator URI nosi nazwę identyfikatora URI skrótu. Jeżeli dostawca zwróci pojedynczy wiersz, bieżący skrót zostanie zastąpiony nowym. Jeżeli zostanie przesłana wartość null, bieżąca propozycja przestanie być uznawana za ważną. Klasa SearchManager definiuje w Androidzie dwie stałe, pozwalające na odróżnianie tych segmentów identyfikatorów URI ( sea rch_ s u g g e s t_ s e a r c h i s e a rc h_ s ug g es t_ s ho rtcut ) . Są to odpowiednio: Sea rchManage r . SUGGEST_URI_PATH_QUERY Sea rchManag e r . SUGGEST_URI_ PATH_SHORTCUT
Zadaniem dostawcy propozycji jest rozpoznawanie tych identyfikatorów, przychodzących w metodzie query ( ) . W listingu 14.20 został zaprezentowany sposób przeprowadzania tej czynności za pomocą klasy U riMatc h e r (zastosowanie klasy U riMatcher zostało szczegółowo omówione w rozdziale 3.).
Implementacja metody getType() i określenie typów MIME Ponieważ dostawca propozycji jest ogólnie dostawcą treści, ma obowiązek zaimplemento wania kontraktu, definiującego implementację metody getType ( ) .
W naszym przypadku implementacja metody getType ( ) została ukazana w listingu 14.20. W strukturze v.'Yszukiwania poprzez klasę Sea rchManage r wprowadzono parę stałych, wspomagających przetwarzanie takich typów MIME. Tymi typami MIME są: SearchManag e r . SUGGEST_MIME_TYPE Sea rchManage r . SHORTCUT_MIME_TYPE
Rozdział 1 4 • Wyszukiwanie w Androidzie
551
Są one tłumaczone na wyrażenia: vnd . android . cu rs o r . dir/vnd . an d roid . sea rch . suggest vnd . an d roid . cu rs o r . item/vn d . android . search . suggest
Przekazywanie kwerendy do dostawcy propozycji: argument Selection Ostatnim etapem zastosowania któregoś z identyfikatorów URl omówionych powyżej do wywołania dostawcy jest wywołanie metody query ( ) dostawcy propozycji w celu uzyskania kursora propozycji. Jeżeli przyjrzymy się implementacji metody que ry ( ) z listingu 14.20, to zauważymy, że do sformułowania i zwrócenia kursora używamy argumentów selection oraz selectionArgs. Aby zrozumieć, co jest przekazywane za pomocą tych argumentów, musimy spojrzeć na plik metadanych wyszukiwania, searchabłe.xml. W listingu 14.2 1 został przedstawiony kod tego pliku. Listing 14.21. Metadane wyszukiwania dla niestan da rd owego dostawcy propozycji //xm//searchable.xml csearchable xml n s : and roid=" http : //schema s . android . com/apk/res/android" android : label='@string/sea rch_label' and roid : hint='@st ring/search_hint" android : sea rchMode= ' showSea rchlabelAsBadge' a n d roid : se a rchSettingsDescription='proponuje a d resy u rl" and roid : includelnGlobalSea rch="true" androi d : sea rchSuggestAuthority= " com . a i . android . se a rc h . custom . suggesturlprovider" android : sea rchSuggestlntentAction=" android . inten t . action . VIEW' android: searchSuggestSelection=" ? " />
Zwróćmy uwagę na atrybut s e a r c h S u g g estSelection. Jest on bezpośrednio związany z ar gumentem selection metody q u e ry ( ) znajdującej się w naszym dostawcy treści. Jak pa miętamy z rozdziału 3., argument ten służy do przekazywania klauzuli where wraz z wy mienialnymi symbolami ?. Następnie tablica vqmienialnych wartości jest przekazywana do argumentu tablicy selectionArgs. Tak się rzeczywiście dzieje w naszym przypadku. W przy padku określenia atrybutu searchSuggestSelection Android zaklada, że nie c hcemy otrzymy wać tekstu wyszukiwania poprzez identyfikator URI, lecz za pomocą argumentu selection metody q u e r y ( ) . W takim wypadku proces wyszukiwania w Androidzie będzie wysyłał symbol ? (zwróćmy uwagę na spację poprzedzającą znak ?) jako wartość argumentu selection i przekazywał tekst kwerendy w postaci pierwszego elementu tablicy argumentów selection. Jeśli nie zdefiniujemy argumentu s e a rchSuggestSelection, tekst wyszukiwania zostanie przekazany w formie ostatniego segmentu identyfikatora URI. Możemy wybrać dowolny sposób. W naszym przykładzie wykorzystaliśmy argument selection, a nie identyfikator URI.
Badanie metadanych wyszukiwania pod kątem n iestandardowego dostawcy propozycji Skoro już poruszyliśmy temat metadanych wyszukiwania, to sprawdźmy, jakie są dostępne ::-ozostałe atrybuty. Omówimy atrybuty najczęściej stosowane wobec dostawcy propozycji
552
Android 2. Tworzenie aplikacji
oraz najbliżej z nim związane. Pełną listę atrybutów interfejsu API SearchManager możemy przejrzeć pod adresem:
h ttp:!!developer.android.com/reference/android/app!SearchManager.html Atrybut sea rchSugg e s t i ntentAction używany jest do przekazywania lub definiowania działania danej intencji podczas wywołania aktywności SearchActivity za pomocą tej in tencji. Dzięki temu klasa Sea rchActivity nie jest ograniczona wyłącznie do domyślnego wyszukiwania. Widzimy to w listingu 14.23, gdzie klasa SearchActivity oczekuje działania VIEW lub SEARCH poprzez sprawdzanie wartości działania danej intencji. Kolejny atrybut, którego nie wykorzystaliśmy, lecz który jest dostępny dla dostawców pro pozycji, nosi nazwę searchSuggestPath. Po określeniu tej wartości typu string zostanie ona przyłączona do identyfikatora URI (wywołującego dostawcę propozycji) tuż po segmencie SUGGEST_URI_PATH_QUERY. Umożliwia to pojedynczemu, niestandardowemu dostawcy propo zycji przetworzenie dwóch różnych aktywności vvyszukiwania. Każda aktywność Search '+Activity będzie posiadała inny sufiks identyfikatora URI. Podobnie jak w przypadku działania Intent, również i teraz możemy określić dane intencji za pomocą atrybutu searchSuggestintentData. Jest to identyfikator URI danych, który podczas wywołania może być przekazany - jako część intencji, wraz z działaniem - do aktywności wyszukiwania. Kolejny atrybut, nazwany sea rchSuggestTh res hold, definiuje ilość znaków, jaka ma zostać wpisana w polu QSB, aby dostawca propozycji został wywołany. Domyślną wartością pro gową jest O. Atrybut que ryAft e rZeroRe s u l t s (przyjmujący wartości t rue lub false) wskazuje, czy dla następnego zestawu znaków ma zostać wywołany dostawca w przypadku, gdy dla bieżącego zestawu znaków został zwrócony zerowy zestaw wyników. Po zapoznaniu się z identyfikatorami URI, argumentami selection i metadanymi wyszu kiwania zajmijmy się najistotniejszym aspektem dostawcy propozycji: kursorem propozycji.
Kolumny kursora propozycji Kursor propozycji jest przede wszystkim kursorem. Nie różni się niczym od kursorów ba zodanowych, obszernie omówionych w rozdziale 3. Kursor propozycji pełni rolę kontraktu pomiędzy procesem wyszukiwania w Androidzie a dostawcą propozycji. Oznacza to, że na zwy i typy kolumn zwracanych przez kursor są niezmienne i znane obydwu stronom. W celu uzyskania elastyczności procesu v,ryszukiwania Android oferuje olbrzymią ilość kolumn, w większości opcjonalnych. Dostawca propozycji nie musi zwracać wszystkich kolumn; może zignorować kolumny, które nie są z nim ściśle powiązane. W tej sekcji przyjrzymy się przeznaczeniu większości kolumn (opis pozostałych kolumn znajdziemy we wspomnianym już kilkakrotnie omówieniu interfejsu API SearchManager). Najpierw zajmiemy się kolumnami, które dostawca propozycji może zwracać, omówimy ich przeznaczenie oraz wpływ na v,ryszukiwanie. Tak jak wszystkie kursory, również kursor propozycji musi zawierać kolumnę _id. Jest to ko lumna obowiązkowa. Nazwy pozostałych kolumn rozpoczynają się od przedrostka SUGGEST_ '+COLUMN_. Stałe te są zdefiniowane jako część odniesienia do interfejsu API SearchManager.
Rozdział 1 4 Wyszukiwanie w Androidzie •
553
Poniżej zostaną omówione najczęściej używane kolumny. Pełną ich listę można znaleźć w umieszczonych na końcu rozdziału zasobach dotyczących tego interfejsu API. • text_ l. Jest to pierwsza linijka tekstu propozycji (rysunek 14.3). • text_2. Jest to druga linijka tekstu propozycji (rysunek 14.3). • icon_l. Jest to ikona umieszczona po lewej stronie propozycji; przechowuje ona zazwyczaj identyfikator zasobu. • icon_ 2. Jest to ikona umieszczona po prawej stronie propozycji; przechowuje ona zazv,ryczaj identyfikator zasobu. • intent_a c t ion. Jest to argument przekaz}'\vany aktywności SearchActivity podczas jej wywoływania w postaci działania intencji. W przypadku obecności tej kolumny w metadanych wyszukiwania będzie ona przesłaniała odpowiednie działanie intencji (listing 14.21). • intent_data. Są to informacje przekazywane aktywności SearchActivity podczas jej wywoływania w postaci danych intencji. W przypadku obecności tej kolumny w metadanych wyszukiwania będzie ona przesłaniała odpowiednie działanie intencji (listing 14.21). Jest to identyfikator URI danych. • i nte n t_d ata_ id. Zostaje ona dodana do identyfikatora URI danych. Jest szczególnie przydatna, gdy chcemy jednorazowo wspomnieć o głównej partii danych w metadanych, a następnie zmieniać tę partię dla każdej propozycji. Dzięki tej kolumnie można nieco skuteczniej przeprowadzić taką czynność. • que ry. Ciąg znaków kwerendy, wysyłany do aktywności wyszukiwania. • sho rtcut_id. Jak zostało wcześniej wspomniane, wyszukiwanie w Androidzie przechowuje w pamięci podręcznej propozycje dostarczane przez dostawcę. Takie przechoW}'\vane propozycje noszą nazwę skrótów. Jeżeli ta kolumna jest nieobecna, Android będzie przechowywała propozycję i nigdy nie zażąda jej aktualizacji. Jeżeli zostanie umieszczona wartość SUGGEST_NEVER_MAKE_SHORTCUT, Android przestanie przechowywać propozycje w pamięci podręcznej. Jeżeli wstawimy tu dowolną inną wartość, identyfikator ten zostanie przekazany jako ostatni segment identyfikatora URI skrótu (więcej informacji znajduje się w paragrafie „Identyfikatory URI dostawcy propozycji"). • spinne r_ while _ re f reshing. Ta wartość logiczna pozwala określić, czy podczas procesu aktualizowania skrótów ma być używana kontrolka Spinn e r. Istnieje również zmienny zestaw dodatkowych kolumn, odpowiedzialnych za reagowanie na klawisze działania. Zajmiemy się tą kwestią podczas omawiania klawiszy działania. Zo baczmy, w jaki sposób nasz niestandardowy dostawca propozycji zwraca te kolumny.
Zapełnianie i zwracanie listy kolumn Nie jest wymagane od niestandardowego dostawcy propozycji, aby zwracał wszystkie wy mienione w poprzednim ustępie kolumny. Nasz dostawca będzie zwracał jedynie podzbiór kolumn w oparciu o zadania omówione w podrozdziale „Planowanie niestandardowego do stawcy propozycji". Przyjrzawszy się listingowi 14.20, możemy stwierdzić, że wyjściowa lista kolumn jest nastę pująca (została ona wstawiona do listingu 14.22):
554
Android 2. Tworzenie aplikacji
Listing 14.22. Definiowanie kolumn kursora propozycji private static fina! String [ ) COLUMNS = { "_id " , li musi zawierać tę kolumnę Sea rchManage r . SUGGEST_COLUMN_TEXT_ l , SearchManager . SUGGEST_COLUMN_TEXT_ 2 , Sea rchManage r . SUGGEST_COLUMN_ INTENT OATA, SearchManage r . SUGGEST COLUMN_INTENT_ACTION , Sea rchManager . SUGGEST_COLUMN_SHORTCUT_ID
}; Kolumny te zostały dobrane w taki sposób, aby spełniały następujące wymagania: Użytkownik wpisuje w polu QSB słowo z podpowiedzią, taką jak g rea t . m, nasz dostawca propozycji nie odpowie, dopóki w tekście wyszukiwania znajduje się kropka. Po rozpozna niu wyrazu dostawca propozycji wyizoluje je z całego wyrażenia (w naszym przypadku jest to wyraz great), a następnie zwróci dwie propozycje. Pierwsza propozycja służy do wywołania witryny thefreewebdictionary.com wraz z danym sło wem, a druga propozycja przeszukuje bazę danych Google za pomocą v.ryrażenia define : g reat. W tym celu dostawca wczytuje kolumnę intent_ a c t ion jako obiekt i nt e nt . a c t i o n . view oraz dane intencji zawierające pełny identyfikator URI. Android powinien uruchomić prze glądarkę po rozpoznaniu identyfikatora URI danych, rozpoczynającego się od segmentu http : //. Wypełnimy kolumnę tex t l wartościami sea rch some-websi te wi th.: , a kolumnę text2 właściwym słowem (przypominamy, że w naszym przypadku jest to wyraz great). Aby uprościć zadanie, identyfikatorowi skrótu przypiszemy wartość SUGGEST_ N EVER_MAKE_ SHORTCUT. W ten sposób wyłączymy przechowywanie propozycji w pamięci podręcznej i uniemożliwimy usunięcie identyfikatora URI propozycji. Na tym zakończymy analizę kodu źródłowego klasy niestandardowego dostawcy propozycji. Czytelnicy uzyskali wiedzę na temat identyfikatorów URI, kursorów propozycji oraz meta danych wyszukiwania powiązanych z określonym dostawcą. Wiedzą już także, w jaki spo sób można zapełniać kolumny propozycji. Zastanówmy się teraz nad sposobem zaimplementowania aktywności wyszukiwania do na szego niestandardowego dostawcy propozycji.
Implementacja aktywności wyszukiwania dla niestandardowego dostawcy propozycji Jak już wielokrotnie napomknęliśmy, implementacja niestandardowego dostawcy propozy cji składa się z dwóch zasadniczych elementów: właściwego dostawcy propozycji i aktywno ści wyszukiwania, reagującej na propozycje. W poprzedniej sekcji omówiliśmy implementa cję niestandardowego dostawcy propozycji. Teraz zajmiemy się analizą związanej z nim aktywności wyszukiwania. Tak samo jak w poprzednim ustępie zaczniemy od omówienia ogólnych zadań stojących przed aktywnością wyszukiwania. Następnie zaprezentujemy jej kod źródłowy, ukazujący spo sób wypełniania przez nią oczekiwanych zadań.
Rozdział 1 4 Wyszukiwanie w Androidzie •
555
Zadania aktywności wyszukiwania Podczas omawiania tematu implementacji prostego dostawcy propozycji zajęliśmy się po bieżnie tematem zadań al"tyv\'ności wyszukiwania. Zwróćmy teraz uwagę na pominięte aspekty. Proces przeszukiwania w Androidzie wywołuje aktywność wyszukiwania w celu przetwo rzenia działań wyszukiwania, uruchomionych na jeden z dwóch sposobów. Jednym ze spo sobów uruchomienia działania wyszukiwania jest kliknięcie ikony wyszukiwania, będącej częścią pola QSB; drugi natomiast polega na bezpośrednim kliknięciu propozycji. Aktywność wyszukiwania musi ustalić powód jej wywołania. Informacja ta jest umieszczo na w intencji działania. Dlatego intencja ta musi zostać przeanalizowana przez aktywność wyszukiwania. Wielokrotnie działaniem takim jest ACTION_ SEARCH. Jednak dostawca propozycji posiada możliwość przesłonięcia tego działania poprzez jawne określenie innego działania, korzystając z metadanych wyszukiwania lub kolumny kursora propozycji. W naszym przy kładzie stosujemy działanie VIEW. W trakcie omawiania prostego dostawcy propozycji wspomnieliśmy, że istnieje również możliwość skonfigurowania trybu uruchamiania singleTop wobec aktywności wyszukiwania. W takim przypadku aktywność zostaje obarczona dodatkowym obowiązkiem odpowiedzi na metodę onNewintent ( ) , a także odpowiedzi na metodę onCreate( ) . Omówimy przypadki odpowiadania na obydwie metody i pokażemy podobieństwa występujące pomiędzy nimi. Zastosujemy zarówno metodę onNewintent ( ) , jak i one reate ( ) w celu przeanalizowania działań ACTION_S EARCH i ACTION_VIEW. W trakcie korzystania z działania wyszukiwania spowo dujemy po prostu zwrócenie tekstu kwerendy użytkownikowi. W przypadku działania wi doku przeniesiemy kontrolkę do wyszukiwarki i zakończymy bieżącą aktywność, dzięki czemu użytkownik będzie miał wrażenie wywołania przeglądarki poprzez bezpośrednie kliknięcie propozycji. Skoro już wiemy, czego oczekiwać, spójrzmy na kod źródłowy pliku SearchActivity.java.
Kod źródłowy klasy SearchActivity używanej w niestandardowym dostawcy propozycji Po zapoznaniu się z zadaniami aktywności wyszukiwania, szczególnie zaś tymi eksponowa nymi w naszym przykładzie, można zapoznać się z jej kodem źródłowym (listing 14.23). Listing 14.23. Klasa SearchActivity !!plik: SearchActivity.java public class SearchActivity extends Activity {
p rivate fina! static String tag = " Sea rchActivity " ; @Over ride p rotected void onCreate(Bundle savedinstanceState) { supe r . onCreate ( s avedinstanceStat e ) ; Log . d ( tag , "Jestem tworzona" ) ; setContentView ( R . layout . layout_tes t_search_ activi ty ) ; li uzyskuje i przetwarza tu kwerendę wyszukiwania f i n a ! Intent queryintent
=
getintent ( ) ;
556
Android 2. Tworzenie aplikacji
//działanie kwerendy final String queryAction = querylntent. getAction ( ) ; Log . d ( ta g , " Utwórz działanie intencj i : "+que ryActio n ) ; final String queryString queryintent . getStringExt ra ( SearchManag e r . QUERY ) ; Log . d ( tag , "Utwórz kwerendę intenc j i : "+queryStrin g ) ; =
if ( l ntent . ACTION_SEARCH . equals ( q ueryActio n ) ) { this . doSearchQuery ( qu e ryintent ) ;
}
else if (Intent.ACTION_VIEW.equals( queryAction ) )
{
this . doView ( qu e ryintent ) ;
}
else { Log . d ( tag , " Utwórz int e n c j ę N I E z poziomu wyszukiwania " ) ;
}
ret u r n ;
}
@Over ride public void onNewlntent(final Intent newlntent) {
supe r . onNewlntent ( n ewintent ) ; Log . d ( ta g , "wywołuje mnie nowa intencj a " ) ; li uzyskuje
tu i przetwarza kwerendę wyszukiwania final Intent queryintent = newintent;
!!działanie kwerendy fina! String que ryAction que ryintent . getAction ( ) ; Log . d ( tag , " Nowe działanie intencj i : "+que ryActio n ) ; =
fina! String queryString queryintent. getStringExt ra (Sea rchManage r . QUERY J ; Log . d ( tag , " Nowa kwerenda intencj i : "+queryString ) ; =
if ( I ntent . ACTION_SEARCH . equals ( queryAction ) )
{
}
this . doSearchQuery ( q u e ryintent ) ;
else i f ( Intent . ACTION_VIEW . equals ( q u e ryAction ) ) {
thi s . doView ( queryintent ) ; }
else { Log . d (t a g , "Nowa intencja utworzona NIE z poziomu wyszukiwania " ) ; }
ret u r n ; } p rivate void doSearchQuery (final Intent querylntent) {
Rozdział 1 4 • Wyszukiwanie w Androidzie
557
final St ring querySt ring = queryintent . getStringExt ra (Sea rchManage r . QUERY) ; appendText ( " Szukasz obiektu : " + queryString ) ;
} private void appendText (St ring msg)
{
TextView tv = (TextView ) this . findViewByi d ( R . id . text l ) ; t v . setTex t ( t v . getText ( ) + " \ n " + msg ) ;
} private void doView( final Intent queryintent)
{
U ri uri = queryintent . getData ( ) ; String action = queryinten t . getActio n ( ) ; Intent i = new Inten t ( action ) ; i . setData ( u ri ) ; sta rtActivity ( i ) ; this . finis h ( ) ;
} }
Rozpoczniemy analizę kodu źródłowego od określenia sposobu wywołania naszej aktywno ści wyszukiwania.
Szczegóły wywołania klasy SearchActivity Tak jak w przypadku wszystkich aktyvmości, również aktywność wyszukiwania musi zostać wywołana za pomocą intencji. Jednak założenie, że za wywołanie aktywności jest zawsze odpowiedzialna intencja action, jest niewłaściwe. Okazuje się, że aktywność wyszukiwania jest jawnie wywoływana poprzez specyfikację jej składowej nazwy. Zastanówmy się, dlaczego jest to takie istotne. Jak wiemy, w naszym dostawcy propozycji jawnie definiujemy intencję a c t i o n w krotce propozycji. Jeżeli działaniem intencji jest VIEW, a jej danymi - adres URL HTTP, to nieświadomy programista mógłby uznać, że w odpo wiedzi zostanie uruchomiona przeglądarka, a nie aktywność wyszukiwania. Takie zjawisko byłoby z pewnością bardzo pożądane. Jednak ponieważ intencja posiada nazwę aktywności wyszukiwania oraz działanie i dane intencji, nazwa aktywności uzyskuje ostatecznie pierw szeństwo. Nie jesteśmy pewni, dlaczego zostało wprowadzone takie ograniczenie oraz w jaki sposób można je ominąć. Faktem jest jednak, że niezależnie od działania intencji definiowanej przez dostawcę propozycji to właśnie aktywność wyszukiwania zostanie wywołana. W na szym przykładzie uruchomimy po prostu przeglądarkę z poziomu aktywności wyszukiwa nia i zamkniemy tę aktywność. Zademonstrujemy to rozwiązanie na podstawie intencji uruchamianej przez system po kliknięciu propozycji w celu wywołania naszej aktywności wyszukiwania: launching Intent { act=android . inten t . action . VIEW dat=http : //www . google . com flg=OxlOOOOOOO cmp=com . ai . android . se a rch . c ustom/ . SearchActivity ( h a s ext r a s ) }
558
Android 2. Tworzenie aplikacji
Zwróćmy uwagę na specyfikację składnika intencji. Wskazuje ona bezpośrednio aktywność wyszukiwania. Zatem bez względu na określone przez nas działanie intencji Android będzie zawsze v.rywoływał aktywność wyszukiwania. W wyniku tego na tę aktywność spada obo wiązek wywołania przeglądarki. Przyjrzyjmy się teraz, co się dzieje z tymi intencjami w aktywności wyszukiwania.
Odpowiedź na działania ACTION_SEARCH i ACTION_VIEW Wiemy, że proces wyszukiwania w Androidzie do wywołania aktywności wyszukiwania jawnie używa jej nazwy. Jednak intencja wywołująca przechowuje również określone dzia łanie. Kiedy pole QSB wywołuje tę aktywność po kliknięciu przez użytkownika ikony wy szukiwania, mamy do czynienia z działaniem ACTION_SEARCH. Działanie może być inne w przypadku jego wywołania przez propozycję wyszukiwania. Za leży to od skonfigurowania propozycji przez dostawcę. W naszym przypadku dostawca propozycji konfiguruje działanie ACTION_ VIEW. Z powodu obecności różnych działań aktywność wyszukiwania musi je rozpoznać. Przed stawiamy poniżej kod umożliwiający określenie, czy ma zostać wywołana metoda wyszuki wania kwerendy, czy metoda widoku (segment kodu został zaczerpnięty z listingu 14.23): if ( Intent . ACTION_SEARCH . equals ( queryAction ) ) { t h i s . doSearchQuery(querylntent ) ; }
else if ( Intent . ACTION_VIEW . equa ls (q ue ryAct ion ) )
{
this . doView(querylntent ) ;
} Widzimy, że dla działania widoku wywołujemy metodę doView( ) , a w przypadku działania wyszukiwania - doSearchQuery ( ) . Za pomocą funkcji doView( ) odczytujemy działanie oraz identyfikator URI danych i zapeł niamy nimi nową intencję, a następnie wywołujemy aktywność. W ten sposób zostanie przywo łana przeglądarka. Zakończymy aktywność w taki sposób, aby przycisk cofania przeniósł nas do wywołującego ją procesu wyszukiwania. W metodzie doSea r chQ u e ry ( ) wyświetlamy jedynie tekst kwerendy wyszukiwania w widoku. Przyjrzyjmy się układowi grafi cznemu używanemu do obsługi funkcji doSea rchQue ry ( ) .
Układ graficzny aktywności wyszukiwania Listing 14.24 przedstawia prosty układ graficzny, używany przez aktywność wyszukiwania w przypadku uruchomienia metody doSea rchQue ry ( ) . Jedyny istotny fragment został za znaczony tłustym drukiem.
Listing 14.24. Układ graficzny klasy SearchActivity /!plik: layout_search_activity.xml
Rozdział 1 4 • Wyszukiwanie w Androidzie
559
android : layout_height="fill_parent" >
Nadszedł odpowiedni moment na zaprezentowanie zawartości pliku strings.xml, odpowie dzialnego za wyświetlanie niektórych ciągów znaków w naszej aplikacji.
Plik strings.xml Przedstawiony w listingu 14.25 plik strings.xml definiuje ciągi znaków tekstowych układu graficznego oraz takie elementy, jak nazwa aplikacji, niektóre ciągi znaków konfiguracji wy szukiwania itp.
Listing 14.25. Plik string s.xml Jest to aktywność wyszu kiwania . \n\n Ten widok zostanie wywo.łany, j eżeli zostanie użyte cizia.łanie action_searc h , a nie action_view. \n\n Dzia.łanie action_search zostaje u ruchomione po kliknięciu ikony wyszukiwania . \n\n Dzia.łanie action_view zosta j e u ruchomione po kliknięciu p ropozycj i . Niestanda rdowa aplikacj a propozycji Demonstracja niestandardowej propozycji Demonst racj a podpowiedzi niestandardowej p ropozycji
Odpowiedź na metody onCreate() i onNewlntent() Jeżeli ponownie przyjrzymy się kodowi z listingu 14.23, zauważymy, że fragmenty w meto dach one reate ( ) i onNewintenet ( ) są niemal identyczne. Jest to dosyć powszechny wzorzec.
W zależności od trybu uruchamiania aktywności wyszukiwania po jej wywołaniu przywo ływana jest metoda one reate ( ) lub onNewin t e n t ( ) . Jeżeli nie odpowiemy na którąś z nich, możemy ominąć wywołanie wyszukiwania. W umieszczonej pod koniec rozdziału sekcji „Zasoby" z najdzie my odnośnik do
P'Trr= użytecznych materiałów na temat trybów uruchamiania aktywności wyszukiwania.
560
Android 2. Tworzenie aplikacji
Uwagi na temat zakończenia aktywności wyszukiwania Wspomnieliśmy wcześniej o sposobie odpowiedzi na metodę do View ( ) . Listing 14.26 za wiera kod tej funkcji (jest to wyciąg z listingu 14.23).
listing 1 4.26. Zakończenie aktywności wyszukiwania private void doView(final Intent queryintent) { U ri u r i = queryintent . getDat a ( ) ; St ring action = queryintent . g etAction ( ) ; Intent i = new Intent ( action ) ; i . setData ( u ri ) ; sta rtActivity ( i ) ; t h i s . finish ( ) ; } Celem tej funkcji jest wywołanie przeglądarki. Gdybyśmy na końcu nie wykonali funkcji
f i n i s h ( ) , użytkownik po kliknięciu przycisku cofania zostałby przeniesiony z przeglądarki do aktywności wyszukiwania, a nie z powrotem na ekran wyszukiwania, jak należałoby się spodziewać. W idealnym przypadku, aby zapewnić użytkownikowi jak najlepsze wrażenia, kontrolka nie powinna nigdy przechodzić przez aktywność wyszukiwania. Zakończenie tej aktywności roz wiązuje problem. Powyższy segment kodu daje nam również okazję zbadania, w jaki sposób przenosimy działanie i dane intencji z oryginalnej intencji (ustanowionej przez dostawcę propozycji), a następnie dokonujemy jej transferu do nowej intencji przeglądarki. Zakończymy na tym kilka tematów. Przedstawiliśmy szczegółowo implementacje dostawcy propozycji oraz aktywności wyszukiwania. W międzyczasie pokazaliśmy również plik me tadanych v.ryszukiwania
i
plik
strings.xml.
Analizę plików v.rymaganych do implementacji
naszego projektu zamkniemy badaniem pliku manifestu aplikacji.
Plik manifest niestandardowego dostawcy propozycji W pliku manifeście zostają zebrane wszystkie składniki naszej aplikacji. Tak samo jak w przy padku innych przykładowych aplikacji, deklarujemy tutaj składniki naszego dostawcy pro pozycji, takie jak aktywność wyszukiwania oraz właściwy kod dostawcy propozycji. Plik ten służy również do zadeklarowania możliwości wyszukiwania lokalnego przez aplikację, po przez ustanowienie „aktywności wyszukiwania" domyślnym procesem przeszukiwania. Poszczególne v.rymienione powyżej informacje zostały v.rytłuszczone "" kodzie pliku manife stu (listing 14.27).
listing 1 4.27. Plik manifest niestandardowego dosta wcy propozycji l/p/ik:manifest.xml c?xml version= " l . 0 " encoding=" u t f - 8 " ?>
Rozdział 1 4 • Wyszukiwanie w Androidzie
561
****************************************************************
-->
Jak widać, zaznaczyliśmy trzy elementy: • definicję aktywności wyszukiwania oraz związany z nią plik XML metadanych wyszukiwania, • definicję aktywności wyszukiwania jako domyślnego procesu wyszukiwania w aplikacji, • definicję dostawcy propozycji oraz jego uprawnienia. Po utworzeniu niezbędnego kodu czas uruchomić aplikację i sprawdzić, jak się ona p rezentuje na emulatorze.
Wrażenia użytkownika podczas korzystania z niestandardowych propozycji Po skompilowaniu i wdrożeniu aplikacj i za pomocą narzędzi ADT nie ujrzymy żadnych pojawiających się aktywności, ponieważ żadna nie została uruchomiona. Zamiast tego zoba czymy w konsoli Eclipse, że aplikacja została poprawnie zainstalowana. Oznacza to, że dostawca propozycji jest gotowy na przetwarzanie wpisów w globalnym polu QSB. Zanim to jednak nastąpi, musimy dołączyć naszego dostawcę do udziału w procesie wyszukiwania globalnego.
562
Android 2. Tworzenie aplikacji
Pokazaliśmy na początku rozdziału, w jaki sposób możemy dotrzeć do aplikacji ustawień '.vyszukiwania. Przedstawimy teraz szybsze rozwiązanie, korzystające z tej samej funkcji vry szukiwania, którą omawiamy w tym rozdziale. Otwórzmy globalne pole QSB i wpiszmy w nim sett. Zostanie \"')'ŚWietlona aplikacja usta wień jako jedna z propozycji "")'Szukiwania (rysunek 14.30).
Rysunek 1 4.30. Wywoływanie aplikacji ustawień za pomocą procesu wyszukiwania Wykorzystujemy tę samą wiedzę, którą uzyskaliśmy na temat poła QSB, aby wywołać apli kację ustawień. Skorzystajmy z rozwiązania omówionego na początku rozdziału do dołą czenia naszej aplikacji do propozycji. Gdy to zrobimy, wpiszmy w polu QSB tekst widoczny na rysunku 14.31.
Rysunek 14.3 1 . Więcej wyników od niestandardowego dostawcy propozycji
Rozdział 1 4 • Wyszukiwanie w Androidzie
563
Zwróćmy uwagę na sposób prezentowania propozycji dostarczanych przez naszego dostaw cę. Jeśli teraz przejdziemy do jednej z propozycji prezentowanych przez niestandardowego dostawcę i klikniemy ikonę wyszukiwania, Android bezpośrednio przeniesie nas do aktyw ności wyszukiwania, pominąwszy przeglądarkę, co zostało pokazane na rysunku 14.32.
Rysunek 14.32. Kwerenda wyszukiwania wywołująca wyniki wyszukiwania Przykład ten ukazuje porównanie pomiędzy działaniem ACTION SEARCH a ACTION_ VIEW. Po kilkukrotnym użyciu tego dostawcy propozycji propozycje te zostaną wyświetlone na głów nym ekranie v.ryboru, a nie w elemencie menu Więcej wyników Na rysunku 14.33 został zilu strowany przykład wpisania wyrazu chia roscu ro . m w globalnym polu QSB. . . . .
Qftjjj © 9:18 chiaroscuro.
--}szukJJ • '�
t" ( �
ni
na w1t�yrne freed!CtiOn.ar)'.' <ł''•
Rysunek 14.33. Pierwszeństwo dostawcy propozycji
Android 2. Tworzenie aplikacji
564
Zauważmy, że propozycje są teraz wyświetlane bezpośrednio, bez elementu Wię cej wyników.„. Jeśli klikniemy teraz pierwszą propozycję (darmov.ry słownik), zostanie wywołana przeglą darka, co zostało zaprezentowane na rysunku 14.34. fół!IJCJ 3:51 PM
:-Tt"f'�!&°'V.JSCVT .&.. \' t•' ' fdr('J� lt .>rC'.I) :O.. V'\-'. \"1:. \'."eń.5 {Cl��. r."..,._ '- � f:rd-JF-R.l�� t'U1� ?.l.r.•e<� .•
-�
Ge ;. :ree
[�t'Tl�·e, ':'::-c.a:„-1
i t-
Lf' Arr �oÓf P:r1:lff - :�n:;� lJ-C:-13 l\'<:ste" et.V�t' 0"1 �NTN . ·....
WeO":"er
ie....-.. ,.
.1iG f.)1�1r • �er.KtS C.i.1 C!Z' hł�'C}·T..ar fQ<
L1 : >��tTl�t'
.:. • . ' ;:-�;..;
'>ean
<::b'TJ�
cf �:ir=c�1e,;:e.-c1!.� >,',_"!,o-,· T�tAA.�� y.:.J\C rt.!d
chi·a·ro·scu·ro i:::,...;.J·'_.,$k�r·.- -:,ir;yXr'rt: fX
1. �l«h1L..fJt'O�;..;irs.l?'tt:n:J :;.-.�n��..Y<:I �::rese'l:.;.loJ't
2. r.� ;r'a",;:e�: .;,f 'i;t: .>"'n�rkc"'�:!.:1!' „ prt=.·o Y.) . ri
•.A.\�-.cl :::
��:·:�: .�hL�����;�:;!r·: �t:-. ������::���-���:.?�;� cbs.c"" tr.;S
se e
(S)i'..f'U· l"'I ;'\;;l;-Ę1,.ro ��r.m:i:s·· �
Rysunek 1 4.34. Darmowy słownik
Jeżeli klikniemy propozycję przejścia na witrynę Google, ujrzymy przeglądarkę widoczną na rysunku 14.35.
� http://www.google.co...
Coogle Web Related phrase s:.
H
defno:
;: :. „�1
. ::!r „· c·1:ł ! <·<:. _.lr;:
c "'.":-J•" ·,, •
(f-_; a�cxuM:p� ·t l.'.h'<' .o: ie: tcoo:
g ch a�o�::::;rc. p3tC!":\·:o"'" � cra:�rn
O1. i'"�!"k �
-ło.•.'lrov„r, ; ·-"� "'.Id<„ :�
Def1nit1ons of chiaroscuro on the Web: • a monochrome p1cture made by usmg sev d1rrerem: shJ:d�s or the sa�e color
•..-urd r�t o ':i e �p r1�:�to1'.00L1 p�: l1"'\%€'bw-n
• Chiaroscuro {ht. Halian for ·11ght-Cark·) is I
bel canto dass1cal s1ngmg te{hn•oue, 011g11 m
llalj, Hl which a hght · pretty- sound is c
wilt- the dark mrbre nroduced in the sma
Rysunek 14.35. Wyszukiwanie definicji na stronie google.com
Na rysunku 14.36 przedstawiamy widok, jaki pojawi się, jeśli nie dodamy przyrostka . m .
Rozdział 1 4 • Wyszukiwanie w Androidzie
565
FtilJl!llJ G 4:06 PM
r- -- � () I ch i aroscurq _J '-"' chiaroscuro - def1mt1on of ch1aros...
Chiaroscuro · Wikipedia, the free ...
(
'
chiaroscuro dehnmon
(
chiaroscuro san
(
chiaroscuro
francisco
pronunciation
chiaroscuro ch1cago
Rysunek 14.36. Niestandardowy dostawca pozbawiony podpowiedzi
Odnotujmy fakt, że nasz dostawca propozycji nie zwrócił żadnego wyniku. W ten sposób kończymy dyskusję dotyczącą tematu budowania od podstaw funkcjonalne
go, niestandardowego dostawcy propozycji. Chociaż omówiliśmy każdy aspekt procesu wy szukiwania, istnieje jeszcze kilka tematów, których do tej pory nie poruszyliśmy. Należą do nich pojęcia klawiszy działania oraz danych wyszukiwania specyficznych dla aplikacji. Te maty te są naszym kolejnym celem.
Zastosowanie klawiszy działania i danych wyszukiwania specyficznych dla aplikacji Klawisze działania oraz specyficzne dla aplikacji dane wyszukiwania zwiększają elastyczność procesu wyszukiwania w Androidzie. Klawisze działania umożliwiają przystosowanie klawiszy funkcyjnych urządzenia do obsługi funkcji wyszukiwania. Specyficzne dla aplikacji dane wyszukiwania stanowią dodatkowe in formacje, które są przekazywane aktywności wyszukiwania. Zacznijmy od klawiszy działania.
Wykorzystanie klawiszy działania w procesie wyszukiwania Do tej pory zademonstrowaliśmy wiele sposobów wywołania procesu wyszukiwania: • poprzez ikonę wyszukiwania dostępną w polu QSB, • poprzez klawisz wyszukiwania, stanowiący jeden z klawiszy działania (ukazanych na rysunku 14.1), • jawnie poprzez ikonę lub przycisk wyświetlane przez aktywność, • poprzez naciśnięcie dowolnego klawisza w oparciu o deklarację trybu type-to-search.
566
Android 2. Tworzenie aplikacji
W obecnym podrozdziale przyjrzymy się kolejnej technice wywoływania procesu wyszu kiwania - za pomocą klavriszy działania. Klawiszami działania nazywamy zestaw klawiszy umieszczonych na urządzeniu, którym są przypisane określone działania. Przykłady kilku takich klawiszy działania zostały przedstawione w listingu 14.28.
Listing 1 4.28. Lista kodów k lawiszy działania keycode_ dpad_ u p keycode_ dpad_down keycode_ dpad_ left keycode_dpad_right keycode_dpad_center keycode back keycode_call keycode camera keycode_clea r kecode_ endcall keycode_home keycode_menu keycode mute keycode_power keycode_search keycode_volume_up keycode volume_down
Klawisze te są zdefiniowane w interfejsie API klasy KeyEvent, której opis można znaleźć na następującej stronie: http:!!developer.android.com/reference/android/vieiv!KeyEvent.html Nie wszystkie wymienione klawisze działania można dołączyć do procesu wyszukiwania,
=·ers= niektóre jednak nie stanowią pod tym względem problemu, na przykład keyc od e_call.
Należy samodzielnie sprawdzić każdy klawisz działania pod kątem działania i przydatności.
Po wybraniu interesującego nas klawisza działania możemy powiadomić o tym system po przez umieszczenie informacji na ten temat w metadanych w sposób przedstawiony na li stingu 14.29. Listing 1 4.29. Przykładowa defi ni cj a klawisza działania csearchable xmlns : and roid= " h t t p : //schema s . android . com/apk/res/android " android : label="@st ring/se a rch_label " a n d roid : hint="@string/search_ h i n t " a n d r oid : sea rchMode=" showSea rchLabelAsBadge" and roid : in c ludelnGlobalSea rch="true" android: searchSuggestAuthority="com . a i . android . search . simplesp . SimpleSuggestionProvider" android: searchSuggestSelection=" ? "
>
Rozdział 1 4 Wyszukiwanie w Androidzie •
567
android : queryActionMsg="call" android : suggestActionMsg="call" androi d : s uggestActionMsgColumn= " call_column" />
Możemy również przyporządkować kilka klawiszy działania dla tego samego kontekstu wy szukiwania. Poniżej wyjaśniamy, do czego służy każdy element a c t i onKey oraz w jaki spo sób jest stosowany do przetwarzania naciśnięcia przycisku.
• keycode. Jest to kod klawisza zdefiniowany w interfejsie API klasy KeyEvent, który powinien zostać użyty do wywołania aktywności wyszukiwania. Możliwość wciśnięcia klawisza identyfikowanego przez taki kod pojawia się dwukrotnie. Pierwszy raz v.rystępuje ona podczas wpisywania przez użytkownika tekstu wyszukiwania w polu wyszukiwania, po którym nie zostają wyświetlone propozycje. Jeżeli funkcja klawisza działania nie została zaimplementowana, użytkownik przeważnie wciśnie ikonę wyszukiwania w polu QSB. W przypadku zaimplementowania klawisza działania w metadanych wyszukiwania Android pozwala użytkownikowi na wciśnięcie tego klawisza zamiast ikony '''Yszukiwania. Drugi raz pojawia się, gdy użytkownik przejdzie do określonej propozycji i wciśnie klawisz działania. W obydwu przypadkach zostaje wywołane dla aktywności wyszukiwania działanie ACTION_ SEARCH. Informację na ten temat przenosi dodatkowy ciąg znaków SearchManager . ACTION_KEY. Jeżeli znajduje się w nim jakaś wartość, to wiadomo, że nastąpi wywołanie w odpowiedzi na wciśnięcie klawisza działania. • queryActionMsg. Dowolny tekst wpisany w tym elemencie zostaje przekazany jako wartość dodatkowego ciągu znaków Sea rchManage r . ACTION_ MSG do aktywności wyszukiwania wywołującej intencję. Jeżeli odczytamy tę informację z intencji i jest ona identyczna z danymi zdefiniowanymi w metadanych, wiadomo będzie, że następuje bezpośrednie >rywołanie z poziomu pola QSB w wyniku wciśnięcia klawisza działania. Bez takiego testu nie będziemy mieli pewności, czy działanie ACTION_ S EARCH zostało bezpośrednio \\rywołane wobec propozycji w odpowiedzi na wciśnięcie klawisza działania. • suggestActionMsg. Dowolny tekst wpisany w tym elemencie zostaje przekazany jako wartość dodatkowego ciągu znaków Sea rchManage r . ACTION_MSG do aktywności wyszukiwania 'rywołującej intencję. Dodatkowe klawisze dla tego argumentu i pola que ryActionMsg są takie same. Jeżeli przypiszemy tym polom identyczną wartość, na przykład call, nie dowiemy się, w jaki sposób użytkownik wywołał klawisz działania. W wielu przypadkach jest to nieistotne, zatem można obydwu argumentom przypisać taką samą wartość. Jednak w razie konieczności odróżnienia obydwu sposobów wywołania musimy wstawić tu wartość inną niż obecna w argumencie que ryActionMsg.
• suggestActionMsgColumn. Wartości argumentów q u e ryActionMsg i suggestActionMsg są stosowane globalnie wobec danej aktywności wyszukiwania oraz dostawcy propozycji. Nie ma możliwości zmiany znaczenia działania opartego
568
Android 2. Tworzenie aplikacji
na propozycji. Można jednak powiadomić metadane o istnieniu dodatkowej kolumny w kursorze propozycji. W ten sposób Android pobierze informacje z tej kolumny i prześle j ą aktywności jako część intencji wywołującej ACTION_SEARCH. Co ciekawe, wartość tej dodatkowej kolumny jest przesyłana w intencji za pomocą identycznego, dodatkowego klucza, mianowicie Sea rchManage r . ACTION_ MSG. Spośród v.rymienionych atrybutów obowiązkowym jest kod klawisza. Ponadto musi być obecny przynajmniej jeden z pozostałych trzech atrybutów, aby klawisz działania został uru chomiony. Jeżeli chcemy korzystać z atrybutu suggestActionMsgColumn, musimy zapełnić tę kolumnę w klasie dostawcy propozycji. Gdybyśmy chcieli używać obydwu klawiszy, powinniśmy w listin gu 14.29 umieścić dwie dodatkowe kolumny typu string zdefiniowane w kursorze propozycji (listing 14.22), mianowicie kolumny call column i my_column. W takim przypadku nasza tablica kolumn kursora powinna przypominać tabelę przedstawioną w listingu 14.30.
Listing 1 4.30. Przykładowe kolumny klawiszy działania w kursorze propozycji private static final String [ ] COLUMNS " _ i d " , li musi zawierać tę kolumnę
=
{
SearchManage r . SUGGEST_COLUMN TEXT 1 , SearchManage r . SUGGEST_COLUMN_TEXT_ 2 , SearchManager . SUGGEST_COLUMN_ INTENT_DATA , SearchManage r . SUGGEST_COLUMN_INTENT_ACTION, SearchManager . SUGGEST_COLUMN_SHORTCUT_ I D , " call_ column " , "my_column" };
Praca ze s pecyficznym dla aplikacji kontekstem wyszukiwania Proces wyszukiwania w Androidzie umożliwia aktywności przekazanie dodatkowych da nych wyszukiwania do "'rywołanej aktywności wyszukiwania. Omówimy teraz szczegóły tego procesu. Pokazaliśmy wcześniej, że aktywność aplikacji może przesłonić metodę onSea rchRequested ( ) , dzięki czemu zostaje zwrócona wartość f a l s e i proces wyszukiwania zostaje ""Yłączony. Co ciekawe, w ten sam sposób możemy przekazać aktywności wyszukiwania dodatkowe dane, specyficzne dla aplikacji. W listingu 14.3 1 został zaprezentowany przykład.
Listing 1 4.31 . Przekazywanie dodatkowego kontekstu public boolean onSearchRequested ( )
{
Bundle applicationData = new Bundle ( ) ; applicationData . putString ( " s t ring_key " , " j akaś wartość typu string " ) ; applicationData . putlong ( " long_key" , 290904 ) ; applicationDat a . putFloat ( " float_key" , 2 . 0f ) ; s t a rtSea rch ( null , li Początkowy ciąg znaków kwerendy wyszukiwania fal se, /lnie "zaznaczaj początkowej kwerendy"
Rozdział 1 4 • Wyszukiwanie w Androidzie
569
applicationData , li dodatkowe dane false li nie wymusza wyszukiwania globalnego ); return t ru e ; }
1"•'f1111"';i11!<1i16"•
Różnorodne funkcje i nterfejsu API Bundle s ą omówione pod adresem: http://deve/oper.android.com/reference!android/os/Bundle.html.
Po uruchomieniu w ten sposób procesu wyszukiwania aktywność może wykorzystać obiekt Sea rchManage r . APP_ DATA do odczytania kompletu danych aplikacji. Listing 14.32 przed stawia sposób odczytania każdego z powyższych pól.
Listing 14.32. Odzyskiwanie dodatkowego kontekstu Bundle applicationData queryintent . getBundleExtra ( SearchManag e r . APP_DATA ) ; if ( applicationData 1 = null) =
{
String s = applicationDat a . getString ( " st ring key" ) ; long 1 applicationData . getlon g ( " long_key" ) ; float f = applicationData . getFloat ( " float_key" ) ; =
}
Przyjrzyjmy się metodzie s t a rtSea rch ( ) . Została ona szczegółowo omówiona jako część interfejsu API klasy A c t i v i ty, opis a nego na stronie:
http:l!developer.android.comlreference!androidlapp/Activity.html Metoda ta jest tworzona przez cztery argumenty wymienione poniżej : •
init i a l Q u e ry (argu ment typ u s t ring )
•
selec t i n i t ialQue ry (argu ment logiczny)
•
applicationDataBundle (argument typ u Bundle )
•
g lobalSea rchOnly (argument logiczny)
Jeżeli pierwszy argument zostanie zastosowany, wypełni tekst kwerendy w polu QSB. Wartość t ru e w drugim argumencie spowoduje zaznaczenie tekstu. Użytkownik będzie mógł podm ienić cały zaznaczony tekst kwerendy jego poprawioną wersj ą . W przypadku warto ści f a l s e ku rsor zostani e um ieszczony na końcu tekstu kwerendy. Trzeci argument stanowi o czywi ście przygotowywany przez nas zestaw danych .
Jeżeli w czwartym argumencie zostanie umieszczona wartość t rue, zawsze będzie wywoły wany proces wyszukiwania globalnego. Po wprowadzeniu wartości f a l s e najpierw zostanie wywołane wyszukiwanie lokalne (jeżeli j est dostępne); w przeciwnym razie będzie stosowa ne wyszukiwanie globalne.
570
Android 2. Tworzenie aplikacji
Zasoby Na zako11czenie tego rozdziału chcielibyśmy podzielić się listą zasobów, które uznaliśmy za przydatne podczas jego pisania. Poniższy adres zawiera główną dokumentację dotyczącą procesu wyszukiwania w Andro idzie, utworzoną przez firmę Google. To samo łącze stanowi również źródło materiałów na temat głównego obiektu odpowiedzialnego za proces wyszukiwania, mianowicie klasy Sea rchManage r:
http://developer.android.com/reference/android/app!SearchManager.html W miarę tworzenia własnych aktywności wyszukiwania warto czasem konfigurować je w trybie s ingleTop, dzięki czemu zostanie wygenerowana metoda onNewintent ( ) . Informacje na jej temat można znaleźć na stronie http://developer.android.com/reference/android!appl
A ctivity.h tml#onNewln tent(android. con tent.In tent) Przykładowa implementacja dostawcy propozycji znajduje się pod adresem wstawionym poniżej. Łącze to wskazuje przede wszystkim kod źródłov.ry implementacji.
h ttp:/ldeveloper.android.com/guide!samples/SearchableDictionarylindex.html Materiały dotyczące interfejsu API przeszukiwania ostatnich propozycji zostały umieszczone w witrynie http:l!developer.android.com/reference/android/provider/SearchRecentSuggestions.html. Dane zaprezentowane pod poniższym adresem URL pomagają zrozumieć aktywności, za dania oraz tryby uruchamiania, zwłaszcza tryb singleTop, często używany jako aktywność wyszukiwania:
http://developer.android.com/guide!topics/fundamentals.html Dzięki następującemu odnośnikowi poznamy różne funkcje dostępne w obiekcie bundle. Są to informacje przydatne zwłaszcza podczas implementacji danych wyszukiwania specyficz nych dla aplikacji:
http://developer.android.com/referencelandroid!os/Bundle.html Poniższa strona zawiera badania autorów na temat procesu wyszukiwania w Androidzie. Nawet po opublikowaniu tej książki będziemy aktualizować zawartość tej witryny. Zostały tam umieszczone również odnośniki do miejsc, z których można pobrać omówione w tym rozdziale projekty.
h ttp:l!www.satyakomatineni.com/akc!display?url=NotesIMPTitlesURL&ownerUserld= satya&folderName=Android%20Search
Podsumowanie W tym rozdziale skupiliśmy się przede wszystkim na szczegółowym omówieniu działania procesu wyszukiwania w Androidzie. Wyjaśniliśmy, w jaki sposób aktywności i dostawcy propozycji współpracują z procesem wyszukiwania. Zademonstrowaliśmy sposób wykorzy stania klasy Sea rchRecentSugges t i o n s P rovider.
Rozdział 1 4 • Wyszukiwanie w Androidzie
57 1
Zaprojektowaliśmy o d podstaw niestandardowego dostawcę propozycji, a w mi9dzyczasie dokładnie opisaliśmy kursor podpowiedzi i jego kolumny. Przyjrzeliśmy się identyfikatorom URI odpowiedzialnym za uzyskiwanie danych od dostawców propozycji. Zaprezentowali śmy wiele fragmentów kodu źródłowego, ułatwiających opracowanie i zaimplementowanie własnych strategii wyszukiwania. W oparciu o elastyczność samego kursora propozycji wyszukiwanie w Androidzie prze kształca się z prostego proces u w prawdziwego przewodnika informacji dostępnych w za sięgu ręki.
ROZDZIAŁ
15 Analiza interfejsów przetwarzania tekstu na m owę oraz tłumaczenia
Począwszy o d wersji 1.6 środowiska Android, dostępny stał się silnik odpowie dzialny za syntezę mowy, nazwany Pico. Za jego pomocą aplikacje w Andro idzie mogą wymawiać ciągi znaków tekstowych z akcentem typo"-rym dla wybra nego języka. Technologia przetwarzania tekstu na mowę umożliwia interakcję urządzenia z użytkownikiem bez konieczności spoglądania na ekran. W przy padku platformy mobilnej jest to niezmiernie istotna funkcja. Ilu ludziom zda rzyło się wyjść przypadkiem na środek jezdni w trakcie czytania wiadomości tekstowej? Czy nie wystarczyłoby po prostu odsłuchać te wiadomości? A gdyby można było posłuchać przewodnika turystycznego zamiast czytać go w trakcie zwiedzania? Istnieje olbrzymia ilość aplikacji, w przypadku których zamiesz czenie głosu zwiększyłoby ich użyteczność. W tym rozdziale przyjrzymy się klasie TextToSpeech i wyjaśnimy, co zrobić, aby wprowadzony przez nas tekst został wypowiedziany przez urządzenie. Nauczymy się także zarządzać dostęp nymi ustawieniami regionalnymi, językami i głosami. Zajmiemy się także omówieniem interfejsu internetowego tłumacza Google służącego do przekładu tekstu z jednego języka na inny. Rozwiązanie to jest już dostępne od pewnego czasu.
Podstawy technologii przetwarzania tekstu na mowę w Androidzie Zanim przekształcimy funkcję TTS (ang. Text to speech tekst na mowę) w aplika cję, sprawdźmy, jak ona brzmi na żywo. Przejdźmy w emulatorze lub w urządzeniu (wersja systemu co najmniej 1.6) do głównego ekranu ustawień Settings i wybierz my element Przeb1arzanie t e k s t u na mowę (lub, w zależności od posiada nej wersji, Speec h s y n t h e s i s ) . Znajdziemy tam opcję Posłuchaj przykładu. -
574 Android 2. Tworzenie aplikacji Po jej kliknięciu usłyszymy słowa „This is an example of speech synthesis in English" (Jest to przykład syntezy mowy w języku angielskim). Zwróćmy uwagę na pozostałe elementy listy (rysunek 1 5 . 1 ) .
Rysunek 1 5. 1 . Ekran ustawień przetwarzania tekstu n a mowę Możemy zmienić używany język oraz prędkość mowy. Opcja Język zmienia zaró\rno wy mawiane słowa, jak i akcent głosu, chociaż wymawiany będzie tekst „Jest to przykład synte zy mowy" w tłumaczeniu na inne języki. Zostaje przetłumaczony odczytywany tekst oraz zmieniony akcent zgodnie z ustawieniami v.rybranymi w opcji Język. Pamiętajmy, że funkcja TTS zawiera w sobie jedynie część odpowiedzialn�! za generowanie głosu. Za tłumaczenie odpowiedzialny jest tłumacz Google, omówiony w drugiej połowie rozdziału. W trakcie przykładowej implementacji funkcji TTS w naszej aplikacji będziemy chcieli, aby syntezo wany głos zgadzał się z wybranym językiem, zatem żeby francuski tekst mówiony był przez głos mówiący w języku francuskim. Prędkość mov.ry jest konfigurowana w zakresie od Bardzo wolno do Bardzo szybko. Powinniśmy zachować ostrożność w przypadku używania opcji Zaw sze używaj moich ustawień. Wybranie jej w ustawieniach systemowych przez dowolnego użytkownika może spowodować nieprzewidywalne zachowanie aplikacji, ponieważ te usta wienia mogą przesłonić ustawienia aplikacji. Musimy zrozumieć, co się dzieje podczas konfigurowania tych ustawień funkcji TTS. An droid uruchamia poza wzrokiem użytkownika silnik Pico, czyli wielojęzyczny silnik syntezy mowy. Aktywność ustawień, w której przebywamy, zainicjowała silnik przetwarzający bie żący język i prędkość mowy. Po kliknięciu opcji Posłuchaj przykładu aktywność preferencji przesyła tekst do silnika Pico, który z kolei wymawia ten tekst poprzez głośnik. Silnik Pico rozbija tekst na fragmenty, które potrafi wymawiać, i składa te porcje dźwięków w całkiem naturalny sposób. Algorytmy tworzące ten silnik są o wiele bardziej złożone - możemy jednak udawać, że mamy do czynienia z magią. Na szczęście dla nas magia ta zajmuje bar dzo niewiele pamięci oraz pojemności dyskowej, zatem silnik Pico jest idealnym dodatkiem do telefonu. W urządzeniu znajduje się tylko jeden silnik TTS. Jest on współdzielony pomiędzy wszyst kimi aktywnościami urządzenia, zatem musimy mieć świadomość, że nie tylko my możemy korzystać z tej funkcji. Oznacza to także, że nie możemy być pewni, kiedy (jeżeli w ogóle)
Rozdział 1 5 • Analiza interfejsów przetwarzania tekstu na mowę oraz tłumaczenia
575
nasz tekst zostanie wypowiedziany. Jednak dzięki interfejsowi funkcji TTS posiadamy do stęp do wywołań zwrotnych, zatem mamy pojęcie, co się dzieje z wysłanym przez nas tekstem. \V poniższym przykładzie utworzymy aplikację, która będzie odczytywata wpisywane przez nas informacje. Nie jest ona skomplikowana; została zaprojektowana w celu pokazania, jak łatwo można zaimplementować funkcj ę przetwarzania tekstu na mowę. Na początku stwórzmy nowy projekt w Androidzie, korzystając z artefaktów zamieszczonych w listingu 1 5 . 1 .
Listing
15.1.
Kody X M L i Java prostej wersji demonstracyjnej funkcji TIS
c?xml version=" l . O " encoding= " ut f - 8 " ?>
cRelativelayout xmln s : android= " h t t p : //schemas . android . com/apk/res/android" and roid : o rientation="vertical" and roid : layout_height=" fill_parent " and roid : layout _width= " fill_parent "> c/Relativelayout>
592
Android 2. Tworzenie aplikacji
Rysunek 15.4. Interfejs użytkownika aplikacji demonstracyjnej odpowiedzialnej za tłumaczenie
Nasz układ graficzny nie jest skomplikowany. Konfigurujemy pola tekstowe odpowiedzialne za przyjęcie tekstu tłumaczenia oraz wyświetlenie przekładu. Wstawiamy także dwa obiekty Spinner, służące jako rozwijane menu, w których wybierane będą język źródłowy i język docelo wy. Potrzebny jest również przycisk rozpoczynający proces tłumaczenia oraz napis powered by Google u spodu ekranu (niedługo wyjaśnimy, dlaczego ten wpis jest potrzebny). W li stingu 15.6 zostaty zamieszczone pliki strings.xml i arrays.xml, służące do zdefiniowania ciągów znaków naszego interfejsu użytkownika i menu. Listing 1 5.6. Pliki strings.xml i arrays.xml c?xml version= " l . O" encoding=" u t f - 8 " ?> - Chiński
- Polski
- Francuski
- Niemiecki
- J apoński
- Hiszpański
- zh
- pl
- f re/ item>
- de
- j a
- es
Rozdział 1 5 • Analiza interfejsów przetwarzania tekstu na mowę oraz tłumaczenia
593
Po utworzeniu podstawowego interfejsu użytkownika skierujmy naszą uwagę na utworzenie usługi, która będzie podlegała interakcji z interfejsem Google AJAX Language API. Listing 15.7 zawiera pliki definiujące interfejs usługi.
Listing 1 5.7. Pliki interfejsu usługi naszej aplikacji tłumaczącej li fest to plik ITranslate.aidl umieszczony w katalogu lsrc interface ITranslate { St ring t ranslate ( in String text, in St ring f ro m , in String t o ) ; } li jest to plik TranslateService.java impo rt android . a pp . Service; import android . conten t . Intent; import android . os . IBinder; import and roid . ut i l . Log ; public class Translateservice extends Service { public static final String TAG "TranslateService" ; =
private final ITranslat e . Stub mBinder = new ITranslate . Stub ( ) { public St ring t ranslat e ( St ring text, String f rom, String to ) { t ry { return Translato r . t ranslate ( text , f r o m , to ) ; } catch ( Exception e ) { Log . e ( TAG, "Nie udalo się p rzetlumaczyć : " + e . getMessage ( ) ) ; return n u l l ; } }; @Ove r ride public IBinder onBind ( Intent intent) { return mBinde r ;
}
Pamiętajmy, że środowisko Eclipse automatycznie wygeneruje kod Java z naszego pliku
.aidl po zachowaniu tego pliku. Nasza nowa usługa musi wyv.•ołać metodę statyczną Translat o r . <+t ranslate ( ) , która została odzwierciedlona w listingu 15.8. Listing 1 5 .8. Kod Java tworzący połączenie z interfejsem Google AJAX Language API li fest to plik Translator.Java import j ava . io . BufferedRead e r ; import j ava . io . InputSt ream; import j a va . io . InputSt reamReade r ; import j av a . net . HttpURLConnection; import j ava . net . URL; import java . net . URLEncoder; import org . apache. commons . lang . StringEscapeUtils; import org . j so n . JSONObject; import android . util . Log ;
594
Android 2. Tworzenie aplikacji
public class Translator { p rivate static fina! String ENCODING = "UTF - 8 " ; p rivate static fina! String URL BASE " http : //aj ax . googleapis . com/aj ax/se rvices/language/t ranslate?v=l . O&langpai r=" ; private static fina! String INPUT_ TEXT = "&q=" ; private static fina! String MY_SITE = " h t t p : //my . website . com" ; private static fina! String TAG = "Translato r" ; =
public static String translate(String text, String from, String to) th rows Exception {
t ry { StringBuilder u rl = new StringBuilde r ( ) ; u r l . append ( URL_ BASE ) . append { f rom) . append { "%7C" ) . append { to ) ; u rl . appen d ( INPUT_TEXT) . append ( URLEncode r . encode ( text , ENCODING ) ) ; HttpURLConnection conn = ( HttpURLConnection ) new URL { u r l . toString { ) ) . openConnectio n ( ) ; con n . setRequestProperty { " REFERER" , MY_SITE ) ; conn . setDoinput { t rue ) ; conn . setDoOutput ( t rue ) ; t ry { InputSt ream is= c o n n . getinputSt ream ( ) ; String rawResult makeResult ( i s ) ; =
JSONObject j so n = new JSONOb j e c t ( rawResult ) ; String result = ( ( JSONObject ) j so n . get ( " responseDat a " ) ) . getString ( " t ranslatedText " ) ; return ( S t ringEscapeUtils . unescapeXml ( result ) ) ; } finally { conn . getinputStream { ) . close { ) ; i f { conn . getErrorS t ream { ) ! = n u l l ) conn . ge t E r rorStream { ) . close { ) ; }
catch ( Exception ex ) { th row e x ;
} private static String makeResult ( InputSt ream inputStream) th rows Exception { StringBuilder outputString = new StringBuild e r ( ) ; t ry { St ring st ring ; if ( in putSt ream ! = null) { BufferedReader reader = new BufferedReader( new InputSt reamReade r { inputStream, ENCODING) ) ; while ( null ' = { s t ring = read e r . readline { ) ) ) { outputString . appen d ( st ring ) . append ( ' \ n ' ) ; } } catch ( Exception ex ) { Log . e ( TAG, "B.łąd podczas odczytu st rumienia t.łumaczenia . " , ex ) ; }
return outputS t r i n g . toString { ) ;
}
Rozdział 1 5 • Analiza interfejsów przetwarzania tekstu na mowę oraz tłumaczenia
595
Klasa T r a n s l a t o r stanowi sedno naszej przykładowej aplikacji. Generalnie tworzy ona wy \\"Ołanie HTTP do usługi Google AJAX Language API, a następnie odczytuje odpowiedź. Szczegółami zajmiemy się nieco później, najpierw jednak dokoI'lczmy aplikację tak, aby można było ją wypróbować. W listingu 1 5.9 widzimy kod aktywności MainActivity. Listing 1 5 .9. Kod Java aktywności Mai nActivity I Jest to plik MainActivity.java
import import import import import import import import import import impo rt import import import import import
android . ap p . Activity; android . content . ComponentName; android . content . Context; android . content . Inten t ; and roid . content . ServiceConnection ; android . os . Bu n d l e ; android . o s . Handler; android . os . IBinder; and roid . ut i l . Log ; a n d roid . view.View; and ro id . vie1� . View . OnClickListene r ; android .widget . ArrayAdapte r ; androi d . widget . Button ; android . widget . EditText; android . widget . Spinne r ; android .widget . TextView;
public class MainActivity extends Activity implements OnClickListener { static final String TAG = "Translat o r " ; private EditText inputText = null ; p rivate TextView outputText = n u l l ; private Spinner f romlang = n u l l ; p rivate Spinner toLang = n u l l ; private Button t ranslateBtn = null; private String [ ] langSho rtNames = n u l l ; private Handler mHandler = new Handle r ( ) ; p rivate ITranslate mTranslateService; p rivate ServiceConnection mTranslateConn = new ServiceConnection ( ) { public void onServiceConnected( ComponentName name , IBinder service) { mTranslateService = ITranslate . St u b . asinterface( service) ; if ( mTranslateService ! = n u l l ) { t ranslateBt n . setEnabled ( t rue ) ; } else { t ranslateBt n . setEnabled ( false ) ; Log . e ( TAG, "Nie można znaleźć us.ługi TranslateService" ) ; }
public void onServiceDisconnected ( ComponentName name) { t ranslateBt n . setEnabled ( false ) ; mTranslateService null; =
} };
@Over ride p rotected void onCreate(Bundle icicle) {
596
Android 2. Tworzenie aplikacji
supe r . onCreate(icicle ) ; setContentView ( R . layout .main ) ; inputText = ( Edi tText ) findVie11Byid ( R . i d . input ) ; outputText = ( Ed itText ) findViewByid ( R . id . t ranslatio n ) ; f romlang = {Spinner) findViewByi d { R . i d . from ) ; tolang = ( Spinne r ) findViewByid ( R . i d . to ) ; langSho rtNames
=
getResou rces ( ) . getStringArray ( R . a r r a y . l a n g u age_values ) ;
t r a n s lateBtn = { Button ) findViewByid ( R . id . t ra n s lateBtn ) ; t r a n s lateBt n . setOnClicklistene r ( this ) ; Ar rayAdapter f romAdapter = A r rayAdapte r . c reateF romReso u rce( this , R . a rray. languages, android . R . layou t . simple_spinner_item ) ; f romAdapter. setDropDownViewResou rce(android . R . layout. simple_dropdown_item_lline ) ; fromlang . setAd a pt e r { f romAdapter ) ; f romlang . setSelection { 1 ) ; li Polski A r rayAdapter toAdapter = A r rayAdapte r . c reateFromResource { t h i s , R. a rray . languages , and roid . R . layout . simple_spinner_item) ; toAdapter . setDropDownViewResource ( a n d roid . R . layout. simple_d ropdown_item_lline) ; tola n g . setAdapte r { toAdapte r ) ; tolang . setSelection ( 3 ) ; li Niemiecki inputText . selectAl l { ) ; Intent intent = new Inte n t { Intent . ACTION VIEW) ; bindServic e ( intent, mTranslateConn , Context . BIND_AUTO_CREATE ) ;
} @Dverride protected void onDestroy ( ) { supe r . onDestroy ( ) ; unbindService(mTranslateConn ) ;
} public void onCli c k ( View v ) { if ( inputText . getText ( ) . length ( ) > O ) { doTranslate ( ) ;
}
}
p rivate void doTranslat e ( ) { mHandle r . post ( new Runnable { ) { public void run { ) { String result = " " ; t ry { int f romPosition = f romlang . getSelecteditemPosition { ) ; int toPosition = toLang . getSelecteditemPosition { ) ; String input inputText . getTex t { ) . toString { ) ; i f ( in p u t . lengt h { ) > 5000) input input . substring { 0 , 5000 ) ; Log . v {TAG, "Tłumaczenie z " + langShortName s [ f romPosition] + langSho rtNames [ toPositio n ] ) ; result = mTranslateService . t ranslate { input , =
=
"
na
"
+
Rozdział 1 5 • Analiza interfejsów przetwarzania tekstu na mowę oraz tłumaczenia
597
langSho rtNames [ f romPosition ] , langShortName s [ toPositio n ] ) ; if ( result == null) { th row new Exception ( " Proces t:l:umaczenia zakończony niepowodzeniem " ) ; } outputText . setText ( result ) ; inputText . selectAll ( ) ; } catch ( Exception e ) { Log . e ( TAG, "Błąd : " + e . getMessage ( ) ) ; }
}) ;
}
} }
Nasza główna aktywność konfiguruje interfejs użytkownika i usługę, a także dostarcza me todę wywołującą proces tłumaczenia po wciśnięciu przycisku. Pozostał nam jeszcze do zde fo1iowana tylko plik AndroidManifest.xml, pokazany w listingu 15.10. Zauważmy, że musimy wprowadzić upoważnienie do uzyskania dostępu do internetu, aby posiadać możliwość wywo łania interfejsu Google AJAX Language API. Listing 1 5.1 O. Plik AndroidManifest.xml c ?xml version=" l . O " encoding= " ut f - 8 " ?> capplication and roid : label="Tłumaczenie" and roid : icon="@drawable/icon"> cuses - p e rmission android : name=" and ro id . permission . INTERNET" />
Przed poprawnym skompilowaniem przykładu musimy wprowadzić klasę pomocniczą. W pro jekcie Jakarta Commons Lang znajduje się klasa StringEscapeUtils, którą wykorzystamy do konwersji wynikowego ciągu znaków z interfejsu AJAX Language API na tekst zrozumiały dla
598
Android 2. Tworzenie aplikacji
użytkownika. Interfejs ten zwraca nam obiekty XML reprezentujące określone znaki spe cjalne. Na przykład odpowiednikiem apostrofu jest tu wartość ' ; . Chcemy, aby te znaki specjalne były wyświetlane w sposób znany użytkownikowi. W tym celu zastosujemy pro jekt Jakarta Commons Lang. Można go znaleźć pod adresem:
http://commons.apache.org/langl Wejdźmy na stronę projektu Jakarta Commons Lang i pobierzmy odpowiedni plik .zip (dla systemów Windows) lub .tar (dla systemów Linux oraz Mac OS X), w którym zawarte są pliki .jar. Następnie należy je rozpakować. W środowisku Eclipse wybieramy następnie projekt, klikamy go prawym przyciskiem myszy i wybieramy opcje Build Path!Configure Build Path. Klikamy zakładkę Libraries i wybieramy opcję Add External fARs. Wyszukujemy pobrany plik commons-lang i dodajemy go. Aby zakończyć proces dodawania pliku, klika my przycisk OK. Cała aplikacja powinna zostać bezbłędnie zbudowana. Nic nie stoi na przeszkodzi, żeby teraz ją wypróbować. Jeżeli nie wygląda ona zbyt dobrze w orientacji pio nowej, możemy wypróbować kombinację klawiszy Ctrl+F12, aby przełączyć emulator w tryb orientacji poziomej. Jeżeli wątpimy w poprawność generowanych wyników, możemy je po równać z tłumaczeniem na serwerze Google:
http:llwww.google.com/uds/samples//anguage/translate.html Chcielibyśmy uczulić Czytelnika na kilka rzeczy. Z powodu określonych zapisów w warun kach korzystania z usługi Google nasz przykład zawiera w interfejsie użytkownika ciąg zna ków powered by Google. Te same warunki określają maksymalny limit 5000 wprowadzanych znaków, zatem po przekroczeniu tej ilości nadmiar jest usuwany. Prawdopodobnie chcemy zaprojektować tu nieco inny model, na przykład umożliwiający dzielenie tekstu na edyto walne fragmenty, które są następnie przesyłane do interfejsu API. Celowo stworzyliśmy krótką listę dostępnych języków, aby nasza aplikacja była łatwiejsza do zarządzania, można jednak bez problemu zamieścić dowolną ilość języków w tablicy ciągów znaków. Musimy mieć jednak świadomość, że czcionki Droid mogą nie posiadać kompletu znaków dla nie których języków, które są dostępne w tłumaczu. Czcionki te zostały stworzone specjalnie dla systemu Android, nie zostały w nich jednak zamieszczone wszystkie istniejące znaki na świecie. Jeżeli tłumaczenie wynikowe wygląda podejrzanie, prawdopodobnie mamy pro blem z czcionką. Można temu zapobiec poprzez wprowadzenie dodatkowych czcionek, nie jest to jednak Lematem tego rozdziału. Odpowiedzi interfejsu API przybierają postać for matu JSON. Zatem będziemy za pomocą tego formatu poddawać analizie składniowej zwracane wynikowe ciągi znaków (format JSON stanowi część struktury Androida, zatem nie musieliśmy go pobierać jako osobnego pliku .jar). Jedną z cech interfejsu AJAX Language API jest brak obowiązku informowania go o języku źródłowym. Interfejs ten spróbuje samodzielnie ustalić, jaki język jest używany. Jeżeli chcemy skorzystać z takiego rozwiązania, nie zamieszczamy wartości języka źródłowego ,,. przeka zywanym adresie URL, lecz zamiast tego w atrybucie langpai r= zamieszczamy wartość %7C. Jest to przydatna funkcja, jeśli nie jesteśmy pewni, jaki język źródłowy został użyty; jednak jeżeli ilość wprowadzonego tekstu jest zbyt mała, interfejs API może nie rozpoznać języka.
Rozdział 1 S • Analiza interfejsów przetwarzania tekstu na mowę oraz tłumaczenia
599
Podsumowanie Pokazaliśmy w tym rozdziale, w jaki sposób aplikacja systemu Android może przemówić do użytkownika. Android został wyposażony w bardzo przyjemny silnik TTS, pozwalający na łatwe wykorzystanie tej funkcji. Dla programisty nie ma tu zbyt wiele do nauki. Silnik Pico zajmuje się większością operacji. Pokazaliśmy, że jeżeli syntezator natrafi na problematycz ne słowo, istnieją sposoby uzyskania pożądanego efektu jego wymówienia. Zaawansowane funkcje również znacznie ułatwiają życie. Podczas pracy z technologią przetwarzania tekstu na mowę musimy pamiętać o kilku podstawowych zasadach: o oszczędzaniu zasobów, roz sądnym współdzieleniu silnika TTS oraz właściwym wykorzystaniu syntezatora mo"''Y· Zademonstrowaliśmy także sposób wywołania interfejsu API firmy Google poprzez inter net. W naszym przykładzie był to interfejs Google AJAX Language API, jednak taka sama technika obowiązuje wobec pozostałych interfejsów firmy Google. Jest to wspaniałe rozwią zanie dla osób wymagających tłumaczenia na bieżąco tekstu na inny język.
ROZDZIAŁ
16 E krany d otykowe
Wiele urządzeń obsługiwanych przez system Android posiada ekran dotykowy. W przypadku braku klawiatury fizycznej dane muszą być wprowadzane przez użytkownika za pomocą takiego dotykowego ekranu. Nieraz więc aplikacje muszą charakteryzować się możliwością przetwarzania danych wprowadzanych po przez dotyk. Czytelnicy najprawdopodobniej mieli już do czynienia z wirtualną klawiaturą, która jest wyświetlana w momentach, gdy należy wprowadzić jakieś dane. W rozdziale 7., poświęconym aplikacji wyświetlającej mapę, dotyk posłużył nam do przesuwania mapy. Takie implementacje interfejsu ekranu dotykowe go były dotychczas przed nami ukryte, teraz jednak pokażemy, w jaki sposób możemy wykorzystać jego możliwości. Podzieliliśmy ten rozdział na cztery zasadnicze części. Część pierwsza została poświęcona obiektom klasy MotionEvent, które powiadamiają aplikację o tym, że użytkownik dotyka ekranu. Zajmiemy się w tej części także obiektami Veloci ty -...Tracker oraz funkcją przeciągania elementów na ekranie. W drugiej sekcji skupimy się na wielodotykowości (ang. multi-touch), czyli możliwości wprowadza nia danych za pomocą wielu palców jednocześnie. W części trzeciej omówimy funkcje dotyku w aplikacjach obsługujących mapy, ponieważ są w nich stoso wane specjalne klasy i metody, ułatwiające powiązanie map z ekranem doty kowym. Ostatni podrozdział dotyczy gestów, czyli wyspecjalizowanego rozwią zania, w którym zaprogramowane sekwencje dotykowe są interpretowane jako polecenia.
Klasa MotionEvents W tej części poświęcimy uwagę sposobom informowania aplikacji przez system o zdarzeniach dotykowych, wywoływanych przez użytkownika. Na razie sku pi.my się na dotykaniu ekranu za pomocą jednego palca (funkcja wielodotykowo ści zostanie omówiona w dalszej części rozdziału). Ekran dotykowy wykonany jest ze specjalnych tworzyw, przetwarzających siłę nacisku na współrzędne ekranu. Informacja o dotyku jest przetwarzana na dane, które są z kolei przekazywane oprogramowaniu.
602
Android 2. Tworzenie aplikacji
Dotknięcie ekranu przez użytkownika powoduje utwo rzenie obiektu MotionEvent. Obiekt ten zawiera informacje o czasie i m iejs cu dotknięcia oraz inne info rmacj e na temat tego zdarzenia. Zostaje on przekazany odpowiedniej metodzie danej aplikacji. Może to być na przykład metoda onTouchEvent ( ) klasy Vie•1. Nie zapominajmy, że klasa View jest nadrzędna w stosunku do dość licznej grupy innych klas, w tym takich jak Layout, Button, L i s t , Surface, C l o c k i tak dalej . Oznacza to, że za pomocą zdarzeń dotykowych możemy od działywać na te wszystkie rodzaje obiektów klasy View. Wywołana metoda może przeanalizować obiekt Mo t ion View w celu podjęcia decyzji o rodzaju przeprowadzanej czynności. Na przy kład klasa MapView może wykorzystywać zdarzenia dotykowe do przem ieszczan ia mapy na boki, dzięki czemu użytkownik ręcznie "''Yszukuje interesujące go punkty. Obiekt wirtualnej klawiatury może odbierać zdarzenia dotykowe, które po wciśnięciu klawisza dotykowego są przetwarzane na znaki '"'prowadzane do innej części interfejsu użytkownika. Obiekt Mot i on Eve n t należy do sekwencji zdarzeń związanych ze zjawiskiem dotykania ekranu przez użytkownika. Sek·wencja tal
Jeżeli proced ura obsługi obiektu MotionEvent ( p op rzez metodę onTouchEvent ( ) lub o n Tou c h ( ) ) pochłonie zdarzenie i nikt nie musi o tym wiedzieć, metoda powinna zwrócić wartość t rue. W ten sposób system zostaje poinformowany, że zdarzenie nie musi zostać przesłan e do innych widoków. Jeżeli obiekt View nie jest zainteresowany tym zdarzeniem, ani żadnym przyszłym zdarzeniem związanym z tą sekwencją dotyku, zwraca wartość false. Metoda onTouchEvent ( ) bazowej klasy View nie wykonuje żad nej czynności i zwraca war tość false. Jej klasy podrzędne mogą, ale nie muszą zachowywać się w ten sam sposób. Na przykład obiekt Button pochłonie zdarzenie dotyku, ponieważ dotknięcie jest równoważne kliknięciu, zatem zwraca wartość t rue z metody onTouchEvent ( ) . Po otrzymaniu działania ACTION_DOWN obiekt Button zmieni swój kolor, aby w ten sposób poinformować, że prze twarza zdarzenie kliknięcia. Obiekt ten czeka również na działanie ACTION_ UP, które oznacza za-
Rozdział 1 6 • Ekrany dotykowe
603
kończenie czynności przez użytkownika, dzięki czemu zostaje uruchomiony proces kliknię cia. Jeżeli obiekt Button zwrócił z metody onTouchEvent ( ) wartość false, nie otrzyma już żadnego obiektu MotionEvent, informującego o zdjęciu przez użytkownika palca z ekranu. Jeżeli chcemy, aby zdarzenia dotyku definiowały nową czynność na określonym obiekcie możemy rozszerzyć klasę, przesłonić metodę onTouchEvent ( ) i wstawić własny algo rytm. Możemy także zaimplementować interfejs View. OnTouchlistene r i skonfigurować procedurę obsługi wywołań zwrotnych wobec obiektu View. Jeżeli skonfigurujemy proce du rę obsługi wywołania metody onTouch ( ) , zostaną do niej dostarczone wszelkie obiekty MotionView, zanim przejdą do metody onTouchEvent ( ) klasy View. Metoda onTouchEvent ( ) zostanie wywołana jedynie w przypadku zwrotu wartości false przez metodę onTouch ( ) . Przejdźmy do przykładowej aplikacji, która powinna ułatwić zrozumienie omówionych zjawisk.
View,
Listing 16.l przedstawia kod XML układu graficznego aplikacji. Utwórzmy nowy projekt w środowisku Eclipse i '"stawmy weń ten układ graficzny. Listing
16.1. Plik XM L u kła d u
graficznego dla aplikacji TouchDemo 1
c?xml version= " l . 0 " encoding= " u t f - 8 " ?>
6 1 6 Android 2 . Tworzenie aplikacji androi d : tag= " t rueDot" androi d : layout_width="wrap_content" android : layout_height="wrap_content" I>
import import import import import import import
a n d roid . content . Context; a n d roid . g raphics . Canva s ; a n d roid . g raphics . Colo r ; a n d roid . g raphics . Paint ; android . util . AttributeSet; a n d roid . view.MotionEvent ; a n d roid . view.View;
public class Dot extends View { private static finał float RADIUS private float x = 3 0 ; private float y = 3 0 ; private float initialX; private float initialY; private float offsetX; p rivate float offsetY; p rivate Paint backgroundPaint ; private Paint myPaint ;
20 ;
public Dot ( Context context, Att ributeSet att r s ) { s u p e r ( context , a t t rs ) ; backg roundPaint new Pa int ( ) ; backgroundPaint . setColo r ( Co lo r . BLUE ) ; =
myPaint = new Paint ( ) ; myPaint . setColo r ( Color . WHITE) ; myPaint . setAntiAlias ( t rue ) ; }
@Over ride public boolean onTouchEvent (MotionEvent event ) { int action = event . getAction ( ) ; switch ( action) { case Mot ionEvent . ACTION_DOWN :
li Musi zapamiętać położenie środka punktu startowego li naszego obiektu Dot oraz w którym miejscu następuje dotknięcie initialX = x ; initialY = y ; offsetX = event . getX ( ) ; offsetY = event . getY ( ) ; brea k ; case MotionEvent . ACTION MOVE : case MotionEvent . ACTION_UP : case Mot ionEvent . ACTION_CANCEL : x = initialX + event . getX ( ) y = initialY + event . getY ( ) break; }
·
offsetX; offsetY;
Rozdział 1 6 • Ekrany dotykowe
617
even t . recycle ( ) ; retu rn ( t r ue ) ;
} @Ove r ride public void d raw(Canvas canva s ) { int width = canva s . getWidt h ( ) ; int height ca� vas . getHeight ( ) ; canvas . d rawRect ( O , O , width, height, backgroundPain t ) ; =
canvas . d ra�1Ci rcle ( x , y , RADIUS , myPaint ) ; invalidate ( ) ;
}
Rysunek 16.2. I nte rfej s użytkownika aplikacji demonstrującej zjawisko przesuwania obiektu Po uruchomieniu tej aplikacji ujrzymy białą kropkę na niebieskim tle. Możemy ją dotknąć i przesuwać po obszarze ekranu. Po oderwaniu palca od ekranu kropka nie wraca na pozy cję początkową i jest gotowa do dalszego przesuwania. Znacznie uprościliśmy cały proces, aby zaprezentować jedynie podstawy przenoszenia obiektu po ekranie. Metoda d raw( ) umiesz cza kropkę w bieżącej pozycji X i Y. Poprzez otrzymywanie obiektów MotionEvent w meto dzie onTouchEvent ( ) możemy w miarę przesuwania palca po ekranie modyfikować współ rzędne X i Y. Rejestrujemy położenie początkowe kropki oraz początkowy punkt dotknięcia ekranu w metodzie ACTION_DOWN. Ponieważ nie zawsze dotykamy środka obiektu, współ rzędne dotyku nie są tożsame współrzędnym położenia obiektu. Musimy również wziąć pod uwagę przypadek, gdy punktem odniesienia dla naszego obiektu jest nie środek, a lewy górny róg ekranu. Kiedy palec porusza się po ekranie, dopasowujemy położenie obiektu poprzez obliczanie różnic współrzędnych X i Y w oparciu o otrzymy>vane obiekty MotionEvent. Po zaprzestaniu ruchu (na przykład z powodu wywołania działania ACTION_UP) definiujemy
61 8
Android 2. Tworzenie aplikacji
końcowe położenie obiektu, stanowiące ostatnie współrzędne dotyku. Troszeczkę tu oszu kujemy, ponieważ widok Dot jest umieszczony na ekranie względem punktu (O, O). Oznacza to, że możemy po prostu narysować kropkę zależną od punktu (O, O), w przeciwieństwie do innego punktu referencyjnego. Gdyby nasz obiekt nie odnosił się do punktu (O, O), musieli byśmy wstawić dodatkowe pozycje dla położenia tego obiektu. Nie musimy się także przej mować w naszym przykładzie paskami przewijania, które skomplikowałyby obliczenia poło żenia obiektu na ekranie. Podstawowa zasada jest jednak zawsze taka sama. Znając początkową pozycję przesuwanego obiektu oraz śledząc zmiany wartości dotyku, począwszy od zdarzenia ACTION_ DOWN do zdarzenia ACTION UP, możemy dokładnie ustalić położenie obiektu na ekranie. Upuszczanie obiektu na inny obiekt wymaga jedynie znajomości położenia elementów na ekranie. Nie zaprezentujemy przykładu upuszczania obiektu, wyjaśnimy jednak zasady działania tego mechanizmu. Jak widzieliśmy wcześniej, w czasie przesuwania obiektu po ekranie znamy jego relatywne położenie w stosunku do jednego lub kilku punktów na ekranie. Możemy również poznać rozmiary i położenie innych elementów. Mając te dane, możemy określić, czy przenoszony obiekt znajduje się „ponad" innym obiektem. Zazwyczaj stosowanym algo rytmem jest iterowanie po dostępnych obiektach, na których możemy umieścić nasz ele ment, oraz sprawdzanie, czy bieżące położenie przesuwanego obiektu pokrywa się z położe niem obiektu znajdującego się „pod spodem". W tym celu mogą zostać wykorzystane rozmiar i położenie każdego obiektu (czasami również kształt). Jeżeli otrzymamy zdarzenie ACTION_UP, oznaczające, że użytkownik upuścił przenoszony element, oraz obiekt ten znaj duje się nad innym elementem, muszą zostać uruchomione algorytmy przetwarzające dzia łanie upuszczenia. Może to być na przykład proces przenoszenia obiektu do kosza, z które go umieszczony obiekt może zostać usunięty. Może to być również folder, to którego jest przenoszony lub kopiowany plik.
Wielodotykowość Po omówieniu obsługi ekranu za pomocą jednego palca możemy przejść do kwestii wielo dotykowości. Technologia wielodotykowości zyskała olbrzymie zainteresowanie od czasu konferencji TED w 2006 roku, w czasie której Jeff Han zademonstrował wielodotykową powierzchnię dla komputerowego interfejsu Ul. Używanie wielu palców na ekranie otwiera mnóstwo możliwości manipulowania jego zawartością. Na przykład rozsunięcie dwóch pal ców na pliku graficznym może skutkować powiększeniem obrazu. Przyłożenie kilku palców na takim obrazie i ich obrót zgodnie z ruchem wskazówek zegara może obrócić ten rysunek. Obsługa wielodotykowości została wprowadzona w wersji 2.0 zestawu Android SDK. Poja wiła się możliwość wykorzystywania maksymalnie trzech palców jednocześnie do wykonywania takich czynności, jak przybliżanie, obracanie oraz wszelkie inne operacje, podczas których korzysta się z technologii wielodotykowości. Jednak po dłuższym zastanowieniu można stwierdzić, że nie ma w tym żadnej magii. Jeżeli elektronika urządzenia pozwala na wykry wanie wielodotykowości oraz informuje aplikację o przesuwaniu palców po ekranie i o ich zdjęciu z wyświetlacza, aplikacja ta może odczytać, co użytkownik chciał przekazać tym ge stem. Mimo że nie jest żadną magią, technologia ta nie należy do najprostszych wynalaz ków. W tym podrozdziale spróbujemy pomóc w zrozumieniu idei wielodotykowości. Podstawy wielodotykowości są dokładnie takie same, jak w przypadku pojedynczego dotyku. Tak jak poprzednio, obiekty MotionEvent są tworzone dla dotyków, a następnie przekazywane do odpowiednich metod. Program może przeczytać informacje o dotknięciach i zadecydo wać, jak je zinterpretować. Na podstawowym poziomie metody klasy MotionEvent są iden-
Rozdział 1 6 • Ekrany dotykowe
619
tyczne: to znaczy wy;vołujemy metody getAction ( ) , getDownTime ( J , getX ( J i tak dalej. Jednak w przypadku dotknięcia przez liczbę palców większą od jednego obiekt MotionEvent musi zawrzeć in fo rm acj e o dotkni ęciach wszystkich palców wraz z odpowiednim wyjaśnie niem. Wartość działania z metody getAction ( ) jest przeznaczona dla jednego palca, nie dla wszystkich. v\Tartość getDownTime ( ) jest definiowana przez pierwszy palec, który dotknie powi erzchni, i pozostaje niezmienna tak długo, dopóki przynajmniej jeden z palców dotyka ekranu. Wartości położenia z metod getX ( ) oraz g e t Y ( ) , a także metody g e t P re s s u r e ( ) i getSize ( ) mogą pobierać argumenty dla palca; musimy zatem skorzystać z jakiejś warto ści indeksu do uzyskiwania informacji na temat interesującego nas palca. Mamy do dyspo zycji używane przez nas wcześniej wywołania metod niep ob ierające żadnego argumentu określającego palec (na przykład wywołania metod getX ( ) i getY ( ) ), zatem który palec będzie dawcą wartości dla tych metod? Można to odkryć samemu, jest to jednak dość czasochłon ne zajęcie. Dlatego jeżeli nie bierzemy cały czas pod uwagę wielodotykowości, mogą pojawić się dziwne rezultaty. Przeanalizujmy jednak dokładnie ten temat, żeby dowiedzieć się, co należy robić. Podstawową metodą obiekt u MotionEvent używaną w przypadku wielodotykowości jest getPointerCount ( ) . Zostaje w niej zdefiniowana ilość palców reprezentowanych w obiekcie MotionEvent. Nie musi ona informować o faktycznej ilości palców biorących udział w wie lodotykowości, ponieważ jest to zależne od sprzętu oraz od systemu Android. Może się zda rzyć, że w pewnych urządzeni ach metoda getPointerCount ( ) nie b ędzie zgłaszała wszyst kich palców dotykaj ących ekranu, a tylko niektóre. Idźmy jednak dalej. J ak tylko zostanie zgłoszona większa ilość palców w obiektach MotionEvent, musimy zaj ąć się indeksem oraz identyfikatorami wskaźnika.
Obiekt MotionEvent przech owuj e in fo rmacj e dla wskaźników, począwszy od indeksu O do wartości reprezentującej ilość palców zgłoszonych do tego obiektu. T nd eks wskaźnika zawsze rozpoczyna się od wartości O. Jeżeli zostały zgłoszon e trzy palce, indeksy ich wskaź nika przybiorą wartości O, 1 i 2. Wywołania takich metod jak getX ( ) muszą zawierać wartość indeksu wskaźnika palca, o którym chcemy uzyskać informacje. Identyfikatory wskaźnika stanowią wartości typu całkowitego, wskazujące palec, który jest obserwowany. Identyfika tor wskaźnika posiada wartość O dla pi erwszego palca, który dotknął ekranu, jednak nie zawsze jest rozpoczynany od tej wartości w przypadku palców naprzemiennie przytykanych i odrywanych od ekranu. U'.?najmy identyfikator wskaźnika za nazwę palca śledzonego przez system. Na przykład V\ryobraźmy sobie dwuetapową sekwencję dotyku, wykonywaną przez dwa palce w następującej kolej ności: przykładamy do ekran u pal ec 1, następnie palec 2, odrywamy palec 1 i po nim podnosimy palec 2. Pierwszy przyłożony palec otrzyma identy fikator od wartości O. Drugi przyłożony palec posiada identyfikator 1. Po podniesieniu pi erwszego palca drugi palec nadal będzie posiadał identyfikator 1. W tym samym czasie indeks wskaźnika dla palca dwa uzyska wartość O, ponieważ indeks ten zawsze rozpoczyna się od wartości O. W omawianym przykładzie identyfikator wskaźnika 1 rozpoczyna j ako indeks wskaźnika 1 po dotknięci u ekranu pierwszym palcem, a następnie przeskakuje do indeksu wskaźnika O po podniesieniu tego palca. Nasze aplikacj e będą korzystały z identyfi katorów wskaźnika do powiązania zdarzeń z określonym palcem, nawet jeżeli w tym czasie są używane także inne palce. Spójrzmy na przykład.
Listing 16.10 przedstawia nowy plik XML układu graficznego oraz kod Java dla aplikacji ob sługującej funkcję wielo do tykowości. Stwórzmy nov.ry projekt, posiłkując się informacjami zawartymi w listingu 1 6 . 1 0, i włączmy go. Rysunek 16.3 stanowi ilustrację interfej s u użyt kownika tej aplikacji.
620 Android 2. Tworzenie aplikacji Listing 16.1 O. Plik XML układu graficznego i kod Java aplikacji demonstrującej technologię wielodotykowości c?xml version= " l . O " encoding=" u t f - 8 " ?>
li Jest to plik MainActivity.java import import import import import import impo rt
andro�d . a p p . Activity; android . o s . Bundle; android . util . Lo g ; android . view . MotionEvent; android . view . View; android . vie�1. View. OnTouchlistene r ; and ro id . widget . Relativelayout ;
public class MainActivity extends Activity implements OnTouchlistener { /** Wywoływane podczas pierwszego utworzenia aktywności. *I @Over ride public void o n C reat e ( Bundle savedinstanceState) { supe r . on C reate ( savedinstanceStat e ) ; setContentView ( R . layout . main ) ; Relativelayout layoutl = ( Relativelayout ) findVie�1Byid ( R . id . layout! ) ; layout l . setOnTouchListene r ( this ) ; }
@Over ride public boolean onTouch ( View v , MotionEvent event) { String myTag = v . getTag ( ) . toString ( ) ; Log. v ( myTa g , " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - " ) ; Log . v ( myTag , "Umieszczono widok " + myTag + " w metodzie onTouch " ) ; Log . v ( myTag, describeEvent(event ) ) ; i f ( " t rue" . equals ( myTag . s ubstring ( O , 4 ) ) ) { Log . v (myTag, " i zwracam wartość t rue" ) ; return t rue; else { Log . v ( myTa g , " i zwracam wartość false" ) ;
Rozdział 1 6 • Ekrany dotykowe
621
return fal s e ; } } p rotected static String desc ribeEvent ( MotionEvent event) St ringBuilder result = new StringBuilde r ( SOO ) ; res ul t . append ( "Działanie : " ) . append ( event . getAction ( ) ) . append ( "\ n " ) ; int numPointers event . getPointerCoun t ( ) ; result . append ( " Ilość wskażników: " ) . append ( numPointe r s ) . append ( " \ n " ) ; int ptridx O; while ( ptridx < num Po int e r s ) { int p t r i d = event . getPointerid ( pt ri dx ) ; result . append ( " Indeks wskażnika: " ) . append ( pt ridx ) ; result append ( " identyfikator wskażnika : " J . append(ptrid ) . append ( " \ n " ) ; result . append ( " Lokacj a : " ) . appen d ( event . getX(ptridx ) ) ; result . append ( " x " ) . append ( even t . getY ( pt ri dx ) ) . append ( " \ n " ) ; result . append ( " Siła nacis ku : " ) . append ( even t . getPre s s u re ( pt ridx ) ) ; result . append ( " Rozmia r : " ) . appen d ( event . getSize ( pt ridx ) ) . append ( " \ n " ) ; =
=
.
,
ptridx++; } result . append ( "Czas dotknięcia : " ) . append ( event . getDownTime ( ) ) . append ( "ms\n" ) ; res ult . append ( "Czas zda rzenia : " ) . append ( event . getEventTime ( ) ) . append ( "ms" ) ; result . append ( " Szacowany: " ) . appen d ( event. getEventTime ( ) - event . getDownTime ( ) ) ; result . append ( " ms\n " ) ; return result . toString ( ) ; }
Rysunek 1 6.3. Aplikacja demonstrująca zastosowanie wielodotykowości Aplikacja działa na emulatorze, nie ma jednak możliwości symulowania dotknięcia ekranu za pomocą kilku palców. Zostaną wyświetlone w takim przypadku wyniki podobne do przedsta wionych w poprzednim przykładzie. W listingu 16. 1 1 przedstawiamy przykładowe komunikaty
622
Android 2. Tworzenie aplikacji
narzędzia LogCat dla opisanej kilka stron wcześniej sekwencji dotyku, to znaczy palec 1 dotyka ekranu, następnie dołącza do niego palec 2, palec 1 zostaje uniesiony, o po nim następuje to samo z palcem 2.
Listing 16.1 1 . Przykładowe wyniki narzędzia LogCat dla aplikacji wi e l odoty kowej t rueLayoutTop t rueLayoutTop t rueLayou tTop t rueLayoutTop t rueLayoutTop t r ueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayout Top t rueLayoutTop
Umieszczono widok t rueLayoutTop w metodzie onTouch Dzialanie: O Ilość wskaźników: 1 Indeks wskaźnika: O , Identfikator ws kaź n i k a : O Lokacj a : 722 . 3844 x 94 . 37604 Sila nacisku : 0 . 07450981 Rozmi a r : 0 . 2 Czas dotknięci a : 1577822lms Czas zda rzenia : 1577822lms Szacowan y : O ms i zwracam wartość t rue
t rueLayoutTop
t rueLayoutTop t r ueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t ru eLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop trueLayoutTop t rueLayoutTop trueLayoutTop t ru e L ayou t Top t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop trueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop
t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop
Umieszczono widok t rueLayoutTop w metodzie onTouch Działanie: 2 Ilość wskaźników: 1 Indeks wskaźnika: O , Identyfikator wskaźnika : O Lokacj a : 722 . 3844 x 97 . 29675 Sila nacisku : 0 . 07450981 Rozmia r : 0 . 2 Czas do t kni ę c i a : 1577822lms Czas z d a rzenia : 15778470ms Szacowany: 249 m s i z�iracam wartość t rue Umieszczono widok t rueLayoutTop w metodzie onTouch Dzialanie: 261 Ilość wskaźników: 2 Indeks wskaźnika: O , Identyfikator wskaźnika : O Loka cj a : 722 . 3844 x 9 8 . 75711 Siła nacisku : 0 . 07450981 Rozmia r : 0 . 2 Indeks wskaźnika : l , Identyfikator wskaźnika: 1 Lokacj a : 343 . 8656 x 103 . 625 Sila nacis ku : 0 . 06666667 Size: 0 . 2 Czas dotknięcia : 15778221ms Czas zdarzenia : 15778499ms Szacowa n y : 278 ms i zwracam wartość t rue
Umieszczono widok t rueLayoutTop w metodzie onTouch Dzialanie: 2 Ilość wskaźników: 2 Indeks wskaźnika : O , Identyfikator wskaźnika: O Lokac j a : 702 . 8365 x 100 . 704285 Sila nacisku : 0 . 07450981 Rozmi a r : 0 . 2 Indeks wskaźnika : l , I dentyfikator wskaźnika : 1 Lokacj a : 343 . 8656 x 95 . 836395 Siła nacisku : 0 . 06666667 Rozmia r : 0 . 2 Czas dotknięcia : 15778221ms Czas zdarzenia : 15778785ms Szacowany: 564 ms i zwracam wartość t rue Umieszczono widok t rueLayoutTop w metodzie onTouch
Rozdział 1 6 • Ekrany dotykowe t rueLayoutTop trueLayoutTop trueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop trueLayoutTop t ruelayoutTop trueLayoutTop t rueLayoutTop trueLayoutTop
Działanie: 6 Ilość wskażników: 2 Indeks wskażnika: O , Identyfikator wskażnika : O Lokacj a : 702 . 8365 x 100. 704285 Siła nacisku : 0 . 07450981 Rozmia r : 0 . 2 Indeks wskażnika: 1 , Identyfikator wskażnika : 1 Lokacj a : 343 . 8656 x 95 . 34961 Siła nacisku : 0 . 06666667 Rozmia r : 0 . 2 Czas dotknięcia : 15778221ms Czas zdarzenia : 15778812ms Szacowa n y : S91 ms i zwracam wartość t rue
t rueLayoutTop trueLayoutTop t rueLayoutTop t rueLayoutTop t ruelayoutTop t rueLayoutTop trueLayoutTop t rueLayoutTop truelayoutTop trueLayoutTop
Umieszczono widok t rueLayoutTop w metodzie onTouch Działanie: 2 Ilość wskażników: 1 Indeks wskażnika : O , Identyfikator wskażnika : 1 Lokacj a : 343. 86S6 x 94 . 86282 Sila nacisku: 0 . 074S0981 Rozmia r : 0 . 2 Czas dotknięcia : 15778221ms Czas zdarzenia: 15778825ms Szacowany: 604 ms i zwracam wartość t rue
t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t ruelayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop t rueLayoutTop
Umieszczono widok t rueLayoutTop w metodzie onTouch Działanie : 1 Ilość wskażników: 1 Indeks wskażnika: O , Identyfikator wskażnika : 1 Lokacj a : 323 . 42917 x 9 2 . 42886 Sila nacisku : 0 . 07450981 Rozmi a r : 0 . 2 Czas dotknięcia : 15778221ms Czas zdarzenia : 15779138ms Szacowany: 917 ms i zwracam wartość t rue
623
Zastanówmy się, co się dzieje w tej aplikacji. Pierwszym zdarzeniem jest działanie ACTION_DOWN wywołane przyłożeniem pierwszego palca. Mówi nam o tym metoda getAction ( ) . Aby dowiedzieć się, jakie wyniki są generowane przez poszczególne metody, wystarczy spojrzeć na kod metody d e s c ribeEvent ( ) umieszczony w pliku MainActivi t y . j a v a . Otrzymujemy jeden wskaźnik, którego indeks i identyfikator mają przypisaną wartość O. Następnie zosta nie prawdopodobnie wygenerowanych kilka zdarzeń ACTION_MOVE dla tego palca. Ciągle posiadamy jeden wskaźnik zawierający wymienione wcześniej wartości. Chwilę później dotykamy ekranu drugim palcem. Działanie otrzymuje teraz wartość dziesiętną równą 261. Co to oznacza? Wartość działania składa się dwóch części: wskaźnika, dla którego "''Ykony wane jest działanie, oraz rodzaju przeprowadzanej czynności. Przekształcając wartość dzie siętną 261 na wartość szesnastkową, otrzymujemy Ox00000105. Działanie jest oznaczone przez najmniejszy bajt (w naszym wypadku 5), natomiast identyfikator wskaźnika jest defi niowany przez następny dostępny bajt (u nas jest to 1). Zwróćmy uwagę, że jesteśmy infor mowani o identyfikatorze wskainika, a nie o jego indeksie. Po dotknięciu ekranu trzecim palcem działanie uzyska wartość Ox00000205 (517 w systemie dziesiętnym). Dotknięcie czwartym palcem zmieniłoby wartość na Ox00000305 (dziesiętne 773 ) , i tak dalej. Przyjrzyjmy się następnej parze rekordów okna LogCat z listingu 16. 1 1 . Pierwszy rejestr od powiada za zdarzenie ACTION_MOVE. Pamiętajmy, że ciężko jest utrzymać palce w bezruchu na prawdziwym ekranie. Po podniesieniu palca 1 otrzymujemy podobną wartość działania,
624
Android 2. Tworzenie aplikacji
jak w przypadku jego przyłożenia do ekranu, tym razem jednak składowa działania zmienia wartość na 6 (poprzednio była wstawiona wartość 5). W przypadku wielodotykowości pod niesienie pierwszego paka generuje działanie o wartości Ox00000006 (w systemie dziesięt nym jest to wartość 6). Jeżeli podniesiemy drugi palec, otrzymamy działanie Ox00000106 (262 w kodzie decymalnym). Zauważmy, że nadal otrzymujemy informacje o dwóch pal cach, gdy jeden z nich generuje zdarzenie ACTION_ UP. Ostatnia para rekordów w listingu 6. 1 1 przedstawia jeszcze jedno zdarzenie ACTION_MOVE dla paka 2, po którym następuje działanie ACTION_UP. Tym razem widzimy wartość działa nia równą 1 (ACTION_ UP). Nie otrzymaliśmy wartości 262, ale za chwilę wyjaśnimy, dlaczego. Odnotujmy również fakt, że w przypadku zdarzenia ACTION_MOVE indeks wskaźnika uległ zmianie z wartości 1 na O, ale identyfikator wskaźnika ciągle wynosi O. Wracając do początku listingu 16. 1 1, identyfikator wskaźnika dla pierwszego przyłożonego paka posiada wartość O, dlaczego więc nie otrzymujemy wartości Ox00000005 (dziesiętnie 5) dla działania, skoro palec ten dotknął ekranu przed innymi palcami? Jest to dobre pytanie, na które nie ma dobrej odpowiedzi. Możemy otrzymać wartość działania równą 5 w nastę pującym przypadku. Najpierw dotykamy ekranu palcem 1, następnie palcem 2, dzięki cze mu otrzymujemy wartości działania odpowiednio O i 261 (pomijamy na razie zdarzenia ACTION_MOVE). Teraz podnosimy palec l (wartość działania 6) i ponownie przykładamy go do ekranu. Identyfikator wskaźnika dla drugiego palca pozostał niezmieniony i posiada wartość 1. Przez czas oderwania palca 1 od ekranu aplikacja wiedziała jedynie o identyfika torze wskaźnika posiadającym wartość 1. Po ponownym dotknięciu viyświetlacza palcowi l został przydzielony identyfikator O, skoro natomiast używamy wielu palców, otrzymujemy wartość działania równą 5 (identyfikator wskaźnika o wartości O i działanie o wartości 5). Odpowiedzią na zadane wcześniej pytanie jest kompatybilność wsteczna, nie powinniśmy się jednak cieszyć z jej powodu. W przypadku korzystania z dwóch palców, jeżeli pierwszy palec dotknie ekranu w jednym miejscu, a po nim drugi palec dotknie innego obszaru ekra nu, podniesienie pierwszego palca nie zostanie rozpoznane przez aplikację nieobsługującą zdarzeń wielodotykowości. Wynika to z faktu, że podniesienie tego palca spowoduje wyge nerowanie wartości działania 6, a nie 1. Podniesienie palca 2 spowoduje dopiero utworzenie wartości działania równej 1. Gdy na ekranie pozostaje tylko jeden palec, Android interpretuje jego zachowanie jako działania obsługiwane za pomocą jednego palca. Otrzymujemy zatem starą wartość ACTION_ UP równą 1, zamiast wielodotykowej wartości ACTION_ UP równej 6, powiązanej z identyfikatorem wskaźnika. Przyjrzyjmy się jednak ponownie - identyfikator wskaźnika dla ostatniego palca na ekranie nadal posiada wartość l, więc w rzeczywistości powinniśmy otrzymać wartość działania równą 262. Kod aplikacji musi takie przypadki rozpatrywać z ostrożnością. Identyfikator wskaźnika o wartości O mógłby wygenerować wartość zdarzenia ACTION_DOWN w przedziale od 1 do 5, w zależności od wykorzystywanych wskaźników. Ostatni podniesiony palec zawsze wygeneruje wartość zdarzenia ACTION_ UP równą 1, bez względu na identyfikator wskaźnika. Klasa MotionEvent zawiera pewne stałe pomocnicze, przydające się w zrozumieniu sytuacji. Na przykład stała MotionEvent . ACTION_POINTER_3_DOWN posiada wartość Ox00000205 (dzie siętnie 517), która, jak już wiemy, definiuje trzeci palec przyłożony do ekranu. Wartości te mogą jednak okazać się nie tak bardzo przydatne, jeżeli zdecydujemy się rozpoznawać przyłożone palce poprzez identyfikator wskaźnika umieszczony w drugim bajcie oraz po przez działanie widoczne w pierwszym bajcie. W rzeczywistości jednak byłoby nawet lepiej, gdybyśmy używali innych stałych klasy MotionEvent do odczytywania wartości zwracanych przez metodę getAc t i o n { ) . Myślimy tu o stałych MotionEvent . ACTION_ POINTER_ ID_MASK,
Rozdział 1 6 • Ekrany dotykowe
625
"ot ionEvent . ACTION_ MASK i Motio n E vent . ACTION_ POINTER_ ID_ S H IFT. Poprzez powiąza nie zwróconej wartości z każdą wymienioną maską oraz przeniesienie wyniku wygenerowanego dla identyfikatora wskaźnika będziemy mogli w rzetelny sposób określić sytuację, bez względu na to, ile palców jest obsługiwanych przez urządzenie. Przykładowy kod został umieszczony
16.12.
w listingu
Listing 1 6.12. Przykładowy kod służący do określenia rezultatu działania metody MotionEvent.getAction() int action = event . getActio n ( ) ; int p t r i d = event . getPointe rid ( O ) ; if ( event . getPointerCount ( ) > 1 ) ptrid ( a c t i on & MotionEvent . ACTION POINTER_ID_MASK) >>> MotionEvent. ACTION_POINTER_ID SHIFT; action = action & MotionEven t . ACTION_MASK; i f ( action < 7 && action > 4 ) action = action S; int pt rindex event . findPointerindex ( ptrid ) ; =
-
=
Zauważmy, że powyższy kod radzi sobie z omówioną wcześniej osobliwością, w której identyfi kator wskaźnika ostatniego palca dotykającego ekranu nie stanowi składowej wartości zwra canej przez metodę getAction ( ) , a także w której zwracana wartość składowej działania jest równa
5 lub 6 zamiast O lub 1. Po ·wykonaniu instrukcji umieszczonych w listingu 16.12
atrybut pt r i d będzie przechowywał identyfikator wskaźnika związany z określonym działa
niem, a c t i o n będzie posiadał wartości w przedziale od O do 4, a w atrybucie p t rindex zo
stanie umieszczona wartość indeksu wskaźnika stosowana przez metodę
getX ( ) oraz inne
metody klasy MotionEvent. Spoglądając na wartości zwracane z metody getAction ( ) , można j e interpretować w taki sposób, ż e wartości większe o d 4 reprezentują wartości związane z identyfi katorem wskaźnika. Wartości mniejsze lub równe
4 związane są jedynie
z używanym palcem,
bez względu na jego identyfikator wskaźnika.
Obsługa map za pomocą dotyku Mapy również obsługują zdarzenia dotyku. Widzieliśmy już, że dotknięcie mapy może wy wołać kontrolkę przybliżania lub umożliwia przesuwanie mapy. Są to wbudowane funkcje map.
A jeżeli chcemy zrobić coś innego? Zademonstrujemy implementację kilku ciekawych roz wiązań związanych z mapami, między innymi funkcji pobierającej współrzędne geograficzne lo kacji po jej dotknięciu. W ten sposób mamy dostęp do bardzo wielu przydatnych funkcji. Jedną z głównych klas służących do obsługi map jest klasa MapView. Klasa ta, podobnie jak omawiana wcześniej klasa View, zawiera metodę onTouchEvent ( ) , której jedynym argu mentem jest obiekt MotionEvents. Możemy także użyć metody setOnTouchlis t e ne r ( ) wobec klasy MapView do skonfigurowania procedury obsługi wywołań zwrotnych, reagujących na dotknięcia. Innymi głównymi obiektami dla map jest zestaw nakładek Overlay, w tym takie klasy jak ItemizedOverlay i MyLocationOverlay. Wszystkie wymienione obiekty zo stały omówione w rozdziale
7.
Klasy Ove rlay również posiadają metodę onTouchEvent ( ) ,
chociaż jej sygnatura jest nieco inna od analogicznej metody używanej w standardowej kla sie View. Dla klasy Overlay wygląda ona tak:
onTouchEvent (and ro id . view. MotionEvent e , MapVie11 mapView)
626
Android 2. Tworzenie aplikacji
Możemy przesłonić metodę onTouchEvent ( ) , jeśli chcemy wprowadzić inne formy obsługi map. Częściej spotykamy się z przesłanianiem metod w klasie Overlay, niż w klasie MapView, zatem obecny ustęp poświęcony został temu zagadnieniu. Podobnie jak poprzednio metoda
onTouchEvent ( ) klas Ove rl a y zajmuje się obiektami MotionEvent. Nawet w przypadku map obiekt MotionEvent przechowuje współrzędne X i Y obszaru dotkniętego przez użyt kownika. Jest to tutaj niemal nieprzydatne, ponieważ przeważnie będziemy chcieli znać współrzędne dotkniętego punktu na mapie, nie na ekranie. Na szczęście istnieją pewne roz wiązania. Klasa MapView została wyposażona w interfejs P roj ection, który zawiera metody przetwa rzające piksele na obiekty GeoPoint i odwrotnie. Dostęp do tego interfejsu uzyskujemy poprzez wywołanie metody MapView . g e t P roj ection ( ) . Po wprowadzeniu interfejsu Proj ection możemy do konwersji wykorzystać metody f rom Pix e l s ( ) i toPixels ( ) . Pamiętajmy, że klasa Proj ection jest przydatna jedynie wtedy, gdy mapa nie ulega zmianie w widoku. We wnątrz metody onTouchEvent ( ) możemy za pomocą metody f romPixels ( ) przekonwertować
wartości X i Y położenia na obiekt GeoPoint.
Bardzo przydatną i jednocześnie interesującą metodą klasy Ove rlay jest metoda onTap ( ) , bardzo podobna do omówionej wcześniej metody onTouch ( ) , różniącej się jednak pewnym kluczowym aspektem. Klasy Overlay nie posiadają metody onTouch ( ) . Sygnatura metody onTap( ) została pokazana poniżej: public boolean onTap( GeoPoint p , MapView mapView) Oznacza to, że po dotknięciu klasy Overlay przez użytkownika metoda onTap ( ) zostanie wywołana wraz z obiektem Geopoint wskazującym dotknięte miejsce. W ten sposób zaosz czędzimy mnóstwo czasu podczas prób określenia dotkniętego miejsca na mapie. simy już martwić się o konwersję współrzędnych Zajmuje się tym system.
Przyjrzymy się teraz ponownie przykładowi z rozdziału 7„ wraz z przyciskami oglądania jej
w
ie mu
X i Y lokacji na współrzędne geograficzne. w
którym wyświetlaliśmy mapę
różnych trybach (satelitarny, uliczny, widok ruchu
ulicznego oraz tryb standardowy). Dodamy możliwość uruchamiania trybu widoku uliczne go lokacji wskazanej na mapie. W tym celu musimy umieścić nakładkę Overlay w widoku MapView, a po dotknięciu obiektu Overlay zdarzenie to zostanie przekształcone na wskaza ne miejsce na mapie. Po przekształceniu w taki sposób lokacji uruchomimy intencję wy wołującą tryb widoku ulicznego. Rozpoczniemy od utworzenia w środowisku Eclipse kopii aplikacji MapsDemo z rozdziału 7. (listingi 7.12 i 7. 13). Następnie wykorzystamy informacje z listingu 16. 13 do zmodyfikowania metody onCreate ( ) głównej klasy Activity oraz do damy nową klasę, również umieszczoną w listingu 16.13, w pliku C l i ckReceiv e r . j ava. Zmiany
w metodzie o n C reate ( ) zostały zaznaczone tłustym drukiem. Interfejs, widoczny na rysunku
7.7, nie ulegnie zmianie. Listing 16.13. Dodawanie funkcji d oty ku do aplikacji demonstrującej mapy @Over ride protected void onCreate(Bundle savedinstanceState) { supe r . onCreate( savedinstanceStat e ) ; setContentView ( R . layout . mapview) ; mapView
=
(MapView) findViewByid ( R . i d . mapview) ;
ClickReceiver clickRecvr = new ClickReceive r ( this ) ; mapView . getOverlays ( ) . add( clickRecv r ) ;
Rozdział 16 • Ekrany dotykowe
627
} li Jest to plik ClickReceiver.java
import import import import
android . content. Context; and roid . conten t . Intent; and roid . ne t . U r i ; and roid . ut i l . Lo g ;
import com . g oogle . a nd roid . maps . GeoPoint; import com . google . a n d roid . maps . MapView; import com . g oogle . a n d roid . maps . Overlay; public class ClickReceiver extends Overlay{ p rivate static fina! String TAG = " Cl ickReceive r " ; private Context context ; public ClickReceive r ( Context _ context) context = _ cont ext ;
} @Ove r ride public boolean onTa p ( GeoPoint p , MapView mapView) { Log . v ( TAG , "Ot rzymano kliknięcie w tym punkcie: " + p ) ; ifimapView . is S t reetView ( ) ) { Intent mylntent = new Intent ( Intent . ACTION VIEW, U r i . parse ( " google . s t reetview : c bll=" + ( f loat ) p . getlatitudeE6 ( ) I lOOOOOOf + " , " + (float ) p . getlongitudeE6 ( ) I lOOOOOOf +"&cbp=l , 180 , , 0 , l . O " )); context . sta rtActivit y ( myintent ) ; return t r u e ;
}
return false;
To wystarczy, aby uruchomić zmodyfikowaną wersję aplikacji - chyba że nie ·posiadamy aplikacji widoku ulicznego (StreetView) na emulatorze lub w urządzeniu. Aplikacja StreetView została zamieszczona w emulatorach CupCake (1.5) i Donut ( 1 .6), a została usunięta z emulatora Eclair (2.0). Można rozwiązać ten problem poprzez nabycie rzeczywistego urządzenia zawie rającego funkcję StreetView i przetestowanie na nim aplikacji. Osoby posiadające wyłącznie emulator mogą sprawdzić następującą procedurę:
1 . Skonfiguruj urządzenie AVD oparte na interfejsie Google API w wersji 1.6 lub
1.5.
2. Wykonaj polecenie adb pull lsystemlapplSt reetView. apk, aby skopiować
aplikację StreetView.apk z emulatora na dysk twardy stacji roboczej.
3. Skonfiguruj urządzenie AVD dla wersji interfejsu Google API, z którego zamierzasz
korzystać.
4. Wykonaj polecenie adb w punkcie 2.
in stall
St reetView. apk na pliku . apk skopiowanym
628
Android 2. Tworzenie aplikacji
Aplikacja StreetView powinna zostać zainstalowana na emulatorze, dzięki czemu nasza przykła dowa aplikacja zadziała. Po uruchomieniu świeżo zmodyfikowanej aplikacji demonstrującej mapy wykonajmy zbli żenie pozwalające na ujrzenie ulic miasta. Kliknijmy przycisk Ulica, aby zostały na niebie sko zaznaczone ulice obsługiwane przez aplikację StreetView (na przykład zdjęcia tych ulic są zamieszczone w bazie danych Google). Dotknijmy teraz jednej z ulic, a zostanie wywołana metoda onTap( ) klasy ClickReceiver, która z kolei skontaktuje za pomocą intencji aktyw ność aplikacji Street View z dotkniętą lokacją. Jeżeli dotkniemy obszar mapy nieobsługiwany przez aplikację Street View, pojawi się pusty ekran tej aplikacji wraz z komunikatem typu „nieprawidłowa panorama". Oznacza to, że serwer Google nie może odnaleźć zdjęć znaj dujących się w pobliżu wybranego miejsca. Kliknijmy przycisk cofania, aby wrócić do apli kacji obsługującej mapy, i sprawdźmy inną lokację. Jeżeli zajrzymy do okna LogCat, zauwa żymy, że zostały zapisane w nim współrzędne geograficzne dotkniętej lokacji. Zwróćmy uwagę, że obiekt GeoPoint definiuje szerokość i długość geograficzną danymi typu int, natomiast identyfikator URI aplikacji Street View wymaga typu floa t . W naszej przykładowej aplikacji zdecydowaliśmy się n a wysyłanie intencji zawierającej współrzędne geograficzne dotkniętej lokacji do aktywności aplikacji StreetView. Możemy so bie jednak wyobrazić również inne możliwości. Jeżeli mamy szerokość i długość geograficzną lokacji, możemy wykorzystać obiekt Geocoder do identyfikacji jej okolicy. Nic nie stoi na przeszkodzie, aby użyć informacji o lokacji do nawigacji zakręt po zakręcie. Istnieje możli wość zmierzenia odległości wskazanej lokacji od naszego bieżącego położenia. Możemy na wet zachować dane lokacji do późniejszego użytku.
Gesty Gesty są specjalnym przypadkiem zdarzenia dotyku. Generalnie gestem nazywamy przy gotowany wcześniej ruch po ekranie dotykowym, na którego wykonanie aplikacja jest przy gotowana. Jeżeli użytkownik wykona taki gest podczas korzystania z aplikacji, zostaną wy wołane odpowiednie algorytmy w zależności od interpretacji tego gestu przez aplikację. Do gestów wymagana jest nakładka rozpoznające je i przekazująca dalej do aktywności. Stoso wanie gestów może mieć na celu uproszczenie interfejsu użytkownika poprzez usunięcie przycisków i innych kontrolek na rzecz ruchów palcami oraz rysowania kształtów. Pozwa laj
A • Rysunek 1 6.4. Ikona aplikacji Gestures B uilder
Rozdział 1 6 Ekrany dotykowe •
629
Zostaniemy przeniesieni do niemal pustego ekranu. Kliknijmy przycisk Add. Zostaniemy poproszeni o wprowadzenie nazwy. Nadana nazwa zostanie powiązana z gestem, który za chwilę zarejestrujemy. Będzie ona używana w kodzie w odniesieniu do tego gestu i w pew nym sensie posłuży nam jako nazwa polecenia. Gdy użytkownik wykona zakodowany gest, jego nazwa zostanie przekazana do metod po to, aby aplikacja potrafiła przetworzyć żądanie użytkownika. Nazwa powinna być rzeczownikiem, na przykład „spiral" albo „checkmark", ewentualnie może brzmieć jak polecenie, na przykład „fetch" albo „stop''. Naszym pierw szym gestem będzie zaznaczenie, zatem wpiszmy w polu Name nazwę checkmark. Narysujmy teraz w pustej przestrzeni duży znak zaznaczenia. Jeżeli jesteśmy niezadowoleni z rezultatu, możemy spróbować ponownie. Stary gest zniknie w momencie rozpoczęcia rysowania no wego. Gdy już będziemy zadowoleni z wyniku, kliknijmy przycisk Done. Powinien pojawić się ekran podobny do zaprezentowanego na rysunku 16.5.
�!ffi} iJ 8:37 PM
Rysunek 16.5. Gest zaznaczenia zapisany na karcie pamięci
Zwróćmy uwagę, że możemy narysować różne rodzaje gestów zaznaczania i wszystkie nazwać
checkma rk. Zarejestrujmy przynajmniej jeszcze jeden taki gest i nazwijmy go chec kma rk;
powinien w jakiś sposób różnić się od pierwotnego gestu, chociażby rozmiarem. Dodajmy również za pomocą przycisku Add gesture inne gesty, posiadające odmienne nazwy. Każdo razowe wciśnięcie przycisku Done powoduje dodanie kolejnego gestu do biblioteki. Możemy spróbować narysować gest wielodotykowy poprzez narysowanie dwoma palcami równole głych linii imitujących znak równości. W Androidzie 2.0 funkcja ta nie działa i zostanie na rysowana tylko jedna linia. Być może w następnych wersjach będą obsługiwane gesty wielo dotykowe - to znaczy gesty wymagające co najmniej dwóch palców. Każdy gest posiada nazwę i składa się z gestów właściwych (ang. stroke). Gestem właściwym jest sekwencja dotyku rozpoczynająca się od dotknięcia palcem ekranu, a kończąca na jego oderwaniu od wyświetlacza. Jak już wiemy, sekwencja dotyku jest tworzona przez obiekty MotionEvent. v\I analogiczny sposób gest właściwy jest tworzony przez punkty gestu. Gesty
630 Android 2. Tworzenie aplikacji są przechov.rywane w magazynie gestów. Biblioteka gestów zawierana jeden taki magazyn. W Androidzie wszystkie te klasy można v.rykorzystać w kodzie. Na rysunku 16.6 został przedstawiony schemat powiązań pomiędzy poszczególnymi klasami gestów. Gesturelibrary
GestureStore
Gest
Gest
GestureStroke
GestureStroke
GesturePoint
GesturePoint
Gest
(
GestureStroke
)
GesturePoint
Rysunek 1 6.6. Struktura klas g estów Chociaż do utw'orzenia gestu nie możemy używać funkcji wielodotykow'ości, istnieje sposób zamieszczenia wielu gestów właściwych w obrębie jednego gestu. Na przykład: aby stworzyć gest oznaczający literę E, potrzebujemy co najmniej dwóch gestów właściwych; jeden gest właściwy może definiować górny, boczny i dolny odcinek litery, a drugi gest właściwy po służy do narysowania jej środkowego odcinka. Możemy także najpierw narysować za po mocą jednego gestu właściwego pionową linię w literze E, a następne trzy gesty właściwe wykorzystać do narysowania trzech linii poziomych. Istnieją różne metody narysowania li tery E i na szczęście mamy możliwość zapisania ich wszystkich w bibliotece gestów. Zareje strujmy gest odpowiedzialny za literę E na kilka różnych sposobów, ponieważ użytkownicy mogą korzystać z odmiennych technik rysowania tej litery, a chcemy, żeby aplikacja rozpo znała ten gest bez względu na jego wykonanie. Rysunek 16.7 przedstawia różne sposoby re jestrowania litery E. Ut\.vorzenie gestu składającego się z wielu gestów właści'>V)'Ch może stanowić nie lada wy zwanie na emulatorze. Jak już wspomnieliśmy, możemy ponownie narysować nowy gest na geście starym, który zostanie usunięty. Skąd więc Android wie, kiedy rysujemy gest od nowa, a kiedy dodajemy jedynie nowy gest właściwy do istniejącego gestu? Android stosuje w tym celu wartość atrybutu FadeOffset, która jest podawana w milisekundach i jeśli po jej przekro czeniu zaczniemy rysować gest, Android uzna, cały proces należy przeprowadzić od począt ku. Domyślna wartość tego atrybutu wynosi 420 milisekund. Oznacza to, że jeśli podczas rysowania gestu uniesiemy palec na ponad 420 milisekund, system uzna, że skończyliśmy rysowanie tego gestu, i zostanie on zapamiętany w takim stanie. W rzeczywistym urządzeniu ta ki czas może wystarczyć do rozpoczęcia rysowania kolejnego gestu właściwego. W przypad ku emulatora może nie być tak dobrze. Wszystko zależy od szybkości stacji roboczej.
Rozdział 1 6 • Ekrany dotykowe IQ\ll!IJUi 3:36 PM
'i]iJ!iii) Q 3:38 PM
631
fcl!UJt;l 3:40 PM
Rysunek 16.7. Różne sposoby rejestrowa nia lite ry „E" Jeżeli aplikacja Gestures Builder stwarza problemy z akceptacją gestu składającego się z wielu ge stów właściwych, możemy stworzyć własną wersję tej aplikacji i zmodyfikować domyślną wartość atrybutu FadeOffset. GestureBuilder jest jedną z przykładowych aplikacji umiesz czonych w katalogu środowiska Android SDK, dokładniej w podkatalogu platforms/android-2.01 samples/GestureBuilder. Możemy utworzyć nowy projekt w środowisku Eclipse za pomocą opcji Create project from existing sample, a następnie wybrać aplikację GestureBuilder z rozwija nego menu. Przechodzimy następnie w projekcie do pliku /res/layout/create_gesture.xml i do dajemy atrybut and ro id : fadeOffset=" 1000" do elementu Gestu reOve rlayView. Wartość atrybutu fadeOffset zostanie powiększona do 1 sekundy (1000 milisekund). Możemy jednak wstawić tu dowolną wartość. Poszukajmy miejsca, w którym są przechowywane gesty. Wiadomość typu Toast w aplikacji Gestures Builder informuje nas, że gesty są zapisywane w katalogu /sdcard/gestures. Skorzy stajmy z perspektywy File Explorer w środowisku Eclipse lub z powłoki adb, aby odnaleźć folder /sdcard na emulatorze. Znajdziemy w nim plik gestures. Z"'rróćmy uwagę na jego niewielki rozmiar. Jest to plik binarny, zatem nie ma możliwości, aby go ręcznie edytować. Jeśli chcemy modyfikować jego zawartość, musimy otworzyć aplikację Gestures Builder. W trak cie tworzenia aplikacji obsługującej gesty musimy skopiować plik gestures do jej katalogu /res/raw. Dokonujemy tego za pomocą funkcji File Capy, znajdującej się w perspektywie File Explorer, lub za pomocą polecenia adb pull, co pozwala na skopiowanie pliku na dysk twardy, a stamtąd do projektu. Mamy nie tylko możliwość dodawania nowych gestów za pomocą aplikacji Gestures Builder - możemy za pomocą długiego kliknięcia wywołać menu istniejącego gestu. Możemy dzięki znajdującym się tu opcjom zmienić nazwę gestu lub go usunąć. Nie można ponownie reje strować zachowanego gestu, więc jeśli nam się nie podoba, musimy go usunąć i utworzyć nowy. Czasami pojawia się potrzeba zarejestrowania różnych odmian danego gestu i nadania im takiej samej nazwy. Nazwa gestu nie musi być niepowtarzalna, chociaż zalecane jest, aby identycznie nazwane gesty były do siebie podobne. Ma to wpływ na zróżnicowane metody wprowadzania gestów przez użytkowników. Na przykład możemy utworzyć gesty dla kilku różnych zaznaczeń i nadać im taką samą nazwę („checkmark"). Jeżeli użytkownik wykona w naszej aplikacji gest zaznaczenia, aplikacja otrzyma nazwę takiego gestu, gdy będzie on przypominał któryś z gestów zachowanych w bibliotece.
632 Android 2. Tworzenie aplikacji Teraz stworzymy przykładową aplikację obsługującą nasz nowy plik gestures. Utwórzmy nowy projekt Android w środovvisku Eclipse. Listing 16.14 zawiera zarówno plik XML układu graficznego, jak i kod rava aktywności. listing 16.14. Plik układu graficznego oraz kod Java aplikacji wykrywającej gesty
cand roid . ge s t u re . Ge s t u reOverlayView and roid : id="@+id/gestu reOverlay" and roid : layout_widt h="fill_parent" a n d roid : layout_heig ht="fill_parent" and roid : ge s t u reStrokeType= "multiple" and roid : fadeOffset= " lOOO" />
import import import import import impo r t import import import import import
j av a . ut i l . A r raylist; android . a p p . Activity; android . gestu re . Ge s t u r e ; android . ge s t u re . Ge s t u relibraries ; android . ge s t u r e . Gesturelibrary; android . gestu re. Gestu reOve rlayVie1� ; a n d roid . gesture . P rediction ; android . ge s t u r e . GestureOve rlayView . OnGestu rePerformedlistene r ; android . o s . Bundle; a n d roid . ut il . Lo g ; android .widget .Toa st ;
public class MainActivity extends Activity implements OnGestu rePerformedlistener
{
li
private static final S t ring TAG = "Wykrywanie gestów" ; GestureLibrary gestu reLib = n u l l ; @Ove rride public void onCreate( Bundle savedinstanceState) { s u pe r . o n C reate( savedlnstanceStat e ) ; setCont entView ( R . layout.mai n ) ;
gestureLib
=
GestureLibrariesfromRawResource(this, R.raw.gestures);
g e s t u relib = Gesturelibrarie s . fromFile ( " / s d c a rd/gestu res " ) ; if ( ! gesturelib. load ( ) ) { Toa s t . ma keText ( t h i s , "Nie można wczytać pliku / s d c a rd/ges t u re s " , Toast . LENGTH_SHORT) . s h ow ( ) ; finish ( ) ;
}
Rozdział 1 6 • Ekrany dotykowe
633
li Przyjrzyjmy się bibliotece gestów, z którq będziemy pracować Log . v ( TAG , " Fu n k c j e biblioteki : " ) ; Log . v (TAG, " Styl ułożenia : " + gestu reLib . getOrientationStyle ( ) ) ; Log . v (TAG, "Typ s e kwenc j i : " + g e s t u reLib . getSequenceType ( ) ) ; f o r ( String gestu reName : g e s t u reLib . getGe s t u reEnt ries ( ) ) { Log . v ( TAG , "Dla gestu " + gestu reName ) ; int i l; f o r ( Gesture gesture : gestu reLib . getGes t u re s ( gestu reName) ) { Log . v (TAG, " " + i + " · ID : " + gestu re . getID ( ) ) ; L o g . v (TAG, " + i + " : Ilość gestóv1 właś ciwych : " + ges t u re . getStrokesCount ( ) ) ; Log. v ( TAG, • • + i + · : Długość gestu właściwego : " + gestu re. getLength ( ) ) ; =
"
i++ ; }
}
Gestu reOverlayView gestu reView = (Gestu reOverlayView) findViewBy i d ( R . id . gestu reOve rlay ) ; gestu reView. addOnGestu rePe rfo rmed listene r ( this ) ;
} @Over ride public void onGestu rePerfo rmed ( G e s t u reOverlayView view, Gesture gesture) { ArrayList predictions = gestureLib. recognize ( g e s t u re ) ; if ( p redictio n s . s i z e ( ) > O ) { Prediction prediction = ( P rediction) predictio ns . get ( O ) ; if ( p rediction . s core > 1 . 0 ) { Toast . makeTex t ( t h i s , p rediction . name, Toast. LENGTH_SHORT) . s how ( ) ; f o r ( int i=O; i
Postępowanie zgodnie z zasadami Umowa dotycząca dystrybucji produktów przez ich dewelopera za pośrednictwem usługi Android Market zawiera mnóstwo reguł postępowania. Być może przed akceptacją umowy należałoby dać ją do przejrzenia radcy prawnemu. W tym podrozdziale omówimy kilka kwestii wartych odnotowania. • Należy być programistą o nieposzlakowanej reputacji, aby korzystać ze sklepu
Android Market. Oznacza to, że musimy przejść przez omówiony powyżej proces rejestracji, zaakceptować umowę i przestrzegać jej zasad. Skutkiem złamania zasad może być zablokowanie dostępu do sklepu i usunięcie z niego naszych aplikacji. • Możemy dystrybuować zarówno produkty bezpłatnie, jak i za opłatą. Umowa
dopuszcza obie możliwości. W przypadku sprzedaży produktów musimy korzystać z takiego procesora płatności, jak na przykład Google Checkout. W momencie wydania platformy Android 2.0 jedyną formą pobierania opłat pochodzących ze
Rozdział 1 7 • Korzystanie ze sklepu Android Market
639
sklepu Android Market była właśnie usługa Google Checkout. Obecnie użytkownicy mogą po prostu obciążyć swój rachunek telefoniczny podczas pobierania opłat, co zostało ogłoszone przez firmę T-Mobile 4 listopada 2009 roku. Nie ma możliwości zastosowania usługi PayPal lub dowolnego innego procesora płatności do sprzedaży produktów w sklepie. W przyszłości takie podejście może jednak ulec zmianie. Od płatnych aplikacji będzie pobierana opłata transakcyjna oraz e>ventualnie opłata dla operatora sieci komórkowej, które zostaną odjęte od ceny produktu. W czerwcu 20 1 0 roku opłata transakcyjna wynosi 30%, jeśli więc produkt kosztuje 10 dolarów, firma Google otrzymuje 3 dolary, a my 7 (pod warunkiem że nie dochodzą do tego opłaty dla operatora). • Do programisty należy obowiązek odprowadzania zebranego podatku do właści·wych organów podatkowych. Podczas ustanawiania konta handlowego definiujemy odpowiednie wysokości podatku dotyczące zakupu produktu przez osoby znajdujące się w innych rejonach. Usługa Google Checkout pobierze odpowiedni podatek w zależności od tego, w jaki sposób została skonfigurowana. Opłata ta zostanie wysłana do nas, a my musimy ją odprowadzić do Urzędu Skarbowego. Dodatkowe informacje na temat sprzedaży usług i licencji w Polsce można znaleźć pod adresem
http:!/www.vat.pl!sprzedaz_licencji_ebooki_oprogramowanie_fi rma_ w_in tern ecie_ 1 052.php. •
Istnieje możliwość umieszczania darmowej wersji demonstracyjnej aplikacji, posiadającej opcję odblokowania wszystkich funkcji pełnej wersji po uiszczeniu opłaty; opłata musi być jednak uiszczona poprzez autoryzowany procesor płatności. Nie możemy skierować użytkowników darmowej aplikacji do innego procesora płatności w celu pobrania opłaty za odblokowanie wszystkich funkcji programu. Zabronione jest również pobieranie opłaty za prenumerowanie aplikacji dystrybuowanych poprzez sklep Android Market. Opłaty za usługi zasadniczo są nawet zalecane, gdyż pomagają chronić aplikację przed piractwem oraz zwiększają przepływ· pieniędzy twórców oprogramowania. Oznacza to jednak, że nie możemy sprzedawać takiej wersji aplikacji poprzez Android Market. Być może zostanie to zmienione w przyszłości.
• Zwrot płatności przysługuje użytkownikom, którzy w przeciągu 48 godzin od nabycia usunęli daną aplikację. Można to również uznać za swoistą wersję „próbną" aplikacji. Pobranie darmowej aplikacji, a następnie uaktualnienie jej do pełnej, płatnej wersji może być nieco kłopotliwe, a zwrot płatności stanowi prostsze rozwiązanie (w dalszej części rozdziału pokażemy także inne sposoby). Zwroty płatności nie dotyczą osób, które miary możliwość podglądu produktu. Do tej kategorii zasobów należą dzwonki i tapety. Programista musi zapewnić odpowiedni<) pomoc techniczną użytkownikom produktu. Jeżeli takie wsparcie nie zostanie zapewnione, użytkownicy mogą żądać zwrotu płatności, którymi zostanie obciążony (wraz z kosztami manipulacyjnymi) wydawca. • Użytkownicy posiadają nieograniczoną ilość ponownych instalacji aplikacji pobranych ze sklepu Android Market. W przypadku przywrócenia ustawień fabrycznych urządzenia użytkownik będzie mógł pobrać wszelkie zakupione aplikacji bez konieczności ponownego płacenia za nie. • Wydawca gwarantuje ochronę prywatności i praw użytkowników. Dotyczy to ochrony (na przykład zabezpieczania) wszelkich danych, które mogą zostać zebrane podczas użytkowania aplikacji . Dopuszczalna jest zmiana warunków dotyczących ochrony
640
Android 2. Tworzenie aplikacji
danych użytkownika jedynie w przypadku wyświetlenia odpowiedniej umowy i zaakceptowania jej przez użytkownika. • Nasza aplikacja nie może konkurować ze sklepem Android Market. Firma Google nie pozwala na umieszczanie aplikacji umożliwiających sprzedaż innych produktów poza sklepem Android Market, co jest równoznaczne ominięciu procesora płatności. Nie oznacza to wcale, że nie możemy sprzedawać tej aplikacji innymi kanałami, lecz że ta aplikacja, przebywając w sklepie Android Market, nie może umożliwiać sprzedaży produktów znajdujących się poza tą usługą. •
Umieszczone produkty zostaną objęte systemem oceniania. Oceny mogą być przydzielane w oparciu o udzielaną pomoc techniczną, szybkość instalacji i odinstalowania, szybkość zwrotu kosztów oraz (lub) jako tak zwana ocena ogólna dewelopera (ang. Developer Composite Score). Jest ona szacowana na podstawie ocen y.,rystawianych dla poprzednich aplikacji i może wpływać na ocenę przyszłych produktów. Z tego powodu istotne jest, aby wydawać aplikacje wysokiej jakości, nawet jeśli są darmowe. Wspomnieliśmy wcześniej, że jednym ze sposobów przetestowania aplikacji jest 48-godzinny termin zwrotu płatności. Chcemy tutaj zauważyć, że duża ilość zwrotów płatności może negatywnie wpłynąć na ocenę ogólną, dlatego nie powinniśmy dążyć do tego typu implementacji darmowej wersji próbnej.
• Poprzez sprzedaż aplikacji w sklepie Android Market udzielamy użytkownikowi „niewyłącznej i ogólnoświatowej licencji na odtwarzanie, prezentowanie i użytkowanie Produktu w Urządzeniu". Jednak nic się nie stanie, jeśli napiszemy własne warunki umowy licencyjnej (ang. End User License Agreement EULA), zastępujące pov.ryższe stwierdzenie. Należy taką umowę umieścić na własnej stronie WWW lub zagwarantować jaltiś inny sposób jawnego zaprezentowania jej użytkownikom. -
• Firma Google wymaga przestrzegania cech marki Android. Do tych cech zaliczają się ograniczenia w wykorzystywaniu słowa „Android", jak również ikony robota, znaku towarowego oraz kroju pisma. Informacje na ten temat znajdziemy pod adresem
http://www.android.com/branding.html.
Konsola programisty Z poziomu konsoli programisty możemy zakupić urządzenie ADP (ang.
Android Developer
telefon programisty systemu Android), skonfigurować konto handlowe (naliczać opłatę za aplikację), wstawiać aplikacje oraz przeglądać informacje na temat wstawionych przez nas programów. Możemy także edytować takie szczegóły konta, jak imię i nazwisko pro gramisty, adres e-mail, adres strony WWW oraz numer telefonu.
Phone
-
Android Developer Phone to specjalne urządzenie, zaprojektowane przede wszystkim dla programistów aplikacji na system Android. Jest to profesjonalny telefon, posiadający od blokowane wszystkie funkcje i niezależny od operatorów telefonii komórkowej. Akceptuje wszystkie rodzaje kart SIM, a w jego wyposażeniu znajduje się karta pamięci 1 GB, aparat fotograficzny, wysuwana klawiatura i system GPS. Poprzez odblokowane funkcje mamy na myśli możliwość wykonania każdej czynności w urządzeniu, łącznie z instalacją nowej wer sji oprogramowania sprzętowego i systemu Android, nie tylko aplikacji. Chociaż perspek tywa zakupu takiego urządzenia służącego do testowania aplikacji może być kusząca, jeżeli tylko do tego będzie służyło, lepiej zaopatrzyć się w telefon dostarczany przez operatora telefonii komórkowej. Oprogramowanie dostępne w urządzeniu ADP składa się wyłącznie z podstawo-
Rozdział 1 7 • Korzystanie ze sklepu Android Market
641
wych aplikacji, podczas gdy telefony operatorów zawierają bardziej złożone programy i funkcje. W celu wyszukiviania błędów w aplikacji możemy uzyskać dostęp do standardowego telefo nu ze stacji roboczej tak samo, jak w przypadku urządzenia ADP. Jeżeli natomiast chcemy przetestować nowe wersje oprogramowania sprzętowego lub samą platformę Android, po winniśmy zaopatrzyć się w urządzenie ADP. W przeciwnym wypadku całkowicie wystarczy standardowy telefon. Jeśli nie skonfigurujemy konta handlowego za pomocą usługi Google Checkout, nie bę dziemy mogli pobierać opłat za produkty umieszczone w sklepie Android Market. Ustano wienie takiego konta nie jest skomplikowaną czynnością. Wystarczy kliknąć odpowiednie łącze w konsoli programisty, wypełnić formularz aplikacji, zaakceptować warunki korzystania z usługi i to będzie wszystko. Należy mieć przygotowany pod ręką numer karty kredytowej. Informacje o karcie kredytowej są >vprowadzane w celu uiszczenia zwrotu zaptaty w przypadku, gdy na koncie Google Checkout nie ma wystarczającej ilości funduszy. Możemy także wprowa dzić dane konta bankowego, dzięki czemu zyski ze sprzedaży aplikacji będą przenoszone do banku. Zwróćmy uwagę, że usługa Google Checkout obsługuje nie tylko sklep Android Market. Nie powinniśmy więc zdziwić się, jeśli pojawi się informacja o opłacie transakcyjnej za sprzedaż pochodzącą spoza sklepu Android Market. Wspomniana opłata transakcyjna wynosząca 30% ceny aplikacji jest obliczona dla sklepu Android Market. Istnieją również dodatkowe opłaty transakcyjne dla sprzedaży przeprowadzanych poza tą witryną, niezależ ne od opłaty wspomnianej powyżej. Prawdopodobnie najczęściej stosowanymi funkcjami konsoli programisty będą umieszczanie i monitorowanie aplikacji (w dalszej części rozdziału zajmiemy się procesem umieszczania aplikacji w sklepie). W kwestii monitoringu otrzymujemy narzędzia pozwalające obserwo wać całkowitą ilość pobrań aplikacji oraz ilość użytkowników posiadających zainstalowaną apli kację. Widoczna jest ogólna ocena programu w zakresie od O do 5 gwiazdek, a także ilość osób, które wystawiły ocenę. Z poziomu konsoli programisty możemy ponownie opublikować aplikację - na przykład jej aktualizację - lub ją wycofać ze sklepu. Ta ostatnia czynność nie usuwa aplikacji z urządzeń, a nawet nie musi jej usuwać z serwerów Google, dotyczy to zwłaszcza płatnych aplikacji. Użytkownik, który zapłacił za aplikacj9, a następnie ją odin stalował, lecz nie zażądał zwrotu kosztów, ma prawo do jej ponownego zainstalowania, na wet jeśli została ona wycofana z obiegu. Program przestaje być naprawdę dostępny dla użyt kowników jedynie w przypadku złamania zasad firmy Google. Programiści posiadają dostęp do komentarzy w taki sam sposób, jak użytkownicy - po przez sklep Android Market. Dla własnego dobra powinniśmy jak najczęściej czytać ko mentarze dotyczące naszej aplikacji, aby móc szybko rozwiązywać zauważone problemy.
Przygotowanie aplikacji do sprzedaży Należy przeprowadzić kilka czynności pośrednich, od utworzenia kodu aplikacji do umiesz czenia jej w sklepie Android Market. Poświęcimy teraz uwagę omówieniu tych etapów.
Testowanie kompatybilności wobec różnych urządzeń Bardzo istotne jest, aby przy lawinowej produkcji coraz to nowych urządzeń obsługujących system Android, z których każde zawiera potencjalnie odmienną konfigurację sprzętową, stworzona przez nas aplikacja została przetestowana pod kątem działania na docelowych telefonach. W idealnym przypadku powinniśmy uzyskać dostęp do każdego rodzaju telefonu,
642 Android 2. Tworzenie aplikacji który chcemy przetestować. Kolejnym dobrym rozwiązaniem jest skonfigurowanie urządze11 AVD dla każdego rodzaju telefonu poprzez utworzenie odpowiedniej konfiguracji sprzęto wej, a następnie uruchomienie i przetestowanie takiego urządzenia na emulatorze. Zestaw Android SDK posiada klasę Inst rumentation oraz program UI/Application Exerciser Monkey, usprawniające proces testowania. Wymienione narzędzia umożliwiają automatyzację testo wania, nie musimy więc spędzać wieczności na powtarzaniu tych samych czynności. Przed rozpoczęciem testowania powinniśmy usunąć zbędne artefakty z kodu oraz /res. Chcemy przecież, aby aplikacja była jak najmniejsza oraz jak najszybsza przy minimalnym zużyciu pam1ęc1.
Obsługa różnych rozmiarów ekranu W momencie wydania środowiska Android SDK 1 .6 programiści zaczęli borykać się z no wymi rozmiarami v.ryśv.rietlaczy; aby uruchomić aplikację na nowym, mniejszym ekranie, należy ustanowić swoisty element jako element potomny węzła w pliku AndroidMa n if e s t . xml. Bez wprowadzenia tego znacznika definiującego obsługę małych ekranów przez aplikację, nie byłaby ona widoczna w sklepie Android Market dla urządzeń posiadających niewielkie wyświetlacze. Oznacza to oczywiście, że aplikacja musi zostać skompilowana wobec środowiska Android SDK co najmniej w wersji 1.6. Jeśli chcemy, aby program działał w urządzeniach obsługujących starsze wersje zestawu Android SDK, musimy się upewnić, że nie będzie korzystał z żadnego interfejsu API wprowadzonego w wersji 1.6 lub nowszej oprogramowania. Należy następnie przetestować aplikację zarówno pod kątem urządze11. AVD imitujących starsze telefony, jak i reprezentujące telefony nowsze. W celu obsługi różnych rozmiarów ekranu prawdopodobnie będziemy musieli utworzyć alternatywne pliki zasobów w podkatalogu /res. Na przykład w przypadku dodatkowej ob sługi niewielkich v.ryświetlaczy oprócz plików znajdujących się w katalogu /res/layout trzeba będzie umieścić odpowiedniki tych plików w katalogu /res/layout-small. Nie oznacza to, że musimy tworzyć również odpowiedniki tych plików w katalogach /res/layout-large i /res/layout normal, gdyż jeśli Android nie znajdzie takiego specyficznego katalogu, jak na przykład /res/layout-large, wykorzysta zasoby dostępne w katalogu /res/layout. Pamiętajmy również, że możemy tworzyć kombinacje kwalifikatorów dla plików zasobów; na przykład katalog /res/layout-small-land będzie zawierał układy graficzne dla małych ekranów zorientowa nych w trybie poziomym. Obsługa małych wyświetlaczy oznacza prawdopodobnie również utworzenie alternatywnych wersji obiektów rysowanych, takich jak ikony. W przypadku tych obiektów może również zaistnieć potrzeba utworzenia alternatywnych katalogów za sobów, odpowiadających rozdzielczości ekranu oraz jego rozmiarowi.
Przygotowanie pliku AndroidManifest.xml do umieszczenia w sklepie Android Market Nasz plik AndroidManifest.xml prawdopodobnie musi zostać troszeczkę zmodyfikowany przed umieszczeniem go w sklepie Android Market. Domyślnie narzędzia ADT środowiska Eclipse nie wstawiają dodatkowych atrybutów do znacznika wewnątrz tego pliku. Zanim jednak wyślemy go do sklepu, musimy upewnić się, że wewnątrz wspomnia nego znacznika znajdzie się atrybut android : icon. Standardowo narzędzia ADT umiesz czają ten atrybut wewnątrz węzła , co jest również wymagane. W rzeczywistości aplikacja posiadająca atrybut android : icon wyłącznie wewnątrz węzła będzie bezproblemowo działała w urządzeniach oraz na emulatorze, jednak podczas umieszczania
Rozdział 1 7 • Korzystanie ze sklepu Android Market
643
aplikacji na serwerze usługa Android Market przeszukuje znacznik pod kątem informacji o ikonie. Rozwiązaniem tego problemu jest zwyczajne skopiowanie tego atrybutu z węzła do węzła . Przesyłanie aplikacji zostaje przerwane również w przypadku, gdy nazwa zastosowanego pakietu rozpoczyna się od segmentów com.google, com.android, android lub com.example, marny jednak nadzieję, że nie zostały one zastoso wane w aplikacjach Czytelników. Istnieje również wiele innych kwestii związanych z kompatybilnością, które należy wziąć pod uwagę podczas testowania aplikacji na różnych konfiguracjach sprzętovvych. Niektóre urządzenia posiadają aparat fotograficzny, inne nie posiadają klawiatury fizycznej, jeszcze inne mają manipulator kulkowy zamiast klawiszy nawigacyjnych. W razie potrzeby zasto sujmy znaczniki i < u s es - featu re> w pliku And roidMani fest . xml do zdefiniowania wymaga11 sprzętowych i prograrnov,rych naszej aplikacji. Android Market wymusi te wymagania i uniemożliwi pobranie naszej aplikacji urządzeniom nieposiadają cym odpowiedniej konfiguracji. Zwróćmy uwagę, że mamy tu do czynienia z innymi znacz nikami niż pliku And roidMani fest . xml. Chociaż urządzenie użytkownika może być wyposażone w aparat fotograficzny, nie jes t wcale powiedziane, że użytkownik chce przydzielić naszej aplikacji dostęp do tego aparatu. Jednocześnie zadek.tarowanie uprawnie nia aplikacji do korzystania z aparatu wcale nie musi oznaczać, że obecność tej funkcji jest wymagana przez aplikację. W większości przypadków będziemy umieszczać obydwa znacz niki w pliku And roidMani fes t . xml, aby określić wymóg obecności aparatu fotograficznego oraz uprawnienie korzystania z aparatu w razie potrzeby.
Lokalizacja aplikacji W przypadku korzystania z aplikacji w innych krajach możemy rozważyć proces jej lokali zacji. Z technicznego punktu widzenia jest to względnie prosta czynność. Znalezienie osoby odpowiedzialnej za przeprowadzenie tego procesu to zupełnie inna sprawa. Z technicznego punktu widzenia tworzymy po prostu kolejny folder w katalogu /res - na przykład /res/va lues-fr przechowujący francuską wersję pliku s t rings . xml. Bierzemy nasz bieżący plik st ring s xml, tłumaczymy wartości typu string na nowy język i zachowujemy tak zmo dyfikowany plik w nowym folderze zasobów, nie zmieniając jednocześnie nazwy tego pliku. W przypadku innych rodzajów zasobów - na przykład obiektów rysowanych lub menu stosujemy dokładnie taką samą technikę. Obrazy i kolory mogą lepiej spełniać swoje zada nie, jeśl i będą przystosowane do odmien nych krajów i kultur. Z tego właśnie powodu nie warto stosować prawdziv.'Ych nazw zasobów kolorów. W internetowej dokumentacji doty czącej kolorów często można natrafić na taki zapis: .
#fOO Oznacza on, że w naszym kodzie lub innym pliku zasobów odnosimy się do koloru za po mocą jego rzeczywistej nazwy, w naszym przypadku jest to s o l i d_ red. Aby dostosować kolor do innego kraju lub kultury, najlepiej stosować nazwy kolorów typu a c c en t_ c o l o rl lub a l e rt_ color. W Wielkiej Brytanii odpowiedniejszy może być kolor czerwony, podczas gdy w Hiszpanii swoje zadanie będzie lepiej spełniał jakiś odcień żółci. Ponieważ nazwa alert_ col o r nie zdradza stosowanego przez nas koloru, jego zmiana nie jest już tak bardzo dezorientująca. Jednocześnie możemy zaprojektować przyjemny schemat kolorystyczny, zawierający barwy bazowe i ich odcienie przy jednoczesnej pewności, że właściwe kolory zostały użyte we właściwych miejscach.
644
Android 2. Tworzenie aplikacji
W niektórych krajach opcje menu powinny zostać zmienione poprzez dodanie lub usunię cie pewnych elementów, ewentualnie można wprowadzić inną organizację menu, w zależ ności od miejsca stosowania aplikacji. Jeżeli natrafimy na taką sytuację, prawdopodobnie będzie lepiej, jeśli umieścimy wszystkie tekstowe ciągi znaków w pliku
strings.xml
lub in
nym pliku przechowywanym w podkatalogu /res/values, a następnie będziemy wszędzie od nosić się do tych zasobów za pomocą ich identyfikatorów. Zmniejszymy w ten sposób znacznie niebezpieczeństwo pominięcia tłumaczenia tekstu w jakimś zapomnianym pliku zasobów. Tłumaczenie jest wtedy zatem ograniczone do plików znajdujących się w podka talogu /res/values.
Przygotowanie ikony aplikacji Osoby kupuj ąc e oraz użytkownicy będą wyraźnie widzieć ikonę oraz etykietę aplikacji za
równo w sklepie Android Market, jak i, po jej pobraniu, w urządzeniu. Powinniśmy poświę cić szczególną uwagę utworzeniu
jak najlepszej
ikony i etykiety naszej aplikacji. W razie
potrzeby możemy je również zlokalizować. Nie zapominajmy również o ewentualnym do stosowaniu rozmiarów ikony do
rozmiarów
ekranu. Przyglądajmy się dziełom innych wy
dawców, szczególnie zaś aplikacjom należącym do tej samej kategorii, co nasza. Chcemy, aby nasza aplikacj a była widoczna, musimy więc unikać wtapiania się w tłum. Jednocześnie musimy pamiętać, że ikona i etykieta aplikacji muszą harmonizować z ikonami innych apli
użytkownika. Użytkownik nie może aplikacji, której ikona wskazuje zupełnie inną funkcję.
kacji zainstalowanych w urządzeniu nad przeznaczeniem
zastanawiać
się
Problemy z płatnymi aplikacjami Darmowe aplikacje zazwyczaj nie s ą celem
ataków piratów, inaczej ma się jednak sprawa aplikację, mamy inne zmartwienia na głowie.
z płatnymi programami. Jeżeli ustalamy cenę za Czy tworzymy dwie
wersje
tej samej aplikacji - darmową i płatną - wymagające oddzielnej
A może korzystamy ze wspólnego kodu bazowego i stosujemy o tym, że została uiszczona zapłata za program? Bez względu na zastosowane rozwiązanie, w jaki sposób chronimy aplikację przed jej kopiowaniem i in stalowaniem na innych urządzeniach przez nieupoważnione do tego osoby? Z powodu ograniczonych zabezpieczeń stosO\vanych w telefonach oraz zdolności pewnych osób do obsługi i zarządzania nimi?
jakąś technikę informującą nas
omijania tych środków ostrożności niezwykle ciężko jest zarządzać niezawodnymi techno
logiami ochrony przed kopiowaniem. Jednym z rozwiązań utrzymywania pojedynczego ko du bazowego, pozwalającego na jest wykorzystanie możliwości
umieszczenie oddzielnych trybów klasy PackageManager:
bezpłatnego i płatnego,
this . getPackageManage r ( ) . checkSignatu res ( mainAppPkg , keyPkg) Metoda ta porównuje sygnatury
dwóch
pakietów oraz zwraca wartość P a c k a g e Mana g e r
'+SI GNATURE_MATCH, jeżeli obydwa pakiety istnieją i są identyczne. Nazwy pakietów
się
różnić
dla każdej aplikacji współistniejącej
.
muszą
w sklepie Android Market, ale nic nie szkodzi.
W naszym kodzie, jeżel i chcemy zadecydować o udostępnieniu wszystkich funkcji aplikacji,
wprowadzić nazwę pakietu aplikacji głównej lub aplikacji od zakupi aplikacj ę odblokowującą i pobierze ją na urządzenie, główna aplikacja otrzyma dopasowanie sygnatury i odblokuje dodatkowe funkcje. Nieco mniej przyjemnym rozwiązaniem wykorzystu jącym pojedynczy kod bazowy jest wprowadzenie systemów oznaczających kolejne wersje kodu źródłowego, które skonfigurują odpowiednie współdzielenie wspólnych elementów, oraz stworzenie skryptów zajmujących się tworzeniem darmowych i płatnych wersji aplikacj i. możemy wywołać tę metodę i
blokowującej. Ten drugi
program ustanawiamy następnie jako płatny. Jeżeli użytkownik
Rozdział 1 7 • Korzystanie ze sklepu Android Market
645
Kierowanie użytkowników z powrotem do sklepu W systemie Android wprowadzono nowy schemat identyfikatorów URI, ułatwiający wy
szukiwanie aplikacji w sklepie Android Market: ma rket : //. Na przykład: jeśli chcemy skie rować użytkowników do sklepu w celu znalezienia potrzebnego składnika lub dokupienia dodatkowej aplikacji odblokowującej nowe funkcje naszej aplikacji, powinniśmy wprowa dzić następujący kod, w którym w miejsce MY_PACKAGE_NAME wprowadzamy nazwę naszego pakietu: Intent intent = new I ntent ( Intent .ACTION_VIEW, U ri . pa rse ( "ma rket : //search?ą=pname : MY_PACKAGE_NAME" ) ) ; startActivity ( inten t ) ;
Za pomocą tego kodu zostanie uruchomiona aplikacja Market, która przeniesie użytkowni ka do nazwy tego pakietu. Użytkownik może następnie pobrać lub zakupić aplikację. Zwróćmy uwagę, że powyższy schemat nie działa w normalnej przeglądarce WWW. Mo żemy wyszukiwać za pomocą nazwy pakietu (pname), a także szukać wydawcy za pomocą wyrażenia market : / /sea rch?q=pub: " Fname Lname" lub korzystając z dowolnego pola pu blicznego sklepu Android Market (nazwa aplikacji, nazwa wydawcy oraz opis aplikacji), wpisu jąc ma rket : / / s e a rch ?q=.
Przygotowanie pliku .apk do wysłania Aby przygotować naszą aplikację do wysłania - to znaczy utworzyć w tym celu plik .apk musimy postąpić zgodnie z poniższym algorytmem (został on szczegółowo omówiony w roz dziale 7., w podrozdziale „Podpisywanie wdrażanych aplikacji"):
1. Jeśli jeszcze tego nie zrobiłeś, utwórz certyfikat produktu, za pomocą którego nasza aplikacja zostanie podpisana.
2. Jeżeli nasza aplikacja wykorzystuje mapy, zamień klucz API MAP w pliku And roidMani fest . xml na klucz API MAP produktu. Jeśli tego nie zrobimy,
użytkownicy nie będą widzieli map.
3. Eksportuj aplikację poprzez kliknięcie prawym przyciskiem myszy na nazwie projektu w oknie Package explorer środowisku Eclipse, wybranie opcji Android Tools!Export Unsigned Application Package oraz dobranie odpowiedniej nazwy pliku. Warto nadać temu plikowi tymczasową nazwę, ponieważ po uruchomieniu aplikacji zipalign w punkcie 5. będziemy musieli wprowadzić nazwę pliku wyjściowego, która stanowić będzie nazwę naszego pliku . apk.
4. Uruchom aplikację jarsigner wobec naszego nowego pliku .apk, aby podpisać go za pomocą utworzonego w punkcie 1. certyfikatu produktu. 5. Uruchom aplikację zipalign wobec pliku .apk, aby dopasować nieskompresowane dane do granic pamięci, dzięki czemu uzyskamy lepszą wydajność po uruchomieniu aplikacji. To właśnie teraz wprowadzimy ostateczną nazwę pliku .apk naszej aplikacji.
Wysyła nie aplikacji Wysyłanie aplikacji jest prostym procesem, wymagającym jednak nieco przygotowań. Przed rozpoczęciem wysyłania musimy ustanowić kilka parametrów oraz podjąć pewne decyzje. Niniejszy podrozdział został poświęcony omówieniu tych przygotowai1 i decyzji. Gdy już
646
Android 2. Tworzenie aplikacji
będzie wszystko gotowe, przejdziemy do konsoli programisty i wybierzemy opcję Upload Application. Zostaniemy poproszeni o wprowadzenie wielu informacji dotyczących naszej aplikacji, usługa Market przebada aplikację oraz dostarczone dane i w końcu nasz program pojawi się w sklepie Android Market! W poprzednim podrozdziale omówiliśmy proces przygotowania pliku . apk do wysłania. Przyciągnięcie uwagi klientów wymaga z naszej strony szczypty marketingu. Musimy stworzyć dobry opis aplikacji oraz jej przeznaczenia, konieczne też będzie wykonanie zrzutów ekranu, aby użytkownicy wiedzieli, że nie kupują kota w worku. Jednym z pierwszych elementów, o jakie zostaniemy poproszeni podczas wysyłania aplika cji, są zrzuty ekranu. Najprostszym sposobem ich uzyskania jest zastosowanie narzędzia DDMS. Uruchamiamy środowisko Eclipse, następnie włączamy aplikację na emulatorze lub w rzeczywistym urządzeniu i przełączamy perspektywę na widoki DDMS i Device. We wnątrz widoku Device v.rybieramy urządzenie, na którym jest uruchomiona aplikacja, i kli kamy przycisk Screen Capture (symbolizuje go ikona małego obrazu umieszczona w prawym górnym rogu ekranu) lub wybieramy go z menu View. Jeżeli pojawi się taka możliwość, wy bierzmy 24-bitowy kolor. Android Market przekonwertuje zrzuty ekranu na skompresowane pliki JPEG; początkowa wartość 24 bitów przyniesie lepsze rezultaty niż początkowa war tość 8 bitów. Wybierzmy takie zrzuty, które przy ukazywaniu oryginalności aplikacji pre zentują jednocześnie jej funkcje. Możemy umieścić również grafikę promującą, jej rozmiar będzie jednak mniejszy od zrzutu ekranu. Chociaż taka grafika stanowi jedynie dodatek, warto ją również umieścić. Nigdy nie wiadomo, kiedy zostanie ona wyświetlona; bez niej nie będziemy pewni, co (jeżeli cokol wiek) pojawi się na jej miejscu. Android Market poprosi następnie o informację tekstową dotyczącą aplikacji, która będzie widoczna dla klientów, włącznie z tytułem, opisem oraz tekstem promującym. Możliwość wprowadzenia tekstu promującego stanie się dostępna jedynie po umieszczeniu grafiki promującej. Możemy zawrzeć tekst w wielu językach, gdyż nasza aplikacja będzie dostępna na całym świecie. Grafika wspomniana powyżej może być umieszczona w sklepie Android Market v.ryłącznie jednorazowo, zatem jeśli zrzuty ekranu wyglądają inaczej w różnych ustawieniach regionalnych, powinniśmy rozważyć umieszczenie ich w innym miejscu do stępnym dla klientów, na przykład na stronie domowej. Takie podejście może w przyszłości ulec zmianie. Jeśli napisaliśmy własną wersję umowy EULA, powinniśmy w opisie zamie ścić odnośnik do niej, dzięki czemu użytkownicy zapoznają się z jej treścią przed pobraniem aplikacji. Należy wziąć pod uwagę, że klienci prawdopodobnie będą chcieli wykorzystać funkcję wyszukiwania aplikacji, zatem najlepiej byłoby umieścić w tekście odpowiednie słowa kluczowe, dzięki czemu znacząco wzrośnie prawdopodobieństwo natrafienia na naszą aplikację przez osoby szukające zapewnianych przez nią funkcji. W końcu warto również umieścić krótki komentarz wraz z adresem e-mail na wypadek pojawienia się problemów z aplikacją. Bez tej prostej zachęty użytkownicy mogą częściej wystawiać negatywne opinie, a taka negatywna opinia naprawdę ogranicza możliwość rozwiązania problemu w porów naniu do wymiany informacji z użytkownikiem, który natrafił na jakiś błąd. Jedną z wad omówionego wcześniej mechanizmu wsparcia technicznego jest brak rozróż nienia pomiędzy wersjami aplikacji. Jeżeli wersja 1 . oprogramowania otrzymała negatywne opinie, a my wydaliśmy wersję 2 . pozbawioną wszystkich usterek poprzedniczki, recenzje wersji 1. nie znikają, a klienci nie wiedzą, której wersji dotyczą te opinie. Po v.rydaniu zaktu-
Rozdział 1 7 • Korzystanie ze sklepu Android Market
647
alizowanej aplikacji jej ocena (ilość gwiazdek) również nie zostaje wyzerowana. Nie zapo minajmy o tym podczas tworzenia hasła reklamowego albo rozważmy wydanie nowej wersji aplikacji j ako o ddzielnego programu. Jednym z naszych obowiązków podczas pisania opisu aplikacji jest ujawnienie wymaganych przez nią uprawnień. Mamy na myśli te same uprawnienia, które są definiowane za pomocą znaczników w p liku AndroidManifest.xml. Kiedy użytkownik pobiera aplikację na urządzenie, system sprawdza plik AndroidManifest.xml i przed zakończeniem instalacji pyta użytkownika o wszystkie zamieszczone weń uprawnienia. Równie dobrze możemy umieścić je w opisie aplikacji. W przeci wnym wypadku ryzykujemy otrzymanie negatywnych opinii od użytkowników zaskoczonych faktem, że aplikacj a wymaga upraw nień, których nie zamierzali jej przyznać. Nie wspominamy nawet o zwrotach kosztów, które zaniżają ogólną ocenę dewelopera. Podobnie jak w przypadku uprawnień, jeśli aplikacja wymaga określonego typu ekranu, aparatu fotograficznego lub jakiejś innej funkcji urzą dzenia, odpowiednie informacj e p owinny zostać zamieszczone w op isie programu. W trakcie wysyłania aplikacji musimy wybrać jej rodzaj i kategorię Ponieważ z upływem czasu wartości te ulegają zmianom, nie będziemy ich wymieniać, "''Ystarczy przejść do ekra nu Upload Application, żeby ujrzeć dostępne możliwości. .
Następnym etapem jest ustalenie ceny aplikacji. Domyślnie aplikacja jest darmowa, a żeby to zmienić, musimy posiadać skonfigurowane konto handlowe w usłudze Google Checkout. Wy bór odpowiedniej ceny dla aplikacji na pozór wcale nie je st taki łatv.ry, chyba że posiadamy wyjątkowo rozwinięty zmysł marketingowca, a nawet wtedy nietrudno o pomyłkę. Zbyt wysokie opłaty mogą odrzucać ludzi oraz o dczujemy skutki zwrotów kosztów dla ludzi, którzy uznali naszą aplikację za niewartą swojej ceny. Z kolei zbyt niskie ceny mogą rów nież odrzucić ludzi, którzy uznają, że nasza aplikacja jest niskobudżetowa. Android Market posiada op cję włą czenia ochrony przed kopiowaniem po wysłaniu aplika cji. Rozwiązanie to nieco bardziej obciąża pamięć urządzenia. Nie jest ono również nieza wodne i nie gwarantuje całkowitego zabezpieczenia aplikacji. Z tego powodu możemy wziąć pod uwagę alternatywne lub dodatkowe sposoby ochrony programu.
Jedną z ostatnich decyzji, jakie należy podjąć, jest wybór regionów geograficznych oraz ope ratorów telefonii komórkowej, dla których nasza aplikacja będzie widoczna. Poprzez wybór opcji All aplikacja będzie dostępna na całym świecie. Czasami jednak należy ograniczyć dystrybucję na dany region geograficzny lub operatora. W zależności od funkcji oferowanych przez aplikację wymagane jest nieraz ograniczenie ilości krajów dopuszczonych do obsługi aplikacji ze względu na konieczność przestrzegania prawa eksportowego Stanów Zjednoczo nych. Ograniczamy aplikację p od kątem operatorów, gdy posiada ona problemy z kompaty bilnością z urządzeniami lub zasadami danego operatora. Aby przejrzeć listę dostępnych opera torów, klikamy wybrany kraj, dzięki czemu zostanie wyświetlony spis dostęp nych operatorów w tym państwie. Zaznaczenie opcji All p owoduj e również, że wszelkie nowo dodane pa11stwa i ope ratorzy będą również widzieli naszą aplikację, bez żadnych działań z naszej strony. Chociaż nasz profil programisty zawiera informacje kontaktowe, możemy wprowadzić inne dane podczas wysyłania każdej aplikacji. Usługa Mark et prosi o wprowadzenie strony WVVW, adresu e-mail oraz numeru telefonu służących j ako in form acje kontaktowe związane z daną aplikacją. W celu zapewnienia obsługi klientów musimy wypełnić przynajmniej jedno z wymie nionych pól, nie jest konieczne jednak wprowadzenie danych do wszystkich trzech ele mentów.
648
Android 2. Tworzenie aplikacji
Po podjęciu wszystkich omówionych decyzji musimy naszej aplikacji nadać atest przestrzegania Polityki treści w usłudze Android Market dla programistów (nie jest zbyt rygorystyczna), a także drugi atest - umożliwiający eksportowanie programu poza granice Stanów Zjedno czonych. Wobec naszej aplikacji są stosowane prawa eksportowe Stanów Zjednoczonych, ponieważ serwery Google są umieszczone w tym kraju, nawet w przypadku aplikacji utwo rzonych w innym państwie oraz nawet jeśli zarówno my, jak i klienci znajdujemy się poza USA. Nie zapominajmy, że zawsze możemy publikować aplikację innymi kanałami. W końcu możemy opublikować naszą aplikację poprzez wciśnięcie przycisku Publish. Android Mar ket sprawdzi publikowany program, zwłaszcza pod kątem daty wygaśnięcia certyfikatu aplikacji. Jeżeli cały proces przebiegnie pomyślnie, nasz kod stanie się dostępny do pobrania przez innych użytkowników. Gratulacje!
Wrażenia użytkownika korzystającego ze sklepu Android Ma rket Oficjalnie Android Market jest dostępny wyłącznie z poziomu urządzenia, co oznacza za pewnianie użytkownikowi wrażeń poprzez telefon. Wydawcy nie mają żadnej kontroli nad działaniem sklepu, mogą co najwyżej wstawić ciekawy tekst i zrzuty ekranu do opisu umieszcza nej aplikacji. Zatem pod tym względem o wrażenia użytkownika musi zadbać sama firma Google. Za pomocą urządzenia użytkownik może przeszukiwać bazę danych przy zastoso waniu słowa kluczowego, przeglądać najczęściej pobierane aplikacje (płatne oraz darmowe), zalecane programy lub nowości oraz przeglądać całe kategorie. Po znalezieniu odpowiedniej aplikacji użytkownik może ją od razu zaznaczyć, co spowoduje wyświetlenie ekranu szcze gółów programu, na którym można wybrać opcję jego instalacji lub kupna. Wybór opcji kupna przeniesie użytkownika do usługi Google Checkout, gdzie zostanie przeprowadzona finansowa część transakcji. Pobrana aplikacja pojawia się wśród pozostałych programów. Witryna Android Market posiada możliwość przeglądania pobranych aplikacji w katalogu
My Downloads. Obszar ten zawiera zarówno wszystkie zainstalowane aplikacje, jak i zaku pione programy, nawet po ich usunięciu (najczęściej zostają usunięte wyłącznie z powodu braku miejsca). Oznacza to, że możemy usunąć płatną aplikację i ponownie ją zainstalować w innym terminie bez ponoszenia dodatkowych kosztów. Oczywiście, po wybraniu opcji zwrotu kosztów dana aplikacji nie będzie v.ryświetlana w katalogu My Downloads. Również darmowe aplikacje nie będą pokazywane w tym folderze po ich wykasowaniu. Lista aplikacji wnieszczonych w tym katalogu jest powiązana z kontem Google obsługiwanym przez urządze nie. Oznacza to, że możemy zmienić urządzenie bez strachu o utratę zakupionych aplikacji. Pamiętajmy jednak: ponieważ możemy posiadać kilka tożsamości na serwerach Google, po brać zakupione wcześniej aplikacje możemy jedynie z konta, na którym za nie zapłaciliśmy. Podczas przeglądania aplikacji w katalogu My Downloads wszelkie nowe wersje programów będą zaznaczone oraz gotowe do uaktualnienia. Android Market filtruje aplikacje dostępne dla określonych użytkowników. Proces ten jest 1 przeprowadzany na wiele sposobów. W niektórych krajach dostępne są wyłącznie bezpłat ne wersje programów z powodu różnorakich wymogów prawa handlowego nieodpowiada jących firmie Google w danym państwie. Firma Google bardzo stara się przenvyciężyć te przeszkody, aby płatne aplikacje były dostępne na całym świecie. Do tego czasu użytkownicy
1
Na przykład w Polsce
-
przyp.
tłum.
Rozdział 1 7 • Korzystanie ze sklepu Android Market
649
w niektórych krajach mogą cieszyć się jedynie darmowymi aplikacjami. Osoby posiadające urządzenia zawierające starszą wersję systemu Android nie posiadają dostępu do aplikacji obsłu giwanych przez nowsze wersje zestawu Android SDK. Użytkownicy korzystający z urządzei'i niespełniających v,rymagań sprzętowych danej aplikacji (definiowanych w pliku And roid '+Mani fest . xml) również nie będą widzieć takich programów. Na przykład aplikacje nieob sługujące małych wyświetlaczy nie będą widziane w sklepie Android Market przez użyt kowników posiadających urządzenia z takimi właśnie ekranami. Taka filtracja została wprowa dzona głównie po to, aby uchronić użytkowników od pobrania aplikacji, która nie będzie działać w ich telefonach. Podczas kupowania aplikacji w innych krajach na etapie transakcji może nastąpić przewalu towanie, co zazwyczaj oznacza dodatkową opłatę. Aplikacje są naprawdę kupowane z kraju sprzedawcy za pośrednictwem usługi Google Checkout. Sklep Android Market wyświetla przybliżoną kwotę, lecz w rzeczywistości opłaty mogą być nieco inne w zależności od mo mentu przeprowadzenia transakcji oraz od u7.ytych procesorów płatności. Osoby kupujące mogą zauważyć, że ich konto zostaje obciążone symboliczną opłatą (na przykład
1 dolar)
w czasie przeprowadzania transakcji. Firma Google upewnia się w ten sposób, że v.rprowa dzone informacje o płatności są poprawne, a wspomniana opłata w rzeczywistości nie zo stanie uiszczona. Mniej oficjalnie można uzyskać dostęp do sklepu Android Market, nie korzystając wyłącznie z urządzenia. Istnieje w internecie kilka stron, które stanowią obraz witryny Android Market. Użytkownicy mogą tam wyszukiwać aplikacje, przeglądać kategorie oraz czytać informacje na temat programów bez konieczności posiadania urządzenia. Rozwiązanie to działa na za sadzie filtrowania przez Android Market konfiguracji urządzenia oraz regionu geograficz nego, w którym przebywamy. Jednakże nie ma możliwości pobrania w ten sposób aplikacji na urządzenie. Usługa Android Market jeszcze nie posiada w ofercie możliwości pobierania aplikacji z poziomu stron WWW, zatem jeśli nawet dowiemy się o istnieniu danej aplikacji poprzez jedną z takich witryn, a aplikacja nie będzie widziana przez urządzenie, nie będzie możliwe jej pobranie. Przykładami takich lustrzanych witryn są i
http://www.androlib.com
http://www.androidzoom.com oraz. http://www.cyrket.com.
W dodatku istnieją sklepy z aplikacjami zupełnie niezależne od witryny Android Market. Przykładami mogą być
androidgear.com.
h ttp://www.andappstore.com, http://slideme.org
i
h ttp://www.
Możemy na tych stronach wyszukiwać, przeglądać, czytać informacje
o aplikacjach, a także pobierać je zarówno z poziomu telefonu, jak i przeglądarki. Witryny te nie muszą przestrzegać zasad firmy Google, wliczając w to również opłaty transakcyjne oraz formy płatności. Te oddzielne sklepy akceptują takie procesory płatności, jak na przykład PayPal. Zostaje w nich również pominięte ograniczenie dotyczące rejonu geograficznego i kon figuracji sprzętowej. Niektóre ze sklepów oferują instalację klienta Android, inne zaś doko nują jego wstępnej instalacji w urządzeniu. Użytkownicy mogą po prostu otworzyć przeglą darkę VfWW w urządzeniu i wyszukać w internecie daną aplikację; po jej zapisaniu na karcie
pamięci system będzie wiedział, jak ją zainstalować. Pobrany plik
.apk jest
traktowany jak
aplikacja systemu Android. Jeżeli klikniemy w przeglądarce historię pobranych plików (nie należy mylić jej z omówionym wcześniej katalogiem
My Downloads),
zostaniemy zapytani,
czy chcemy zainstalować pobraną aplikację. Taka swoboda oznacza, że możemy ustanowić własne metody pobierania aplikacji przez użytkowników, nawet z domowej strony WWW, oraz zastosować wybrane przez nas metody płatności. Nadal jednak musimy pobierać po datek od sprzedaży i zwracać go Urzędowi Skarbowemu.
650
Android 2. Tworzenie aplikacji
Chociaż takie alternatywne metody dystrybuowania aplikacji nie są ograniczane przez zasa dy firmy Google, nie oferują również tak wysokiego poziomu ochrony kupca, jak to ma miejsce w sklepie Android Market. Istnieje możliwość, że użytkownik zakupi z takiego źró dła aplikację, która nie będzie działać na jego urządzeniu. Osoba kupująca musi również zająć się tworzeniem kopii zapasowych aplikacji na wypadek jej przypadkowej utraty lub w mo mencie zmiany urządzenia. Pamiętajmy, że firma Google nie zabrania wydawcom jedno czesnej sprzedaży aplikacji w wielu różnych sklepach i w usłudze Android Market. Zatem w celu zwiększenia efektywności powinniśmy wziąć pod uwagę wszystkie możliwości.
Podsumowanie Teraz możemy już podbić cały świat naszymi aplikacjami utworzonymi pod system An droid! Pokazaliśmy, w jaki sposób należy przygotować siebie oraz swoją aplikację, jak należy ją opublikować oraz umożliwić użytkownikom jej "''Yszukanie, pobranie i użytkowanie.
ROZDZIAŁ
18 Perspektywy i zasoby
Ostatni rozdział niniejszej książki poświęcimy tematyce aktualnego rozwoju Androida oraz perspektywom jego wpływu na przyszłość rynku urządzeń mobilnych. Aby zrozumieć, jakim sukcesem okazał się Android w 2009 roku, przejrzymy najpierw listę producentów, którzy zdecydowali się tworzyć urządzenia oparte na tym systemie. Prześledzimy rozwój możliwości tych urządzeń na przykła dzie skrótowego omówienia parametrów technicznych telefonów T-Mobile Gl (wydane w 2008 roku), Motorola Droid (2009 rok) oraz wydanego niedawno przez firmę Google urządzenia NexusOne (początek 2010 roku). Rok 2009 ob fitował również w wysyp sklepów oferujących aplikacje na system Android. Wymienimy niektóre z tych sklepów internetowych. Aby zrozumieć, jaki wynik osiągnie Android w przyszłości, przeanalizujemy inne mobilne systemy operacyjne i porównamy je z systemem Android, rozdział zaś zakończymy zamieszczeniem zbioru przydatnych zasobów poświęconych procesowi projektowania w systemie Android oraz stron WWW zawierających nowości ze świata Androida.
Obecny stan Androida Rok 2009 był dla Androida bardzo pomyślny. Pod koniec 2008 roku było do stępne na rynku tylko jedno urządzenie obsługujące ten system T-Mobile G l . Na początku 2009 roku pojawiły się informacje o możliwości wydania do końca roku urządzeń tworzonych aż przez osiemnastu producentów. Brzmiało to zbyt ambitnie. Na początku 2009 roku istniało również zaledwie kilka tysię cy aplikacji napisanych dla systemu Android. Pod koniec wspomnianego roku rzeczywiście okazalo się, że ponad 1 8 producentów sprzedaje urządzenia oparte na tym systemie, nie tylko telefony komórkowe, lecz także netbooki, a nawet czyt niki elektroniczne. W tym czasie pojawiło się już ponad 20 OOO aplikacji, wli czając w to również zawartość innych sklepów. Ciężko uświadczyć tydzień bez jednego lub dwóch artykułów w magazynie „Wall Street Journal" dotyczących Androida. -
Przyjrzyjmy się zakresowi urządze11 dostępnych na rynku lub zapowiadanych.
652
Android 2. Tworzenie aplikacji
Producenci urządzeń mobilnych bazujących na systemie Android Pod koniec 2009 roku lista producentów tworzących urządzenia obsługujące system Android składała się z następujących firm:
• Archos (tablet internetowy), • Barnes and Noble (czytnik książek Nook), • Entourage (dwustronny czytnik elektroniczny, przypominający nieco książkę), • General Mobile, • HTC (twórca modeli Magie, Hero, Droid Eris, Click/Tattoo), • HKC (bliźniacza platforma firmy HTC), • Huawei, • Lenovo, •
LG Group,
•
Motorola,
• Qigi, • Samsung, • Gini, • Ericsson, • Acer, • Skytone (netbook Alpha-680), • ICD Vega (tablet). Większość wymienionych firm to producenci telefonów komórkowych, niektórzy zaś pro jektują netbooki (Acer) oraz czytniki książek (Barnes and Noble, Entourage). Jak widać, mamy tutaj w bród urządzeń obsługujących system Android. Przyjrzyjmy się niektórym urządzeniom, aby wiedzieć, jakich parametrów po nich oczekiwać. Rozpoczniemy od chyba najpopularniejszego z wymienionych telefonów, Motorola Droid.
Motorola Droid Telefon Motorola Droid został zaopatrzony w procesor ARM Cortex o taktowaniu miesz czącym się w zakresie od 256 MHz do 550 MHz (według specyfikacji firmy Motorola). Możemy natrafić na modele zawierające od 256 MB do 512 MB pamięci RAM. Zawiera po jemnościowy ekran dotykowy WVGA oparty na wyświetlaczu LCD TIT (ang. Thin Film Tran sistor tranzystor cienkowarstwowy). Urządzenie Droid zawiera kamerę o rozdzielczości 5 megapikseli (dla porównania ·wyspecjalizowane, cyfrowe aparaty fotograficzne posia dają 12-megapikselowe matryce). Telefon ten posiada również funkcje GPS, Wifi i Bluetooth oraz został wyposażony w port microUSB 2.0. Ponadto dostępny jest również akcelerometr, czujniki zbliżeniowe oraz sensory oświetlenia tła. Znajdziemy tu również klawiaturę fizyczną. Pełna specyfikacja urządzenia została umieszczona pod adresem http://www.motorola.com/ -
Consumers/US-EN/Consumer-Product-and-Services/Mobile-Phones/ci.Motorola-DROID-US-EN.alt
Rozdział 1 8 • Perspektywy i zasoby
653
T-Mobile G 1 Porównajmy telefon Motorola Droid z modelem T-Mobile Gl, wydanym pod koniec 2008 roku. Posiada on procesor Qualcomm taktowany zegarem 528 MHz. Zamontowane zostaly w nim 192 MB pamięci RAM. Uświadczymy tu płaski ekran dotykowy HVGA (320x480) oparty na technologii TFT. Matryca aparatu fotograficznego posiada rozdzielczość 3,2 mi liona pikseli. Łączność ze światem zapewniają w nim interfejsy Bluetooth, Wifi oraz system GPS. Również w nim znajdziemy wejście micro USB 2.0. Poniższa strona prezentuje konfigurację sprzętową telefonu:
http://www.htc.com/www/product/gllspecification.html
Nexus One Skoro zajmujemy się omawianiem niektórych urządze11, warto zwrócić uwagę na najnowszy produkt firmy Google. Na początku 2010 roku v.rydała ona odblokowany telefon noszący nazwę Nexus One (http://www.google.com/phone), posiadający następującą specyfikację. Po siada procesor z serii Snapdragon (Qualcomm QSD 8250) taktowany zegarem 1 GHz (po równajmy to do 600 MHz najnowszego iPhone'a lub 550 MHz telefonu Droid). Posiada wy świetlacz WVGA w rozdzielczości 800x480 pikseli, a zamiast technologii TFT została w nim wprowadzona technologia AMOLED (ang. Active Matrix Organie Light Emitting Diodes aktywna matryca organicznych diod emitujących światło). Technologia OLED pozwala na uzyskanie jaśniejszego i wyraźniejszego obrazu oraz lżejszych wyświetlaczy. Aparat fotogra ficzny został zaopatrzony w matrycę o rozdzielczości 5 megapikseli. Dostępna jest wyłącznie wirtualna klawiatura oraz funkcje GSM i Wifi. Cena odblokowanego telefonu wynosi 529 dolarów (w Polsce ok. 2500 zł), natomiast będzie ona mniejsza po podpisaniu umowy abo namentowej z operatorem sieci komórkowej 1• Jeśli chcemy przejrzeć pełną specyfikację tech niczną produktu, znajdziemy ją tutaj:
http:l/www.google.com/phone/static/en_US-nexusone_tech_specs.html Jak widać, twórcy systemu Android nie mieli powodu do narzekań w 2009 roku. Spójrzmy teraz na kolejny rozwijający się aspekt Androida - sklepy z aplikacjami.
Sklepy z aplikacjami na system Android Kolejną rozwijającą się dziedziną związaną z platformą Android są internetowe sklepy sprzedające aplikacje opracowane na ten system. W momencie pisania tej książki lista takich witryn jest następująca: •
Android Market (firmy Google),
• Slideme, • Andappstore, • Mplayit, • Androlib, •
Storeoid (firmy General Mobile),
• Androidgear, •
1
Handango.
Na chwilę obecną żaden z operatorów działających w Polsce nie posiada tego smartfonu w ofercie -
przyp. tłum.
654
Android 2. Tworzenie aplikacji
Wiele osób zastanawia się zapewne, dlaczego istnieje tyle sklepów. W zależności od źródła podawane są inne powody takiego stanu rzeczy. Wymienimy niektóre. Na dzień dzisiejszy usługa Google Market nie jest dostępna we wszystkich krajach oraz nie obsługuje wszyst kich metod płatności. Z drugiej strony niektóre urządzenia mogą posiadać specjalną kon strukcję, na przykład obsługę dwóch kart SIM, wymagającą odrębnego traktowania (tak jest w przypadku modelu DSTLl firmy General Mobile). Może zaistnieć potrzeba dostosowania niektórych aplikacji do takich niestandardowych urządzeń. Kolejny powód jest taki, że nie którzy producenci (na przykład Motorola) lub operatorzy chcą dopasowywać aplikacje do własnych potrzeb. Niektórzy krytykują również nie najlepszy system przeglądania aplikacji w usłudze Google Market. Bez względu na powody sklepy z aplikacjami pojawiają się jak grzyby po deszczu. Omówmy pokrótce ofertę każdego z v.rymienionych pov.ryżej sklepów internetowych: Android Market (http://www.android.com/market!), utrzymyw·any przez firmę Google, jest oczywiście oficjalnym sklepem z oprogramowaniem na platformę Android i jest udoskona lany z każdą nową wersją systemu. W rozdziale poświęconym tej usłudze zostały 'vymie nione jej wady i zalety. Slideme (http://slideme.org/applications) został założony w 2008 roku w Seattle. Za cel sklep Slideme postawił sobie rynek niszmvy, metody płatności oraz aplikacje, których użytkowni cy nie mogą znaleźć tradycyjnymi sposobami. Innym zadaniem tego sklepu jest ogólno światowa sprzedaż aplikacji.
W sklepie Andappstore (http://andappstore.com/) dominuje jedno założenie: obsługa za równo zatwierdzonych, jak i niezatwierdzonych urządzeń korzystających z systemu Android. Sama witryna jest jednak bardzo prosta. W porównaniu do niej nawet wcześniej omówiony sklep Slideme wydaje się posiadać złożoną strukturę. mplayit (http://mplayit.com) jest witryną, w której możemy przeglądać nie tylko aplikacje na system Android, lecz także na iPhone' a oraz Blackberry. Jej trzon stanowi silnik serwisu Facebook, co daje nadzieję na usprawnione przeglądanie oraz kupowanie aplikacji. Strona ta jest wykonana całkiem profesjonalnie i istnieje możliwość, że zbierze się wokół niej spo łeczność użytkowników kupujących aplikacje. Strona ta należy do kategorii usług katalogowych, stanowiących punkty danych leżącego u ich podstaw sklepu Android Market.
Podobnie jak wspomniany sklep mplayit, witryna AndroLib (http://www.androlib.com!) jest kolejną usługą katalogową dla aplikacji znajdujących się w sklepie Android Store. Jednak w przeciwieństwie do mplayit, sklep ten nie jest oparty na silniku Facebook. Sklep Storeoid został utworzony przez firmę General Mobile (http://www.generalmobile.com!), która wprowadziła na rynek urządzenia obsługiwane przez system Android, zawierające dwie karty SIM. W celu zapewnienia zoptymalizowanych aplikacji na swoje urządzenia, zwłasz cza w Europie, firma General Mobile otwiera swój sklep Storeoid. Związany z witryną PocketGear sklep Androidgear (http://www.androidgear.com) wydaje się być rzetelną firmą, posiadającą dobrą, sieciową platformę do sprzedaży aplikacji. Strona Androidgear została stworzona do sprzedaży aplikacji na system Android. Jest ona bardzo dobrze zorganizowana. Sklep Handango (http://www.handango.com) jest doświadczonym punktem sprzedaży, po siadającym w ofercie wszystkie rodzaje gadżetów i aplikacji na urządzenia mobilne. Oferują produkty nie tylko na platformę Android, lecz także na inne smartfony.
Rozdział 1 8 • Perspektywy i zasoby
655
Pod koniec 2009 roku łączna ilość wszystkich aplikacji znajdujących się w tych sklepach wynosiła około 20 OOO egzem p la r zy. Jak widać, Android szybko zmierza w stronę masy krytycznej. Dla porównania, iPhone w tym samym czasie posiadał około 80 OOO aplikacji .
Perspektywy Androida Platforma Android przekroczyła wszelkie oczekiwania w 2009 ro ku. Sprawdźmy konkuren cję tego systemu poprzez przejrzen ie udziału różnych producentów na rynku urządzeń mo bilnych. P rzekonamy się także o elastyczności Androida, który obsługuj e szybko zmieniają ce się standardy, takie jak HTML 5. Poniższa analiza da nam odpowiedź, co tak właściwie zadecydowało o sukcesie Androida oraz czy ta dobra passa będzie kontyn uo wana \V przyszłości.
Krótkie podsumowanie mobilnych systemów operacyjnych Od ponad dziesięciu lat mówi się, że praca na urządzeniach przenośn ych j est tech n ologią danego roku. W rzeczyvvistości rozwój tej tech nologii podążał znacznie bardziej stopniową trajektorią. Dop iero powstanie iPhone'a zrewolucjonizowało rynek sprzętu i oprogramo wania mobilnego. Lata 2009 i 2010 będą najprawdopodobniej stanowiły okres przyśpieszo nego wzrostu mocy obliczeniowej i wyrazistości obrazu. Już teraz są zapowiadane urządzenia posiadające procesor taktowany zegarem 1 GHz oraz pamięć RAM w zakresie od 1 GB do 4 GB, które zostaną wydane w bieżącym, 20 1 0 roku. Podczas pracy na systemie Android nasuwa się oczywiste pytanie o rodzaje innych mobil nych systemów operacyjnych oraz ich różni ce w stosunku do omawianej przez całą książkę platformy. Liczba różnych rodzajów systemów operacyjnych przeznaczonych na u rządzenia przenośne nieustannie rośnie. Żeby V.')'lllienić najważniejsze: Symbian, Blackberry (firma RIM Research In Motion), iPhone OS (Apple), Moblin (Intel), Maemo (Nokia), Windows Mobile (Microsoft), Palm OS, BREW ( Qualcom m ) czy JavaFx Mobile/Sava}e (system operacyj ny firmy Sun oparty na środowisku Java). Przyjrzyjmy się podstawowej charakterystyce każdego z wymienionych systemów operacyjnych. -
Blackberry OS (http://na.blackberry.com/eng!developersl) firmy RIM (ang. Research in Motion) jest bardzo popularnym systemem operacyjnym z powodu nastawienia urządzeń obsługuj ą cych tę platformę na rynek korp oracyjny. Jest to bardzo wyspecjalizowany system operacyj ny. Wśród różnych programowalnych i n ter fej sów systemu Blackberry można rnaleźć rów nież obsługę j ęzyka Java poprzez środowisko Java ME. Na uwagę zasługuje również obsługa profilu MIDP (ang. Mobile Information Device Profile) oraz protokołu WAP (ang. Wireless
Application Protocol protokół aplikacji bezprzewodowych; służy on przede wszystkim do obsługi dostępu do mobilnej si ec i WWW z poziomu środowiska telefonu komórkowego). -
Symbian (http://www.symbian.org/) jest jednym ze starszych systemów operacyjnych, zapro jektowanym dla procesorów ARM i dostosowanym do rynku urządzeń mobilnych. Obecnie system ten został przejęty przez firmę Nokia, która wykorzystuje go w tańszych modelach tele fo nów . Kod źródłowy został ujawniony od momentu jego przejęcia przez firmę Nokia. PodstaWO\\'}'l11i językami programowania w tym systemie są C++ i Java (pop r zez środowisko Java ME).
656
Android 2. Tworzenie aplikacji
Moblin (http://moblin.org/about-moblin) jest oparty na systemie Linux oraz jest wspierany przez firmę Intel. Ta zoptymalizowana platforma linuksowa posiada j awny kod źródłowy i jest przeznaczona do obsługi netbooków oraz przenośnych urządzeń internetowych. Śro dowisko programistyczne składa się zasadniczo ze zbioru linuksowych narzędzi projekto wych. Firma Intel stworzyła zestaw bibliotek Moblin Core opartych na języku C, które zo stały zoptymalizowane pod kątem urządzeń mobilnych. Mogą one zostać wywołane w wielu językach wysokiego poziomu. Interfejs użytkownika w systemie Moblin bazuje na posiadają cym jawny kod źródłowy rozwiązaniu zwanym clutter (http://clutter-project.org), które samo stanowi otulinę środowiska OpenGL. System Moblin jest próbą firmy Intel zebrania wszystkich rozwiązań stosowanych w telefonach komórkowych pod jednym kloszem systemu Linux. Podobnie jak Moblin, system Maemo (http://maemo.org/developmentl) bazuje na kodzie Linuk sa. Maemo pozwala na tworzenie aplikacji w językach C, C++ oraz Python za pomocą wtyczek środowiska Eclipse. Znajduje w nim zastosowanie narzędzie scratchbox (http://www.scratch box.org), pozwalające na kompilację krzyżową projektowanych programów, które następnie mogą być uruchamiane na innych procesorach, na przykład ARM. Platforma Maemo zo stała stworzona przez firmę Nokia dla swoich najnowocześniejszych telefonów. Stosowane są tu także takie narzędzia, jak GIK+, popularna aplikacja okienkowa dla systemów Linux.
iPhone OS (http://developer.apple.com/iphone) został stworzony w oparciu o system OS X, lecz dodatkowo jeszcze zoptymalizowano go do działania na urządzeniach przenośnych. Programiści iPhone'a v.rykorzystują narzędzia projektowe środowiska XCode bazujące na systemie Mac OS X. Wśród tych narzędzi można znaleźć środowisko IDE, projektanta in terfejsu użytkownika, narzędzie do wyszukiwania błędów, i tak dalej. Do tworzenia syste mów Mac OS X i iPhone jest wykorzystywany ten sam zestaw narzędzi. Podstawowa struk tura tego środowiska nosi nazwę Cocoa, a jej >vyspecjalizowana wersja została zoptymalizowana pod kątem zdarzeń dotyku i mobilności - jest to Cocoa touch. Struktura Cocoa została za projektowana w obiektowym języku Objective-c, stanowiącym nadzbiór języka C. Uznaje się również, że jest to język dynamiczny, podobnie jak na przykład języki AppleScript, Python czy Ruby. Można pisać aplikacje w tych językach skryptowych poprzez interfejs Cocoa Bridge. Firma Palm (http://www.palm.com/us) wydaje się łączyć w swoich urządzeniach system operacyjny Palm z systemem Windows Mobile. W przypadku tego systemu można korzy stać ze środowiska Codewarrior lub z pakietu programistycznego składającego się ze środowiska Eclipse i kompilatora gcc. System Palm zawiera emulatory współpracujące ze środowiskiem Eclipse. Możliwe jest także programowanie w tym systemie za pomocą standardowego śro dowiska Java ME.
Windows Mobile (http://msdn.microsoft.com/en-us/windowsmobile!default.aspx) przenosi doświadczenia znane z platform Windows na 1ynek urządzeń przenośnych. W procesie pro gramowania aplikacji na tę platformę wykorzystywane są te same narzędzia, na przykład Visual Studio oraz .NET. Każdy j ęzyk rozumiany przez środowisko .NET będzie akceptowany. Wśród licznego grona rozumianych języków znajduje się również C,i;. BREW ( https://brewmobileplatform.qualcomm.coml) jest platformą mobilną stworzoną przez firmę Qualcomm. Programowanie w tym systemie jest przeprowadzane poprzez śro dowisko Visual Studio lub Eclipse, w językach C albo C++. Docelową platformą programi styczną jest Windows, chociaż kod działa na urządzeniach posiadających procesor ARM. Proces projektowania może być również '"'Ykonany poprzez narzędzia oparte na języku Flash. System BREW obsługuje również kod Java poprzez środowisko Java ME.
Rozdział 1 8 • Perspektywy i zasoby
65 7
JavaFX Mobile (http:llwww.sun.com/software/javafx!mobile/index.jsp) jest odpowiedzią firmy Sun przypominającą technologię Silverlight firmy Microsoft, w której do stworzenia boga tego interfejsu Ul jest wykorzystywane podejście deklaracyjne. System JavaFX zadziała na każdej platformie obsługującej środov.risko JavaME. Kilka lat temu firma Sun wykupiła fir mę Savafe, producenta systemu operacyjnego opartego na języku Java dla urządzeń mobil nych. Doświadczenia tej firmy przerodziły się właśnie w system JavaFX Mobile. Ś rodowisko programistyczne w tym systemie jest oparte głównie na języku Java. Równie dobrze może to być środowisko Netbeans firmy Sun, jak i posiadające jawny kod źródłowy środowisko Eclipse. Wziąwszy pod uwagę te przeszłe oraz teraźniejsze rozwiązania, jakie są p rzewidyv.rania wobec Androida? Czy system ten posiada jakieś zalety pozwalające mu znaleźć swoje miej sce na rynku urządzeń mobilnych? Kto będzie stanowił dla niego największą konkurencję?
Porównanie Androida z innymi systemami operacyjnymi Zastanówmy się, jak każdy z uprzednio omóvvionych systemów operacyjnych jest przygo towany na następne miesiące. Przyszłość systemu Symbian jest dość niepewna, gdyż będąca jego sponsorem firma Nokia już teraz wykorzystuje w najlepszych modelach swoich telefonów system Maemo. System Blackberry wydaje się zbytnio zastrzeżony oraz zbyt zależny od środowiska Java ME, aby być vvykorzystywanym na szeroką skalę. Samo środowisko Java ME wpadło w sidła długotrwałe go, zagmatwanego procesu standaryzacji. Przynajmniej z naszej szybko przeprowadzonej analizy wynika, że nie będzie ono stanowiło dużej konkurencji na rynku urządzeń mobilnych. Oparty na Linuksie system Moblin jest nieprzewidywalnym graczem, gdyż jego orędowni kiem jest firma Intel zalecająca jego stosowanie w tabletach internetowych i urządzeniach mobilnych. Zależność tego systemu od Linuksa i jego narzędzi programistycznych sprawia, że nie budzi on zainteresowania wśród programistów przyzwyczajonych do środowisk firm Apple i Microsoft. Prawdopodobnie taka sama sytuacja będzie dotyczyć systemu Maemo. Obydwa te systemy nie są na tyle atrakcyjne, aby przyciągnąć tak dużą grupę społeczności programistów, jak w przypadku firm Apple i Windows. System Palm może podążyć inną ścieżką. Jego wydawca uznał, że bardziej opłaca się zapro jektowanie urządzenia, a nie systemu operacyjnego. Poprzez obsługę wielu systemów opera cyjnych na swoim urządzeniu może v.ryjść całkiem nieźle na ich wojnie. Wszystko wskazuje na to, że firma Palm będzie skłaniała się ku systemowi Windows Mobile. Patrząc z takiej per spektywy, całkiem możliwe okazuje się związanie tej firmy nawet z Androidem w bliżej nie określonej przyszłości. W ten sposób pozostają na rynku trzej naprawdę mocni zawodnicy. Windows Mobile, iPhone OS i Android. Windows Mobile, ponieważ firma Microsoft ma olbrzymie doświad czenie w sprzedaży swoich produktów twórcom urządzeń. Wspaniałe są narzędzia oparte na strukturze .NET, w tym również zestaw Silverlight. Chociaż obecnie uznawane są za po wolne i pełne niedoróbek, w miarę wzrostu mocy obliczeniowej urządzeń będą się stawały coraz szybsze i atrakcyjniejsze. Jedyne pytanie brzmi: czy jądro kodu systemu będzie zdolne do optymalizacji z szybkością niezbędną na rynku urządzeń mobilnych? Firma Apple rów nież posiada znakomity zestaw narzędzi. Jednak warunek wstępny w postaci uzależnienia od języka Objective-C i platformy Mac OS X może w dalszym ciągu ograniczać ilość poten cjalnych programistów, chociaż już teraz dostępne jest ponad 100 OOO aplikacji na iPhone' a. Krótko mówiąc, firma Apple ciągle będzie promować innowacyjność.
658
Android 2. Tworzenie aplikacji
W tej przestrzeni platforma Android posiada zarówno pewne zalety, jak i wady. W porów naniu do omawianych systemów operacyjnych Android stanowi jedną z najprostszych i wszechstronnych platform zawierających wszystkie niezbędne elementy w jednym pliku instalatora. Pierwsze próby programowania nie powinny odstraszać początkujących osób. Jednocześnie struktura Androida jest wystarczająco skomplikowana. Jednak w całokształcie zestaw narzędzi systemu Windows Mobile może być lepszy. Mimo to środowisko Java jest bardziej atrakcyjne i przyciąga większą grupę programistów do platformy Android. Poprzez ustanowienie Javy głównym językiem możemy natrafić na problemy z wydajnością, zwłasz cza w przypadku gier itd. Firma Apple wraz ze swoim jawnym zarządzaniem pamięcią może być pod tym względem lepiej przygotowana. Być może Android poradzi sobie kiedyś z tym problemem poprzez wprowadzenie innych języków. Ostatecznie wyścig ten mogą wygrać zawodnicy współpracujący z programistami, innowa cyjni oraz elastyczni. Skoro mowa o elastyczności, przyjrzyjmy się przykładowi i zastanówmy się, jak to określenie pasuje do Androida.
Obsługa technologii HTML 5 i co z niej wynika Podczas omawiania struktur programistycznych takich jak WPF (ang. Windows Presenta tion Network), Cocoa Touch (struktura interfejsu UI firmy Apple) lub Android, cały czas pomijamy jeden z najważniejszych, programowalnych wołów roboczych urządzenia. Mamy tu na myśli przeglądarkę. Dawno minęły czasy, gdy przeglądarka wyświetlała jedynie za wartość języka HTML Możliwość obsługi kodu JavaScript oraz modyfikowania modelu DOM wprowadziła nowy paradygmat programistyczny. Moda ta pokaże pazury dopiero wraz z wprowadzeniem obsługi języka HTML 5, obsługują cego następujące funkcje:
• wątki robocze (ang. Web workers), • element wideo, • kanwa (ang. canvas), • pamięci podręczne i bazy danych aplikacji, • geolokacja, •
dwukierunkowa komunikacja pomiędzy dokumentami (ang. cross-document messaging),
• modyfikowalna treść, • zdarzenia wysyłane przez serwery. Wątki robocze umożliwiają przeglądarce uruchomienie wielu wątków przetwarzających kod. Wcześniej można było to osiągnąć jedynie za pomocą elementów i f rame oraz technologii AJAX. Obecnie funkcja ta została wbudowana w przeglądarki. Jej zastosowanie zostało ułatwione poprzez wprowadzenie tych nowych obiektów języka JavaScript. Element wideo jest wykorzystywany do natywnego odtwarzania różnych formatów plików wideo w przeglądarce bez potrzeby instalowania takich wtyczek, jak Flash lub Silverlight. Element canvas służy do rysowania dowolnego obiektu, na przykład powierzchni, za po mocą języka skryptowego, w tym przypadku JavaScript. Posiadający j awny kod źródłowy projekt BeSpin stosował kanwy w procesie programowania stron WWW przetwarzanych w chmurze.
Rozdział 1 8 • Perspektywy i zasoby
659
Pamięć podręczna aplikacji pozwala na lokalne po,echowywanie takich elementów, jak wia domości e-mail itd, Funkcja geolokacji umożliwia zarówno określenie położenia geograficznego użytkownika, jak i wykorzystywanych przez niego adresów IP itp, Dzięki dwukierunkowej komunikacji pomiędzy dokumentami możliwe staje się bezpieczne współdzielenie danych pomiędzy dwoma dokumentami znajdującymi się w oddziel nych domenach. Koncepcja modyfikowalnej treści umożliwia użytkownikowi edycję części dokumentu HTML z poziomu przeglądarki. W ten sposób obsługa takich serwisów jak Wikipedia staje się jeszcze bardziej bezpośrednia. W ostatnim przypadku serwery mają możliwość v.rymuszania zdarzeń na przeglądarkach. Razem wszystkie wymienione funkcje przekształcają przeglądarkę w standardową strukturę programistyczną, kontrolowaną przez różne języki skryptowe. Wiele z nich jest już obsłu giwanych przez przeglądarki Chrome, Firefox, Opera czy Safari. Spodziewamy się, że ich obsługa zostanie również wprowadzona w najnowszej wersji Internet Explorera. W kwestii systemu Android jawnie są obsługiwane następujące funkcje: • Obsługa interfejsu bazodanowego wobec klienckich baz danych obsługujących język SQL. • Obsługa pamięci podręcznej aplikacji w przypadku aplikacji lokalnych. •
Obsługa interfejsu geolokacji, który dostarcza informacje na temat położenia geograficznego urządzenia,
•
Obsługa znacznika wideo w trybie pełnoekranowym,
Szybkie wprowadzenie obsługi większej ilości \\')'mienionych wcześniej funkcji da szansę na projektowanie o wiele ciekawszych aplikacji w systemie Android, Jeżeli firmie Google uda się utrzymać taki poziom plastyczności w trakcie dalszego rozwoju platformy Android, bę dzie się ona wyróżniała spośród pozostałych systemów operacyjnych dostępnych na rynku. Podsumujmy ten rozdział listą pożytecznych zasobów dotyczących systemu Android.
Zasoby związane z systemem Android Podzieliliśmy zasoby na dwie kategorie. Pierwsza z nich zawiera listę podstawm�rych źródeł, przydatnych dla programistów, W zestawie drugim umieściliśmy odnośniki do zasobów prezentujących najnowsze wiadomości ze świata Androida.
Podstawowe zasoby dotyczące systemu Android Zasoby umieszczone w tym paragrafie zwi<1zane są przede wszystkim ze wsparciem społecz ności przez firmę Google,
Strona domowa Android Developer (http://developer.android.com). jest to pierwsza witry na, od której powinien rozpocząć podróż każdy projektant aplikacji na ten system. Do każdej nowej wersji środowiska SDK są tu umieszczane odpowiednie odnośniki, Przewodnik programisty (http:!!developer.android.com!guide!index.html). Jak sama nazwa wskazuje, jest to przewodnik programisty aplikacji na system Android, opisujący najnowszą
660
Android 2. Tworzenie aplikacji
edycję platformy. Obecnie umieszczona tam dokumentacja omawia środowisko Android SDK w wersji 2.2 rl.
Funkcje systemu Android 2.0 (http://developer.android.com/sdk/android-2.0-highlights.html). Uzyskamy tutaj ogólny pogląd na funkcje dostępne w systemie Android 2.0. Zmiany na poziomie interfejsów API w systemie Android 2.0 (http://developer.android.com/ sdk!android-2.0.html). Pod tym adresem kryje się lista nowych interfejsów API, wprowa dzonych w wersji 2.0 środowiska.
Zestawy Android SDK do pobrania (http://developer.android.com/sdklindex.html). Znaj dują się tutaj pakiety instalatorów środowiska Android SDK. Kod źródłowy projektu Android (http://source.android.com/). Osoby szukające kodu źró dłowego środowiska Android SDK znajdą go na tej stronie. Konferencja Google I/O (http://code.google.com/events/iol). Na tej stronie umieszczono informacje z konferencji Google I/O, z materiałami poświęconymi Androidowi włącznie. Dostępne są również materiały z poprzednich lat. Git (http://git-scm.com/). Do pracy z kodem platformy Android wymagany jest system kontroli wersji Git, posiadający jawny kod źródłowy, który służy do dostosowywania du żych, dystrybuowanych projektów. The Android Blog (http://android-developers. blogspot.com/). Znajdziemy tu profesjonalne artykuły oraz komentarze dotyczące wnętrzności Androida. Grupa programistów aplikacji na platformę Google Android (http://groups.google.com/ group!android-developers). Jest to grupa dyskusyjna na temat zagadnień projektowych na platformie Android.
Lista problemów dotyczących systemu Android (http://code.google.com/p/android/issues//ist). To właśnie tutaj są zgłaszane wszelkie zauważone błędy oraz umieszczane odpowiednie ronvią zania. Bardzo przydatna strona do przejrzenia listy problemów z systemem Android. Strona jednego ze współautorów książki (http://www.satyakomatineni.com). Satya często prowadzi notatki na temat swojej pracy, przeprowadzanych badań oraz fragmentów stworzone go kodu na platformę Android. Odnośniki na stronie głównej kierują nas na strony poświę cone różnych aspektom Androida.
Zasoby związane z aktualnościami ze świata Androida Warto poświęcić chwilę czasu na przejrzenie zawartych na poniższych stronach aktualności dotyczących systemu Android.
(http://android.eom.pl/). Jest to największa polska strona poświęcona plat formie Android. Często aktualizowana, zawiera bazę artykułów, recenzje urządzeń obsłu gujących system Android oraz odnośniki do forum i polskiej stronie WIKI poświęconej Androidowi.
Android.com.pl
(http://www.androidal.pl/). Druga polska strona poświęcona Androidowi. Również jest często aktualizowana oraz można na niej znaleźć recenzje gier i aplikacji.
Androidal.pl
Anddev.org (http://www.anddev.org). Witryna ta jest zbudowana na bazie złożonego forum,
w którym można odnaleźć mnóstwo aktualności, recenzje telefonów i aplikacji obsługujących
Rozdział 1 8 • Perspektywy i zasoby
661
system Android, omówienie problemów związanych z pisaniem kodu oraz samouczki na przykładach kodu źródłowego. Sama treść nie budzi zastrzeżeń, lecz wykonanie strony mogło być nieco lepsze. Androidandme (http://www.androidandme.com). Witryna ta bardziej przypomina czasopismo informacyjne poświęcone Androidowi. Jej tematyką są telefony, operatorzy telefonii, aplikacje, gry, łamanie zabezpieczeń telefonów, pomoc początkującym programistom i użytkow nikom, a także można tam znaleźć forum i sklep z telefonami oraz akcesoriami. Podobnie jak witrynę anddev.org tworzy jedno wielkie forum, na omawianą stronę składa się szereg blogów pogrupowanych w kategorie. Również i tutaj nie zabrakło forum, a społeczność użytkowników wydaje się całkiem liczebna. Android guys (http://www.androidguys.com). Jest to witryna informacyjna nasta>viona głównie na podcasty. Jej głównymi działami są aktualności, sklep z akcesoriami i oprogramowaniem oraz olbrzymia ilość podcastów. Wspomniany sklep jest bardzo dopracowany. Omawiana strona również jest t'vorzona przez blogi, gdzie każdy element stanowi artykuł lub wpis. Androidauthority (http://www.androidauthority. com/). Kolejna strona poświęcona aktual nościom. Zawiera informacje i recenzje telefonów oraz aplikacji. Umieszczone są tu filmy wideo, sklep oraz oddzielny dział poświęcony netbookom. Witryna ta ma wygląd prostego blogu zawierającego forum, podział na kategorie oraz sklep. Przede wszystkim jednak, jak twierdzą sami autorzy, jest to strona poświęcona aktualnościom i recenzjom. Androidcentral (http://www.androidcentral.com). Jest to strona poświęcona aktualnościom i artykułom, na której znaleźć można również forum i sklep.
Podsumowanie W niniejszej książce poruszyliśmy olbrzymią ilość tematów poświęconych platformie An droid. Wszystkie tematy dogłębnie przeanalizowaliśmy, wspomagając się działającymi przykładami. W ostatnim rozdziale przyjrzeliśmy się rynkowi urządzeń mobilnych i pro gnozowaliśmy miejsce systemu Android w przyszłości urządzeń przenośnych. \iVymieniliśmy również zbiór zasobów, dzięki którym będziemy na bieżąco wraz z rozwojem Androida. Na koniec chcielibyśmy podziękować Czytelnikom za umożliwienie wyłożenia zdobytej przez nas wiedzy na temat systemu Android. W razie wszelkich pytań prosimy o skontak towanie się z nami. Jesteśmy dostępni pod następującymi adresami:
• Sayed Hashimi: [email protected] • Satya Komatineni: [email protected] • Dave MacLean: [email protected]