Vous êtes sur la page 1sur 1177

Spis treci

Przedmowa ............................................................................................. 21
O autorach .............................................................................................. 23
Informacje o redaktorze technicznym ................................................. 25
Podzikowania ....................................................................................... 27
Sowo wstpne ....................................................................................... 29
Rozdzia 1.

Wprowadzenie do platformy obliczeniowej Android ........................ 31


Nowa platforma dla nowego typu komputera osobistego .............................. 32
Pocztki historii Androida .................................................................................. 33
Zapoznanie si ze rodowiskiem Dalvik VM .................................................... 36
Stos programowy Androida ................................................................................ 37
Projektowanie aplikacji uytkownika kocowego
za pomoc zestawu Android SDK ................................................................... 38
Emulator Androida ........................................................................................ 38
Interfejs uytkownika na platformie Android ............................................ 39
Podstawowe skadniki Androida .................................................................. 40
Zaawansowane koncepcje interfejsu uytkownika .................................... 41
Skadniki usug w Androidzie ....................................................................... 43
Skadniki multimediw oraz telefonii w Androidzie ................................ 43
Pakiety Java w Androidzie ............................................................................ 44
Wykorzystanie zalet kodu rdowego Androida ............................................ 48
Przykadowe projekty zawarte w ksice ........................................................... 49
Podsumowanie ...................................................................................................... 49

Rozdzia 2.

Konfigurowanie rodowiska programowania .................................... 51


Konfigurowanie rodowiska ............................................................................... 52
Pobieranie zestawu JDK 6 ............................................................................. 52
Pobieranie rodowiska Eclipse 3.6 ............................................................... 53
Pobieranie zestawu Android SDK ................................................................ 54
Okno narzdzi ................................................................................................. 56
Instalowanie narzdzi ADT .......................................................................... 56
Przedstawienie podstawowych skadnikw ...................................................... 58
Widok ............................................................................................................... 58
Aktywno ....................................................................................................... 59

Spis treci

Intencja ............................................................................................................ 59
Dostawca treci ............................................................................................... 59
Usuga .............................................................................................................. 59
AndroidManifest.xml .................................................................................... 60
Urzdzenia AVD ............................................................................................ 60
Witaj, wiecie! ....................................................................................................... 60
Wirtualne urzdzenia AVD ................................................................................ 65
Poznanie struktury aplikacji Androida ............................................................. 67
Analiza aplikacji Notepad .................................................................................... 69
Wczytanie oraz uruchomienie aplikacji Notepad ...................................... 69
Rozoenie kodu na czynniki pierwsze ........................................................ 71
Badanie cyklu ycia aplikacji ............................................................................... 78
Usuwanie bdw w aplikacji .............................................................................. 81
Uruchamianie emulatora .............................................................................. 83
StrictMode ....................................................................................................... 84
Odnoniki ........................................................................................................ 89
Podsumowanie ...................................................................................................... 89
Rozdzia 3.

Korzystanie z zasobw .......................................................................... 91


Zasoby .................................................................................................................... 91
Zasoby typu string .......................................................................................... 92
Zasoby typu layout ......................................................................................... 94
Skadnia odniesienia do zasobu .................................................................... 95
Definiowanie wasnych identyfikatorw zasobw
do pniejszego uytku ............................................................................... 97
Skompilowane oraz nieskompilowane zasoby Androida ......................... 98
Rodzaje gwnych zasobw w Androidzie ........................................................ 99
Praca na wasnych plikach zasobw XML ...................................................... 109
Praca na nieskompresowanych zasobach ........................................................ 111
Praca z dodatkowymi plikami ........................................................................... 111
Przegld struktury katalogw mieszczcych zasoby ...................................... 112
Zasoby a zmiany konfiguracji ........................................................................... 112
Odnoniki ............................................................................................................ 116
Podsumowanie .................................................................................................... 117

Rozdzia 4 . Dostawcy treci .................................................................................... 119


Analiza wbudowanych dostawcw Androida ................................................ 120
Architektura dostawcw treci ......................................................................... 126
Implementowanie dostawcw treci ................................................................ 139
Testowanie dostawcy BookProvider ................................................................ 150
Dodawanie ksiki ........................................................................................ 150
Usuwanie ksiki .......................................................................................... 150
Zliczanie ksiek ........................................................................................... 151
Wywietlanie listy ksiek ........................................................................... 151
Odnoniki ............................................................................................................ 152
Podsumowanie .................................................................................................... 153

Spis treci

Rozdzia 5.

Intencje ................................................................................................. 155


Podstawowe informacje na temat intencji ...................................................... 155
Intencje dostpne w Androidzie ....................................................................... 156
Przegld struktury intencji ................................................................................ 159
Intencje a identyfikatory danych URI ....................................................... 159
Dziaania oglne ........................................................................................... 160
Korzystanie z dodatkowych informacji ..................................................... 161
Stosowanie skadnikw
do bezporedniego przywoywania aktywnoci ..................................... 162
Kategorie intencji ......................................................................................... 163
Reguy przydzielania intencji do ich skadnikw ..................................... 166
Dziaanie ACTION_PICK .......................................................................... 169
Dziaanie ACTION_GET_CONTENT ..................................................... 171
Wprowadzenie do intencji oczekujcych ........................................................ 172
Odnoniki ............................................................................................................ 173
Podsumowanie .................................................................................................... 174

Rozdzia 6.

Budowanie interfejsw uytkownika oraz uywanie kontrolek ........ 175


Projektowanie interfejsw UI w Androidzie .................................................. 175
Programowanie interfejsu uytkownika wycznie za pomoc kodu ........ 177
Tworzenie interfejsu uytkownika wycznie w pliku XML .................. 179
Konstruowanie interfejsu uytkownika
za pomoc kodu oraz jzyka XML ........................................................... 180
FILL_PARENT a MATCH_PARENT ....................................................... 182
Standardowe kontrolki Androida .................................................................... 182
Kontrolki tekstu ............................................................................................ 183
Kontrolki przyciskw ................................................................................... 187
Kontrolka ImageView .................................................................................. 195
Kontrolki daty i czasu .................................................................................. 197
Kontrolka MapView ..................................................................................... 200
Dziaanie adapterw ........................................................................................... 200
Zapoznanie si z klas SimpleCursorAdapter .......................................... 200
Zapoznanie si z klas ArrayAdapter ........................................................ 202
Wykorzystywanie adapterw wraz z kontrolkami AdapterView ................ 204
Podstawowa kontrolka listy ListView ................................................... 205
Kontrolka GridView .................................................................................... 213
Kontrolka Spinner ........................................................................................ 215
Kontrolka Gallery ......................................................................................... 217
Tworzenie niestandardowych adapterw ................................................. 218
Inne kontrolki w Androidzie ...................................................................... 223
Style i motywy ..................................................................................................... 224
Stosowanie stylw ......................................................................................... 224
Stosowanie motyww .................................................................................. 227

Spis treci

Menedery ukadu graficznego ......................................................................... 227


Meneder ukadu graficznego LinearLayout ............................................ 228
Meneder ukadu graficznego TableLayout ............................................. 231
Meneder ukadu graficznego RelativeLayout ......................................... 235
Meneder ukadu graficznego FrameLayout ............................................ 237
Dostosowanie ukadu graficznego do konfiguracji rnych urzdze ....... 239
Usuwanie bdw i optymalizacja ukadw graficznych
za pomoc narzdzia Hierarchy Viewer ....................................................... 242
Odnoniki ............................................................................................................ 244
Podsumowanie .................................................................................................... 245
Rozdzia 7.

Praca z menu ........................................................................................ 247


Menu w Androidzie ........................................................................................... 247
Tworzenie menu ........................................................................................... 249
Praca z grupami menu ................................................................................. 250
Odpowied na wybr elementw menu .......................................................... 251
Utworzenie rodowiska testowego do sprawdzania menu ........................... 253
Praca z innymi rodzajami menu ....................................................................... 259
Rozszerzone menu ....................................................................................... 259
Praca z menu w postaci ikon ....................................................................... 259
Praca z podmenu .......................................................................................... 260
Zabezpieczanie menu systemowych .......................................................... 261
Praca z menu kontekstowymi ..................................................................... 261
Praca z menu alternatywnymi .................................................................... 264
Praca z menu w odpowiedzi na zmian danych ....................................... 268
Wczytywanie menu poprzez pliki XML .......................................................... 268
Struktura pliku XML zasobw menu ......................................................... 268
Zapenianie plikw XML zasobw menu .................................................. 269
Tworzenie odpowiedzi dla elementw menu opartych na pliku XML ..... 270
Krtkie wprowadzenie do dodatkowych znacznikw
menu w pliku XML .................................................................................... 271
Odnoniki ............................................................................................................ 272
Podsumowanie .................................................................................................... 272

Rozdzia 8.

Praca z oknami dialogowymi .............................................................. 273


Korzystanie z okien dialogowych w Androidzie ............................................ 274
Projektowanie okien alertw ...................................................................... 274
Projektowanie okna dialogowego zachty ................................................ 276
Natura okien dialogowych w Androidzie ................................................. 281
Przeprojektowanie okna dialogowego zachty ......................................... 282
Praca z zarzdzanymi oknami dialogowymi ................................................... 283
Protok zarzdzanych okien dialogowych .............................................. 283
Przeksztacenie niezarzdzanego okna dialogowego
na zarzdzane okno dialogowe ................................................................ 283
Uproszczenie protokou zarzdzanych okien dialogowych ................... 285

Spis treci

Praca z klas Toast .............................................................................................. 293


Odnoniki ............................................................................................................ 294
Podsumowanie .................................................................................................... 294
Rozdzia 9.

Praca z preferencjami i zachowywanie stanw ................................ 295


Badanie struktury preferencji ........................................................................... 296
Klasa ListPreference ..................................................................................... 296
Widok CheckBoxPreference ....................................................................... 305
Widok EditTextPreference .......................................................................... 307
Widok RingtonePreference ......................................................................... 308
Organizowanie preferencji ................................................................................ 310
Programowe zarzdzanie preferencjami ......................................................... 312
Zapisywanie stanu za pomoc preferencji ...................................................... 313
Odnoniki ............................................................................................................ 314
Podsumowanie .................................................................................................... 315

Rozdzia 10. Analiza zabezpiecze i uprawnie ..................................................... 317


Model zabezpiecze w Androidzie ................................................................... 317
Przegld poj dotyczcych zabezpiecze ................................................. 317
Podpisywanie wdraanych aplikacji .......................................................... 318
Przeprowadzanie testw zabezpiecze rodowiska wykonawczego ............. 324
Zabezpieczenia na granicach procesu ........................................................ 324
Deklarowanie oraz stosowanie uprawnie ............................................... 325
Stosowanie niestandardowych uprawnie ................................................ 326
Stosowanie uprawnie identyfikatorw URI ........................................... 332
Odnoniki ............................................................................................................ 334
Podsumowanie .................................................................................................... 335
Rozdzia 11. Tworzenie i uytkowanie usug ............................................................ 337
Uytkowanie usug HTTP ................................................................................. 337
Wykorzystanie moduu HttpClient do da wywoania GET .................. 338
Wykorzystanie moduu HttpClient do da wywoania POST
(przykad wieloczciowy) .......................................................................... 340
Parsery SOAP, JSON i XML ....................................................................... 342
Obsuga wyjtkw ........................................................................................ 343
Problemy z wielowtkowoci .................................................................... 345
Zabawa z przekroczeniami limitu czasu .................................................... 348
Stosowanie klasy HttpURLConnection ..................................................... 349
Uywanie klasy AndroidHttpClient .......................................................... 349
Stosowanie wtkw drugoplanowych (AsyncTask) ................................ 351
Obsuga zmian konfiguracji za pomoc klasy AsyncTask ...................... 357
Pobieranie plikw za pomoc klasy DownloadManager ........................ 362
Stosowanie usug w Androidzie ........................................................................ 367
Usugi w Androidzie .................................................................................... 368
Usugi lokalne ............................................................................................... 369
Usugi AIDL .................................................................................................. 376

10

Spis treci

Definiowanie interfejsu usugi w jzyku AIDL ........................................ 376


Implementowanie interfejsu AIDL ............................................................ 379
Wywoywanie usugi z poziomu aplikacji klienckiej ............................... 381
Przekazywanie usugom zoonych typw danych .................................. 385
Przykad aplikacji uytkowej korzystajcej z usug ........................................ 395
Interfejs Tumacz Google ............................................................................ 395
Stosowanie interfejsu Tumacz Google ..................................................... 397
Odnoniki ............................................................................................................ 405
Podsumowanie .................................................................................................... 405
Rozdzia 12. Analiza pakietw ................................................................................. 407
Pakiety i procesy ................................................................................................. 407
Szczegowa specyfikacja pakietu .............................................................. 407
Przeksztacanie nazwy pakietu w nazw procesu ..................................... 408
Tworzenie listy zainstalowanych pakietw ............................................... 408
Usuwanie pakietu za pomoc aplikacji Package Browser ....................... 409
Jeszcze raz o procesie podpisywania pakietw ............................................... 409
Zrozumienie koncepcji podpisw cyfrowych scenariusz 1. ................. 410
Zrozumienie koncepcji podpisw cyfrowych scenariusz 2. ................. 410
Wyjanienie koncepcji podpisw cyfrowych ............................................ 410
Jak zatem tworzymy cyfrowy podpis ......................................................... 411
Implikacje wynikajce z podpisywania plikw ........................................ 411
Wspdzielenie danych pomidzy pakietami ................................................. 412
Natura wspdzielonych identyfikatorw uytkownika ......................... 412
Schemat kodu wykorzystywanego przy wspdzieleniu danych ........... 413
Projekty bibliotek ................................................................................................ 414
Czym jest projekt bibliotek? ........................................................................ 414
Twierdzenia dotyczce projektw bibliotek ............................................. 414
Utworzenie projektu bibliotek .................................................................... 417
Tworzenie projektu testowego wykorzystujcego projekt bibliotek ..... 420
Odnoniki ............................................................................................................ 425
Podsumowanie .................................................................................................... 426
Rozdzia 13. Analiza procedur obsugi .................................................................... 427
Skadniki Androida i wtkowanie .................................................................... 427
Aktywnoci dziaaj w gwnym wtku .................................................... 428
Odbiorcy komunikatw dziaaj w gwnym wtku ............................... 429
Usugi dziaaj w gwnym wtku ............................................................. 429
Dostawcy treci dziaaj w gwnym wtku ............................................. 429
Skutki posiadania pojedynczego gwnego wtku ................................... 429
Pule wtkw, dostawcy treci, skadniki zewntrznych usug ................... 429
Narzdzia wtkowania poznaj swj wtek ........................................... 429
Procedury obsugi ............................................................................................... 431
Skutki przetrzymywania gwnego wtku ................................................ 432
Zastosowanie procedury obsugi
do opnienia operacji w wtku gwnym ............................................. 432

Spis treci

11

Przykadowy kod rdowy procedury obsugi opniajcej


przeprowadzanie operacji ......................................................................... 433
Konstruowanie odpowiedniego obiektu Message ................................... 435
Wysyanie obiektw Message do kolejki ................................................... 435
Odpowied na metod zwrotn handleMessage ...................................... 436
Stosowanie wtkw roboczych ......................................................................... 436
Przywoywanie wtku roboczego z poziomu menu ................................ 437
Komunikacja pomidzy wtkami gwnym i roboczym ........................ 438
Szybki przegld jak dziaa wtek? .......................................................... 440
Klasy przykadowego sterownika procedury obsugi ...................................... 441
Plik aktywnoci sterujcej ........................................................................... 442
Plik ukadu graficznego ............................................................................... 444
Plik menu ....................................................................................................... 445
Plik manifest .................................................................................................. 445
Czas ycia skadnika i procesu .......................................................................... 446
Cykl ycia aktywnoci .................................................................................. 446
Cykl ycia usugi ........................................................................................... 448
Cykl ycia odbiorcw komunikatw ......................................................... 448
Cykl ycia dostawcy treci ........................................................................... 448
Instrukcje dotyczce kompilowania kodu ....................................................... 449
Utworzenie projektu za pomoc pliku ZIP ............................................... 449
Tworzenie projektu za pomoc listingw ................................................. 449
Odnoniki ............................................................................................................ 450
Podsumowanie .................................................................................................... 450
Rozdzia 14. Odbiorcy komunikatw i usugi dugoterminowe ........................... 453
Odbiorcy komunikatw ..................................................................................... 453
Wysyanie komunikatu ................................................................................ 454
Tworzenie prostego odbiorcy przykadowy kod ................................. 454
Rejestrowanie odbiorcy komunikatw w pliku manifecie .................... 456
Wysyanie komunikatu testowego ............................................................. 456
Wprowadzanie wielu odbiorcw komunikatw ...................................... 460
Projekt wykorzystujcy odbiorcw pozaprocesowych ............................ 462
Uywanie powiadomie pochodzcych od odbiorcy komunikatw .......... 463
Monitorowanie powiadomie za pomoc menedera powiadomie ....... 463
Wysyanie powiadomienia .......................................................................... 464
Dugoterminowi odbiorcy komunikatw i usugi ......................................... 467
Protok dugoterminowego odbiorcy komunikatw ............................ 468
Klasa IntentService ....................................................................................... 469
Kod rdowy klasy IntentService .............................................................. 470
Rozszerzanie klasy IntentService na odbiorc komunikatw ...................... 472
Abstrakcja dugoterminowej usugi wysyajcej komunikaty .................. 472
Dugoterminowy odbiorca komunikatw ................................................ 474
Wyodrbnianie blokady przechodzenia
w stan zatrzymania za pomoc klasy LightedGreenRoom ................... 476

12

Spis treci

Owietlony zielony pokj ............................................................................ 478


Implementacja owietlonego zielonego pokoju ....................................... 478
Implementacja dugoterminowej usugi .......................................................... 483
Szczegowe informacje na temat usugi nietrwaej ................................ 484
Informacje dotyczce trwaej usugi .......................................................... 485
Odmiana nietrwaej usugi ponownie dostarczane intencje .................. 485
Definiowanie flag usugi w metodzie onStartCommand ........................ 485
Wybieranie odpowiedniego trybu usugi .................................................. 485
Kontrolowanie blokady przechodzenia
w stan zatrzymania z dwch miejsc jednoczenie ................................. 486
Implementacja dugoterminowej usugi ................................................... 486
Testowanie dugoterminowych usug ........................................................ 488
Instrukcje dotyczce kompilowania kodu ....................................................... 489
Utworzenie projektw za pomoc pliku ZIP ............................................ 489
Utworzenie projektw za pomoc listingw ............................................ 489
Odnoniki ............................................................................................................ 491
Podsumowanie .................................................................................................... 492
Rozdzia 15. Badanie menedera alarmw ............................................................. 493
Podstawy menedera alarmw konfiguracja prostego alarmu ............... 493
Uzyskanie dostpu do menedera alarmw ............................................. 494
Definiowanie czasu uruchomienia alarmu ............................................... 494
Konfigurowanie odbiorcy dla alarmu ........................................................ 495
Utworzenie oczekujcej intencji dostosowanej do alarmu ..................... 495
Ustawianie alarmu ........................................................................................ 496
Projekt testowy .............................................................................................. 497
Analiza alternatywnych wersji menedera alarmw ..................................... 503
Konfigurowanie powtarzalnego alarmu .................................................... 503
Anulowanie alarmu ...................................................................................... 506
Praca z wieloma alarmami jednoczenie ................................................... 508
Pierwszestwo intencji w uruchamianiu alarmw .................................. 512
Trwao alarmw ........................................................................................ 515
Twierdzenia dotyczce menedera alarmw .................................................. 515
Odnoniki ............................................................................................................ 516
Podsumowanie .................................................................................................... 516
Rozdzia 16. Analiza animacji dwuwymiarowej ..................................................... 517
Animacja poklatkowa ........................................................................................ 518
Zaplanowanie animacji poklatkowej ......................................................... 518
Utworzenie aktywnoci ............................................................................... 519
Dodawanie animacji do aktywnoci .......................................................... 520
Animacja ukadu graficznego ........................................................................... 523
Podstawowe typy animacji klatek kluczowych ......................................... 524
Zaplanowanie rodowiska testowego animacji ukadu graficznego ...... 525

Spis treci

13

Utworzenie aktywnoci oraz widoku ListView ........................................ 525


Animowanie widoku ListView ................................................................... 528
Stosowanie interpolatorw ......................................................................... 531
Animacja widoku ................................................................................................ 533
Animacja widoku ......................................................................................... 533
Dodawanie animacji ..................................................................................... 536
Zastosowanie klasy Camera do symulowania gbi w obrazie
dwuwymiarowym ...................................................................................... 539
Analiza interfejsu AnimationListener ....................................................... 541
Kilka uwag na temat macierzy transformacji ........................................... 541
Odnoniki ............................................................................................................ 542
Podsumowanie .................................................................................................... 543
Rozdzia 17. Analiza usug wykorzystujcych mapy i dane o lokalizacji ............. 545
Pakiet do pracy z mapami ................................................................................. 546
Uzyskanie klucza interfejsu API mapy od firmy Google ........................ 546
Klasy MapView i MapActivity .................................................................... 548
Dodawanie znacznikw za pomoc nakadek .......................................... 553
Pakiet do obsugi danych o pooeniu geograficznym .................................. 559
Geokodowanie w Androidzie ..................................................................... 559
Geokodowanie za pomoc wtkw przebiegajcych w tle ..................... 563
Usuga LocationManager ............................................................................ 566
Wywietlanie informacji o pooeniu za pomoc klasy
MyLocationOverlay ................................................................................... 574
Stosowanie alertw odlegociowych ......................................................... 578
Odnoniki ............................................................................................................ 583
Podsumowanie .................................................................................................... 583
Rozdzia 18. Uywanie interfejsw telefonii .............................................................. 585
Praca z wiadomociami SMS ............................................................................ 585
Wysyanie wiadomoci SMS ....................................................................... 585
Monitorowanie przychodzcych wiadomoci tekstowych ..................... 589
Praca z folderami wiadomoci SMS ........................................................... 592
Wysyanie wiadomoci e-mail .................................................................... 593
Praca z menederem telefonii ........................................................................... 594
Protok inicjalizacji sesji (SIP) ........................................................................ 597
Odnoniki ............................................................................................................ 600
Podsumowanie .................................................................................................... 600
Rozdzia 19. Uywanie szkieletu multimedialnego ............................................... 601
Stosowanie interfejsw API multimediw ...................................................... 601
Wykorzystywanie kart SD ........................................................................... 602
Odtwarzanie multimediw ................................................................................ 606
Odtwarzanie rde dwikowych ............................................................. 607
Odtwarzanie plikw wideo ......................................................................... 619

14

Spis treci

Rejestrowanie multimediw ............................................................................. 621


Analiza procesu rejestracji dwiku za pomoc klasy MediaRecorder ...... 622
Rejestracja dwikw za pomoc klasy AudioRecord ............................. 626
Analiza procesu rejestracji wideo ............................................................... 630
Analiza klasy MediaStore ............................................................................ 640
Rejestrowanie dwiku za pomoc intencji .............................................. 641
Dodawanie plikw do magazynu multimediw ...................................... 644
Podczenie klasy MediaScanner do caej karty SD ................................. 647
Odnoniki ............................................................................................................ 647
Podsumowanie .................................................................................................... 648
Rozdzia 20. Programowanie grafiki trjwymiarowej
za pomoc biblioteki OpenGL ............................................................ 649
Historia i podstawy biblioteki OpenGL ........................................................... 650
OpenGL ES .................................................................................................... 651
rodowisko OpenGL ES a Java ME ............................................................ 652
M3G inny standard grafiki trjwymiarowej rodowiska Java ........... 652
Podstawy struktury OpenGL ............................................................................ 653
Podstawy rysowania za pomoc biblioteki OpenGL ............................... 654
Kamera i wsprzdne ................................................................................. 659
Tworzenie interfejsu pomidzy standardem OpenGL ES a Androidem .... 663
Stosowanie klasy GLSurfaceView i klas pokrewnych .............................. 664
Implementacja klasy Renderer ................................................................... 664
Zastosowanie klasy GLSurfaceView z poziomu aktywnoci .................. 667
Zmiana ustawie kamery ............................................................................ 672
Wykorzystanie indeksw do dodania kolejnego trjkta ....................... 675
Animowanie prostego trjkta w bibliotece OpenGL ............................. 676
Stawianie czoa bibliotece OpenGL ksztaty i tekstury ............................. 678
Rysowanie prostokta .................................................................................. 679
Praca z ksztatami ......................................................................................... 680
Praca z teksturami ........................................................................................ 694
Rysowanie wielu figur geometrycznych .................................................... 699
OpenGL ES 2.0 .................................................................................................... 703
Powizania rodowiska Java z bibliotekami OpenGL ES 2.0 ................. 704
Etapy renderowania ..................................................................................... 707
Jednostki cieniujce ...................................................................................... 708
Kompilowanie jednostek cieniujcych w programie ............................... 709
Uzyskiwanie dostpu do zmiennych jednostek cieniowania ................. 711
Prosty trjkt napisany w rodowisku OpenGL ES 2.0 ........................... 711
Dodatkowe rda dotyczce rodowiska OpenGL ES 2.0 ..................... 715
Instrukcje zwizane z kompilowaniem kodu .................................................. 715
Odnoniki ............................................................................................................ 715
Podsumowanie .................................................................................................... 716

Spis treci

15

Rozdzia 21. Badanie aktywnych folderw ............................................................. 717


Badanie aktywnych folderw ............................................................................ 717
W jaki sposb uytkownik korzysta z aktywnych folderw ................... 718
Tworzenie aktywnego folderu .................................................................... 722
Instrukcje dotyczce kompilowania kodu ....................................................... 733
Odnoniki ............................................................................................................ 733
Podsumowanie .................................................................................................... 734
Rozdzia 22. Widety ekranu startowego ................................................................ 735
Architektura widetw ekranu startowego ..................................................... 736
Czym s widety ekranu startowego? ........................................................ 736
W jaki sposb uytkownik korzysta z widetw ekranu startowego? ........ 736
Cykl ycia widetu ........................................................................................ 740
Przykadowy widet ............................................................................................ 745
Definiowanie dostawcy widetu ................................................................. 747
Definiowanie rozmiaru widetu ................................................................. 748
Pliki zwizane z ukadem graficznym widetu ......................................... 749
Implementacja dostawcy widetu .............................................................. 751
Implementacja modeli widetw ............................................................... 753
Implementacja aktywnoci konfiguracji widetu .................................... 761
Ograniczenia i rozszerzenia widetw ............................................................. 764
Odnoniki ............................................................................................................ 765
Podsumowanie .................................................................................................... 766
Rozdzia 23. Wyszukiwanie w Androidzie ............................................................... 767
Wyszukiwanie w Androidzie ............................................................................ 768
Badanie procesu przeszukiwania globalnego w Androidzie .................. 768
Wczanie dostawcw propozycji do procesu wyszukiwania globalnego ..... 774
Interakcja aktywnoci z przyciskiem wyszukiwania ...................................... 777
Zachowanie przycisku wyszukiwania wobec standardowej
aktywnoci ................................................................................................... 778
Zachowanie aktywnoci wyczajcej wyszukiwanie .............................. 786
Jawne wywoywanie wyszukiwania za pomoc menu ............................. 787
Wyszukiwanie lokalne i pokrewne aktywnoci ........................................ 790
Uruchomienie funkcji type-to-search ....................................................... 797
Implementacja prostego dostawcy propozycji ............................................... 798
Planowanie prostego dostawcy propozycji ............................................... 798
Pliki implementacji prostego dostawcy propozycji ................................. 799
Implementacja klasy SimpleSuggestionProvider ..................................... 799
Aktywno wyszukiwania dostpna w prostym dostawcy propozycji ....... 803
Aktywno wywoania wyszukiwania ........................................................ 808
Uytkowanie prostego dostawcy propozycji ............................................ 810
Implementacja niestandardowego dostawcy propozycji .............................. 813
Implementacja niestandardowego dostawcy propozycji ........................ 814
Pliki wymagane do implementacji projektu SuggestUrlProvider ......... 814

16

Spis treci

Implementacja klasy SuggestUrlProvider ................................................. 815


Implementacja aktywnoci wyszukiwania
dla niestandardowego dostawcy propozycji ........................................... 824
Plik manifest niestandardowego dostawcy propozycji ........................... 830
Korzystanie z niestandardowego dostawcy propozycji ........................... 831
Zastosowanie przyciskw dziaania
i danych wyszukiwania specyficznych dla aplikacji .................................... 835
Wykorzystanie przyciskw dziaania w procesie wyszukiwania ........... 835
Praca ze specyficznym dla aplikacji kontekstem wyszukiwania ................ 838
Odnoniki ............................................................................................................ 839
Wyszukiwanie w tabletach ................................................................................ 840
Podsumowanie .................................................................................................... 840
Rozdzia 24. Analiza interfejsu przetwarzania tekstu na mow ............................. 841
Podstawy technologii przetwarzania tekstu na mow w Androidzie .......... 841
Uywanie wyrae do ledzenia toku wypowiedzi ........................................ 846
Zastosowanie plikw dwikowych do przetwarzania tekstu na mow ......... 848
Zaawansowane funkcje silnika TTS ................................................................. 854
Konfiguracja strumieni audio ..................................................................... 855
Stosowanie ikon akustycznych ................................................................... 855
Odtwarzanie ciszy ......................................................................................... 856
Wybr innych mechanizmw przetwarzania tekstu na mow ................. 856
Stosowanie metod jzykowych ................................................................... 857
Odnoniki ............................................................................................................ 858
Podsumowanie .................................................................................................... 859
Rozdzia 25. Ekrany dotykowe ................................................................................. 861
Klasa MotionEvent ............................................................................................. 861
Obiekt MotionEvent .................................................................................... 862
Wielokrotne wykorzystywanie obiektw MotionEvent .......................... 873
Stosowanie klasy VelocityTracker .............................................................. 874
Analiza funkcji przecigania ....................................................................... 876
Wielodotykowo ............................................................................................... 879
Funkcja wielodotykowoci przed wersj 2.2 Androida ........................... 879
Funkcja wielodotykowoci w systemach poprzedzajcych wersj 2.2 ........ 887
Obsuga map za pomoc dotyku ....................................................................... 888
Gesty ..................................................................................................................... 891
Gest ciskania ................................................................................................ 891
Klasy GestureDetector i OnGestureListener ............................................ 895
Niestandardowe gesty .................................................................................. 898
Aplikacja Gestures Builder .......................................................................... 898
Odnoniki ............................................................................................................ 905
Podsumowanie .................................................................................................... 905

Spis treci

17

Rozdzia 26. Czujniki ................................................................................................. 907


Czym jest czujnik? .............................................................................................. 907
Wykrywanie czujnikw ............................................................................... 908
Jakie informacje moemy uzyska na temat czujnika? ........................... 909
Pobieranie zdarze generowanych przez czujniki ......................................... 911
Problemy pojawiajce si podczas uzyskiwania danych z czujnikw ... 914
Interpretowanie danych czujnika ..................................................................... 921
Czujniki owietlenia ..................................................................................... 921
Czujniki zblieniowe .................................................................................... 922
Termometry .................................................................................................. 922
Czujniki cinienia ......................................................................................... 923
yroskopy ...................................................................................................... 923
Akcelerometry ............................................................................................... 924
Magnetometry ............................................................................................... 930
Wsppraca akcelerometrw z magnetometrami .................................... 931
Czujniki orientacji w przestrzeni ............................................................... 931
Deklinacja magnetyczna i klasa GeomagneticField ................................. 938
Czujniki grawitacji ....................................................................................... 939
Czujniki przypieszenia liniowego ............................................................. 939
Czujniki wektora obrotu ............................................................................. 939
Czujniki komunikacji bliskiego pola ......................................................... 939
Odnoniki ............................................................................................................ 950
Podsumowanie .................................................................................................... 951
Rozdzia 27. Analiza interfejsu kontaktw ............................................................. 953
Koncepcja konta ................................................................................................. 954
Szybki przegld ekranw zwizanych z kontami ..................................... 954
Zwizek pomidzy kontami a kontaktami ................................................ 957
Wyliczanie kont ............................................................................................ 957
Aplikacja Kontakty ............................................................................................. 958
Wywietlanie kontaktw ............................................................................. 958
Wywietlanie szczegw kontaktu ........................................................... 959
Edytowanie szczegw kontaktu .............................................................. 960
Umieszczanie zdjcia powizanego z kontaktem .................................... 962
Eksportowanie kontaktw ........................................................................... 962
Rne typy danych kontaktowych ............................................................. 964
Analiza kontaktw .............................................................................................. 964
Badanie treci bazy danych SQLite ............................................................ 965
Nieprzetworzone kontakty .......................................................................... 965
Tabela danych ............................................................................................... 967
Kontakty zbiorcze ......................................................................................... 968
view_contacts ................................................................................................ 971
contact_entities_view ................................................................................... 971

18

Spis treci

Praca z interfejsem kontaktw .......................................................................... 972


Eksploracja kont ........................................................................................... 972
Badanie kontaktw zbiorczych ................................................................... 980
Badanie nieprzetworzonych kontaktw .................................................... 989
Przegldanie danych nieprzetworzonego kontaktu ................................. 994
Dodawanie kontaktu oraz szczegowych informacji o nim ................. 998
Kontrola agregacji ............................................................................................. 1001
Konsekwencje synchronizacji ......................................................................... 1002
Odnoniki .......................................................................................................... 1002
Podsumowanie .................................................................................................. 1003
Rozdzia 28. Wdraanie aplikacji na rynek Android Market i nie tylko ......... 1005
Jak zosta wydawc? ......................................................................................... 1006
Postpowanie zgodnie z zasadami ........................................................... 1006
Konsola programisty .................................................................................. 1009
Przygotowanie aplikacji do sprzeday ........................................................... 1012
Testowanie dziaania na rnych urzdzeniach ..................................... 1012
Obsuga rnych rozmiarw ekranu ....................................................... 1012
Przygotowanie pliku AndroidManifest.xml
do umieszczenia w sklepie Android Market ........................................ 1013
Lokalizacja aplikacji ................................................................................... 1014
Przygotowanie ikony aplikacji .................................................................. 1015
Problemy zwizane z zarabianiem pienidzy na aplikacjach ............... 1016
Kierowanie uytkownikw z powrotem do sklepu ................................ 1016
Usuga licencyjna systemu Android ........................................................ 1017
Przygotowanie pliku .apk do wysania .................................................... 1018
Wysyanie aplikacji ........................................................................................... 1018
Korzystanie ze sklepu Android Market ......................................................... 1022
Alternatywy dla serwisu Android Market ..................................................... 1023
Odnoniki .......................................................................................................... 1024
Podsumowanie .................................................................................................. 1024
Rozdzia 29. Koncepcja fragmentw oraz inne pojcia dotyczce tabletw ..... 1025
Czym jest fragment? ......................................................................................... 1026
Kiedy naley stosowa fragmenty? ................................................................. 1027
Struktura fragmentu ......................................................................................... 1027
Cykl ycia fragmentu ................................................................................. 1028
Przykadowa aplikacja ukazujca cykl ycia fragmentu ........................ 1033
Klasy FragmentTransaction i drugoplanowy stos fragmentw ................. 1042
Przejcia i animacje zachodzce podczas transakcji fragmentu ........... 1044
Klasa FragmentManager .................................................................................. 1045
Ostrzeenie dotyczce stosowania odniesie do fragmentw ................. 1046
Klasa ListFragment i wze <fragment> .................................................. 1047
Wywoywanie odrbnej aktywnoci w razie potrzeby .......................... 1051
Trwao fragmentw ................................................................................ 1054

Spis treci

19

Fragmenty wywietlajce okna dialogowe .................................................... 1054


Podstawowe informacje o klasie DialogFragment ................................. 1055
Przykadowa aplikacja wykorzystujca klas DialogFragment ................... 1060
Inne formy komunikowania si z fragmentami ........................................... 1073
Stosowanie metod startActivity() i setTargetFragment() ..................... 1074
Tworzenie niestandardowych animacji
za pomoc klasy ObjectAnimator ................................................................ 1075
Odnoniki .......................................................................................................... 1078
Podsumowanie .................................................................................................. 1078
Rozdzia 30. Analiza klasy ActionBar ..................................................................... 1079
Anatomia klasy ActionBar .............................................................................. 1080
Aktywno paska dziaania wywietlajcego zakadki ..................................... 1081
Implementacja bazowych klas aktywnoci .................................................... 1082
Wprowadzenie jednolitego zachowania klas ActionBar ....................... 1084
Implementacja obiektu nasuchujcego zdarze z zakadek ................ 1087
Implementacja aktywnoci przechowujcej pasek zakadek ................ 1088
Przewijalny ukad graficzny zawierajcy widok debugowania ............ 1090
Pasek dziaania a interakcja z menu ........................................................ 1091
Plik manifest Androida .............................................................................. 1093
Badanie aktywnoci przechowujcej pasek zakadek ............................ 1093
Aktywno paska dziaania w trybie wywietlania listy .............................. 1094
Utworzenie klasy SpinnerAdapter ........................................................... 1095
Utworzenie obiektu nasuchujcego listy ................................................ 1095
Konfigurowanie paska dziaania w trybie wywietlania listy ............... 1096
Zmiany w klasie BaseActionBarActivity ................................................. 1097
Zmiany w pliku AndroidManifest.xml .................................................... 1097
Badanie aktywnoci zawierajcej pasek dziaania
w trybie wywietlania listy ...................................................................... 1098
Aktywno przechowujca standardowy pasek dziaania ........................... 1099
Aktywno przechowujca standardowy pasek dziaania .................... 1100
Zmiany w klasie BaseActionBarActivity ................................................. 1101
Zmiany w pliku AndroidManifest.xml .................................................... 1101
Badanie aktywnoci przechowujcej standardowy pasek dziaania .... 1102
Odnoniki .......................................................................................................... 1102
Podsumowanie .................................................................................................. 1104
Rozdzia 31. Dodatkowe zagadnienia zwizane z wersj 3.0 systemu .............. 1105
Widety ekranu startowego oparte na listach ............................................... 1105
Nowe widoki zdalne w wersji 3.0 systemu .............................................. 1106
Praca z listami stanowicymi cz widoku zdalnego ........................... 1107
Dziaajcy przykad
testowy widet ekranu startowego oparty na licie ........................ 1121
Testowanie widetu wywietlajcego list ............................................... 1130

20

Spis treci

Funkcja przecigania ........................................................................................ 1131


Podstawowe informacje
o funkcji przecigania w wersji 3.0 Androida ...................................... 1131
Przykadowa aplikacja prezentujca funkcj przecigania ................... 1133
Testowanie przykadowej aplikacji wykorzystujcej funkcj
przecigania .............................................................................................. 1145
Odnoniki .......................................................................................................... 1146
Podsumowanie .................................................................................................. 1147
Skorowidz ........................................................................................... 1149

Przedmowa

Wszystko ju si kiedy wydarzyo i wszystko wydarzy si ponownie w przyszoci. Teoria


emergencji (wyaniania si) wyjania, w jaki sposb z oddziaywa midzy prostszymi elementami wyaniaj si zoone systemy i wzorce.
Take my ju tu kiedy bylimy.
Gdy w 1985 roku rozpoczynaem przygod z programowaniem, dostpnych byo wiele rnorodnych komputerw osobistych. Gdy zdobywaem pierwsze szlify w pracy z komputerem
Apple II C, moi znajomi korzystali z platform Commodore 128s, Tandy CoCo 3s lub Atari.
Kady z nas rozwija si w tym samym rodowisku, jednak rzadko kiedy moglimy dzieli si
wynikami pracy. Gdy zaczy si pojawia przystpne cenowo klony komputera firmy IBM,
ktre obsugiway system DOS firmy Microsoft, programici dostrzegli potencja tworzcego si wanie rynku i nastpia szybka ewolucja rodowiska DOS. Ostatecznie firma Microsoft zdobya dominujc pozycj na rynku komputerw PC, ktr cieszy si do dzisiaj.
Gdy w 2003 roku zaczem zajmowa si programowaniem mobilnych aplikacji, sytuacja bardzo
przypominaa t z 1985 roku. Moglimy urzeczywistnia swoje pomysy za pomoc rnorodnych rodowisk, poczwszy od .NET CF firmy Microsoft, poprzez Java Micro Edition, a skoczywszy na BREW. Jednak, podobnie jak w przypadku gier, ktre kiedy tworzylimy z przyjacimi, napisane aplikacje funkcjonoway tylko w okrelonym rodowisku.
Teraz, w 2011 roku, firma Google, dziki polityce udostpniania systemu Android producentom sprztu, zdaje si stawa Microsoftem w wiecie Urzdze Mobilnych. Prawdopodobnie dlatego wybrae t ksik i zacze czyta przedmow. Albo jeste studentem historii, albo podobnie jak ja masz szczcie w niej uczestniczy.
Jeli tak mam dla Ciebie dobre wieci! Pracowalimy bardzo ciko, przygotowujc nowe
wydanie tej ksiki, aby udostpni Ci narzdzia umoliwiajce implementacj pomysw krcych Ci po gowie. Przeprowadzimy Ci przez podstawy, poczwszy od konfigurowania rodowiska, a skoczywszy na wdroeniu aplikacji w sklepie Android Marketplace. Oczywicie, jest to
bardzo rozlega podr, dlatego bdziemy podrowa przede wszystkim najbardziej uczszczanymi szlakami. Pokaemy Ci jednak mnstwo zasobw, za pomoc ktrych moesz zwiedza
szeroki wiat Androida na wasn rk.
Powodzenia i szczliwej drogi!
Dylan Phillips

22

Android 3. Tworzenie aplikacji

Sowo wstpne

23

O autorach

Satya Komatineni (www.satyakomatineni.com) ma ponad dwudziestoletnie


dowiadczenie w programowaniu dla duych i mniejszych przedsibiorstw.
Satya Komatineni opublikowa ponad 30 artykuw dotyczcych projektowania stron WWW przy uyciu technologii Java, .NET oraz baz danych. Jest czstym prelegentem na konferencjach przemysowych dotyczcych innowacyjnych technologii oraz regularnie umieszcza wpisy na blogach w serwisie
java.net. Jest take twrc AspireWeb (www.activeintellect.com/aspire)
uproszczonego narzdzia o jawnym kodzie rdowym, sucego do projektowania stron WWW
w jzyku Java, oraz Aspire Knowledge Central sieciowego systemu operacyjnego o jawnym
kodzie rdowym, nastawionego na efektywno oraz moliwo publikowania przez poszczeglne osoby. Autor jest rwnie czonkiem wielu programw SBIR (ang. Small Business Innovation Research Program program rozwoju innowacji w maych przedsibiorstwach). Uzyska
stopie licencjata inynierii elektrycznej na Uniwersytecie Andhra w Visakhapatnamie oraz tytu
magistra inynierii elektrycznej w Indyjskim Instytucie Technologicznym w Nowym Delhi. Mona si z nim skontaktowa, piszc na adres satya.komatineni@gmail.com.
Dave MacLean jest inynierem i architektem oprogramowania. Obecnie mieszka
i pracuje w Jacksonville na Florydzie. Od 1980 roku zajmuje si programowaniem w wielu jzykach oraz projektowaniem poczwszy od systemw automatyzacji robotw, na systemach przechowywania danych skoczywszy, od
automatycznie obsugiwanych aplikacji sieciowych do procesorw transakcji
EDI. Dave MacLean pracowa dla takich firm, jak Sun Microsystems, IBM,
Trimble Navigation, General Motors oraz kilku mniejszych przedsibiorstw.
Ukoczy studia na Uniwersytecie Waterloo w Kanadzie, uzyskujc tytu inyniera projektowania
systemw. Prowadzi blog dostpny pod adresem http://davemac327.blogspot.com, natomiast
jego adres kontaktowy to davemac327@gmail.com.
Sayed Y. Hashimi urodzi si w Afganistanie, obecnie za przebywa w Jacksonville na Florydzie. Ma bogate dowiadczenie w dziedzinie ochrony zdrowia,
finansw, logistyki oraz architektury zorientowanej na usugi. Zawodowo autor
projektuje wielkoskalowe aplikacje rozproszone, wykorzystujc rne jzyki oraz
platformy, w tym C/C++, MFC, J2EE oraz .NET. Publikowa artykuy w najwikszych czasopismach powiconych oprogramowaniu oraz napisa kilka innych popularnych ksiek dla wydawnictwa Apress. Sayed Y. Hashimi posiada
tytu magistra inynierii uzyskany na Uniwersytecie Floryda. Mona si z nim skontaktowa
na stronie www.sayedhashimi.com.
Zapraszamy na nasz stron http://androidbook.com.

24

Android 3. Tworzenie aplikacji

Sowo wstpne

25

Informacje o redaktorze
technicznym

Dylan Phillips jest inynierem i architektem oprogramowania, pracujcym


w brany rozwiza mobilnych ju od 10 lat. Dziki bogatemu dowiadczeniu,
rozcigajcemu si od pracy w rodowisku J2ME, poprzez .NET Compact
Framework, a do systemu Android, z radoci dostrzeg potencja w dostosowywaniu urzdze wykorzystujcych Androida do rnorodnych zapotrzebowa konsumentw. Mona si z nim skontaktowa poprzez adres
mykoan@hotmail.com, @mykoan na Twitterze albo na obiedzie w jednej z licznych restauracji Pho House rozrzuconych po caych Stanach Zjednoczonych.

26

Android 3. Tworzenie aplikacji

Sowo wstpne

27

Podzikowania

Napisanie tej ksiki wymagao wielkiego wysiku nie tylko z naszej autorw strony, lecz
rwnie od czci bardzo utalentowanego zespou wydawnictwa Apress, a take ze strony redaktora technicznego. Chcielimy zatem podzikowa Steveowi Anglinowi, Matthew Moodiemu,
Corbin Collins, Heather Lang, Tracy Brown, Mary Behr oraz Brigid Duffy z wydawnictwa Apress.
Chcielimy take wyrazi nasze uznanie dla redaktora technicznego, Dylana Phillipsa, za prac,
jak woy w t ksik. Jego komentarze oraz poprawki byy bezcenne. W trakcie poszukiwa
odpowiedzi na forum programistycznym Androida czsto pomoc suyli nam Dianne
Hackborn, Nick Pelly, Brad Fitzpatrick oraz inni czonkowie Android Team. Byli gotowi do
pomocy o kadej porze dnia oraz w weekendy, za co chcemy im wszystkim powiedzie: Dzikujemy!. To zdecydowanie oni s najciej pracujcym zespoem w wiecie urzdze mobilnych.
Spoeczno uytkownikw systemu Android jest bardzo aktywna i rozbudowana. Nieraz ludzie
ci suyli pomoc w znajdowaniu odpowiedzi na trudne pytania, udzielali take poytecznych
rad. Mamy nadziej, e ta ksika w jaki sposb przyda si caej tej spoecznoci.
Jestemy take gboko wdziczni naszym rodzinom za wyrozumiao podczas przeduajcego si pisania ksiki.

28

Android 3. Tworzenie aplikacji

Sowo wstpne

29

Sowo wstpne

Czy kiedykolwiek chciae by Rodinem1? Siedzie z dutem w doni i ciosa ska, formujc
j na ksztat wasnej wizji? Wikszo programistw dcych najpopularniejszymi nurtami
trzymao si z daleka od mocno ograniczonych urzdze mobilnych ze strachu przed niemonoci wyrzebienia uytecznej aplikacji. Ale te czasy ju miny.
System Android pozwala nam na prac z nieprawdopodobn liczb programowalnych
urzdze. W tej ksice pragniemy potwierdzi podejrzenia, jakoby system Android znakomicie nadawa si do pisania aplikacji. Jeli programujesz w Javie, zyskujesz olbrzymi
szans na korzyci pynce z tej ekscytujcej, penej moliwoci, wielozadaniowej platformy
obliczeniowej. Cieszymy si z Androida, poniewa stanowi on zaawansowan platform,
wprowadzajc wiele nowych paradygmatw w kwestii projektowania szkieletw (pomimo
ogranicze urzdze mobilnych).
Jest to nasza trzecia, dotychczas najlepsza, edycja ksiki powiconej Androidowi. Pro Android 3
jest rozlegym przewodnikiem programistycznym. W tym wydaniu udoskonalilimy, przepracowalimy stylistycznie oraz poprawilimy wszystkie elementy ksiki Android 2. Tworzenie
aplikacji w celu stworzenia gruntownie uaktualnionego przewodnika, sucego zarwno pocztkujcym, jak i zaawansowanym programistom bdcego wynikiem trzech lat pracy. Omawiamy
ponad 100 zagadnie podzielonych na 31 rozdziaw. Niniejsze wydanie ksiki obejmuje
wersje 2.3 oraz 3.0 systemu Android, ktre s zoptymalizowanymi wersjami dla, odpowiednio,
telefonw i tabletw.
W tym wydaniu powicilimy wicej uwagi wewntrznym mechanizmom Androida poprzez
omwienie wtkw, procesw, dugoterminowych usug, odbiorcw komunikatw oraz menederw alarmw. Omawiamy rwnie o wiele wicej kontrolek interfejsu uytkownika. Zamiecilimy ponad 150 stron powiconych wersji 3.0 systemu, gdzie omwilimy fragmenty, dialogi
fragmentw, klas ActionBar, a take funkcj przecigania. Znacznie rozwinlimy rozdziay
powicone czujnikom i usugom. Rozdzia omawiajcy rodowisko OpenGL zosta zaktualizowany pod ktem obsugi wersji OpenGL ES 2.0.
Zasadniczymi elementami tej ksiki s objanienia poj, listingi oraz samouczki. Wszystkie
rozdziay zostay podporzdkowane tej filozofii. Kady samodzielny samouczek zosta opatrzony
komentarzem eksperta. Wszystkie projekty zawarte w ksice s dostpne do pobrania i mona
je bez trudu zaimportowa do rodowiska Eclipse. Ciko rwnie pracowalimy nad tym, aby
kady pokazany tu kod mg zosta bezproblemowo skompilowany. Lista plikw skadajcych

August Franois-Ren Rodin francuski rzebiarz, ur. 12 listopada 1840 r. w Paryu, zm. 17 listopada
1917 r. w Meudon. W swoich pracach czy elementy symbolizmu i impresjonizmu. By prekursorem
nowoczesnego rzebiarstwa przyp. red.

30

Android 3. Tworzenie aplikacji

si na kady projekt w danym rozdziale zostaa jasno przedstawiona, dziki czemu wasnorczne
utworzenie projektu staje si jeszcze prostsze.
Tematyka ksiki obejmuje takie kluczowe pojcia, jak zasoby, intencje, dostawcy treci, procesy,
wtki, kontrolki interfejsu uytkownika, odbiorcy wiadomoci, usugi oraz usugi dugoterminowe. Osoby dopiero poznajce rodowisko OpenGL znajd tu mnstwo materiaw dotyczcych wersji OpenGL ES 1.0 oraz 2.0. Wiele miejsca powicilimy funkcjom przetwarzania tekstu
na mow, czujnikom oraz wielodotykowoci. Szeroko rwnie omwilimy zagadnienia dotyczce wersji 3.0 Androida, wrd ktrych mona znale informacje o fragmentach, dialogach
fragmentw, klasie ActionBar i funkcji przecigania.
Na koniec warto wspomnie, e wyszlimy w tej ksice poza podstawowe zagadnienia, e na
kady temat zadawalimy trudne pytania oraz e udokumentowalimy wyniki (mnogo tematw
zawartych w tej ksice wida w szczegowym spisie treci). Aktualizujemy rwnie na bieco
pomocnicz stron (www.androidbook.com), publikujc najnowsze oraz przyszociowe materiay
dotyczce zestawu Android SDK. W razie pojawienia si jakichkolwiek pyta w trakcie czytania
ksiki od uzyskania szybkiej odpowiedzi dzieli Ci tylko jeden e-mail.

R OZDZIA

1
Wprowadzenie
do platformy obliczeniowej
Android

Urzdzenia komputerowe staj si coraz bardziej spersonalizowane i przystpne.


Urzdzenia przenone w duej mierze przeksztaciy si w platformy obliczeniowe.
Telefony komrkowe nie su ju wycznie do rozmawiania od pewnego
czasu posiadaj moliwo przenoszenia danych oraz multimediw. Bez rnicy,
czy mwimy o telefonie, czy tablecie, urzdzenia przenone maj olbrzymie moliwoci obliczeniowe, uprawniajce je do uzyskania statusu komputera osobistego
(ang. Personal Computer PC). Wielu znanych producentw, takich jak ASUS,
HP czy Dell, produkuje rnorodne urzdzenia dziaajce pod kontrol systemu
Android. Konkurencja pomidzy poszczeglnymi systemami operacyjnymi, platformami obliczeniowymi, jzykami programowania oraz ramowymi modelami projektowania przenosi si na urzdzenia mobilne i coraz czciej wanie ich dotyczy.
Wida take zwikszenie si liczby programw tworzonych dla urzdze przenonych, jako e coraz wicej aplikacji uytkowych zaczyna mie odpowiedniki dla
urzdze mobilnych. W tej ksice zademonstrujemy sposoby zastosowania jzyka
Java do pisania programw dla urzdze obsugujcych platform Android firmy
Google (http://developer.android.com/index.html). Jest to rodowisko o jawnym
kodzie rdowym, suce do tworzenia aplikacji dla urzdze przenonych.
Android jest niezwykle interesujcy, poniewa wprowadza wiele nowych
paradygmatw projektowania struktury aplikacji (pomimo ogranicze
platformy mobilnej).

W tym rozdziale przedstawimy cechy Androida oraz narzdzia SDK Android.


Krtko scharakteryzujemy jego najwaniejsze elementy, w syntetyczny sposb zaprezentujemy tematyk poszczeglnych rozdziaw, pokaemy, w jaki sposb korzysta z kodu rdowego Androida, oraz przedstawimy zalety projektowania
aplikacji na t platform.

32

Android 3. Tworzenie aplikacji

Nowa platforma
dla nowego typu komputera osobistego
Wspania wieci dla programistw jest informacja, e wyspecjalizowane urzdzenia, takie
jak telefony komrkowe, mog zosta obecnie zaliczone do grona platform obliczeniowych
oglnego przeznaczenia (rysunek 1.1). Poczwszy od wersji Android 3.0, do tej listy moemy
oficjalnie doda tablety. W ten sposb programowanie dla urzdze przenonych staje si dostpne dla jzykw programowania oglnego przeznaczenia, dziki czemu powikszaj si zakres oraz udziay w rynku aplikacji przeznaczonych dla tych urzdze.

Rysunek 1.1. Handheld jest nowym rodzajem komputera osobistego

Platforma Android umoliwia urzeczywistnienie tej idei uniwersalnych komputerw w przypadku urzdze typu handheld. Jest to wszechstronne rodowisko z systemem operacyjnym
opartym na Linuksie, ktry zarzdza urzdzeniami, pamici oraz procesami. Biblioteki Java
Androida zapewniaj obsug funkcji telefonu, wideo, przetwarzania tekstu na mow, grafiki,
cznoci, programowania interfejsu uytkownika oraz wielu innych aspektw urzdzenia.
Chocia Android zosta zaprojektowany pod ktem urzdze przenonych oraz urzdze
typu tablet, posiada struktur w peni wyposaonego systemu operacyjnego. Firma
Google udostpnia t struktur programistom jzyka Java poprzez zestaw SDK
(ang. Software Development Kit zestaw do tworzenia oprogramowania) o nazwie
Android SDK. Praca na tym zestawie sprawia, e wcale nie ma si wraenia, i tworzy si
aplikacj dla urzdzenia przenonego, poniewa mona korzysta z wikszoci bibliotek
klas uywanych na stacji roboczej lub serwerze cznie z relacyjnymi bazami danych.

Zestaw Android SDK zapewnia obsug znacznej czci platformy Java Standard Edition (Java SE),
z wyjtkiem narzdzia Abstract Window Toolkit (AWT) oraz Swing. Zamiast tych narzdzi
Android SDK zosta zaopatrzony we wasny, obszerny, nowoczesny szkielet interfejsu uytkownika. Jzykiem programowania jest Java, zatem niezbdne jest rodowisko JVM (ang. Java
Virtual Machine wirtualna maszyna Javy), w ktrym odbywa si interpretowanie uruchomionego kodu bajtowego. Dziki rodowisku JVM uzyskujemy niezbdn optymalizacj, pozwalajc osign wydajno porwnywaln do wydajnoci aplikacji skompilowanych w takich jzykach, jak C oraz C++. Android zawiera wasne, zoptymalizowane rodowisko JVM,
umoliwiajce uruchomienie skompilowanych plikw klasy Java w celu okrelenia takich ogra-

Rozdzia 1 Wprowadzenie do platformy obliczeniowej Android

33

nicze urzdzenia typu handheld, jak pojemno pamici, szybko procesora oraz moc. Ta
wirtualna maszyna, nazwana Dalvik VM, zostanie dokadniej omwiona w podrozdziale Zapoznanie si ze rodowiskiem Dalvik VM.
Podobiestwo jzyka Java do jego wersji stosowanej w komputerach PC oraz jego
prostota w poczeniu z rozbudowan bibliotek klas Androida sprawiaj, e jest to
bardzo atrakcyjna platforma programistyczna.

Na rysunku 1.2 zosta ukazany stos programowy Androida (wicej informacji na ten temat
mona znale w podrozdziale Stos programowy Androida).

Rysunek 1.2. Wysokopoziomowy widok stosu programowego Androida

Pocztki historii Androida


Dla telefonw komrkowych stworzono wiele rnych systemw operacyjnych, takich jak
Symbian OS, Windows Mobile firmy Microsoft, Mobile Linux, iPhone OS (napisany na podstawie systemu Mac OS X), Moblin (firmy Intel) oraz wiele innych opatentowanych rodowisk.
Do tej pory aden z tych systemw nie sta si formalnym standardem. Dostpne interfejsy API
oraz rodowiska projektowania aplikacji dla urzdze przenonych s zbyt ograniczone i pozostaj w tyle w porwnaniu z analogicznymi strukturami dostpnymi dla stacji roboczych.

34

Android 3. Tworzenie aplikacji

W przeciwiestwie do pozostaych systemw operacyjnych, Android mia by otwarty, przystpny, o jawnym kodzie rdowym oraz, co waniejsze, mia zapewnia nowoczesny,
scentralizowany i spjny szkielet projektowania.
W 2005 roku firma Google wykupia mode przedsibiorstwo Android Inc., ktre rozpoczo
projektowanie platformy Android (rysunek 1.3). Wrd najwaniejszych pracownikw firmy
Android Inc. byli w owym czasie Andy Rubin, Rich Miner, Nick Sears oraz Chris White.

Rysunek 1.3. Pocztki historii Androida

Pod koniec 2007 roku grupa czoowych przedsibiorstw utworzya wok platformy Android
klaster przemysowy Open Handset Alliance (http://www.openhandsetalliance.com). Niektrzy
czonkowie tego to:
Sprint Nextel,
T-Mobile,
Motorola,
Samsung,
Sony Ericsson,
Toshiba,
Vodafone,
Google,
Intel,
Texas Instruments.
Do 2011 roku liczba czonkw tej grupy znacznie si zwikszya (jest ich obecnie ponad 80), co
mona sprawdzi na stronie zrzeszenia Open Handset Alliance.
Zgodnie z informacjami zawartymi w witrynie klastra jednym z jego celw jest szybkie wprowadzanie innowacji oraz lepsza odpowied na potrzeby konsumentw w przestrzeni mobilnej,
a jednym z pierwszych wanych osigni by Android. Zosta on zaprojektowany w celu zaspokojenia potrzeb operatorw sieci komrkowych, producentw urzdze oraz twrcw aplikacji. Czonkowie zrzeszenia zobowizali si udostpni t istotn wasno intelektualn poprzez zastosowanie w stosunku do Androida warunkw licencji Apache License 2.01.
1

Apache License 2.0 jest licencj wolnego oprogramowania autorstwa Apache Software Foundation.
Licencja ta dopuszcza uycie kodu rdowego zarwno na potrzeby wolnego oprogramowania,
jak i zamknitego oprogramowania komercyjnego przyp. red.

Rozdzia 1 Wprowadzenie do platformy obliczeniowej Android

35

Zestaw Android SDK zosta wydany jako wczesna wersja w listopadzie 2007 roku. We wrzeniu 2008 roku firma T-Mobile zapowiedziaa wydanie T-Mobile G1, pierwszego smartfonu
bazujcego na platformie Android. Kilka dni pniej firma Google ogosia wydanie zestawu
Android SDK Release Candidate 1.02. W padzierniku 2008 roku firma Google udostpnia kod
rdowy platformy Android w ramach licencji Apache. Pod koniec 2010 roku firma Google
wydaa zestaw Android SDK w wersji 2.3 dla smartfonw. Zestawowi temu nadano nazw kodow Gingerbread. W marcu 2011 roku zosta on zaktualizowany do wersji 2.3.3. Na pocztku
2011 roku zostaa wydana zoptymalizowana wersja Androida (w wersji 3.0) przeznaczona dla
tabletw, noszca nazw kodow Honeycomb. Jednym z pierwszych tabletw dziaajcych pod
kontrol tej wersji systemu operacyjnego jest Motorola XOOM.
Jednym z najwaniejszych celw twrcw Androida byo umoliwienie wsppracy rnych
aplikacji ze sob, a take wielokrotnego wykorzystywania skadnikw jednej aplikacji przez inn.
Takie uywanie fragmentw innych programw dotyczy nie tylko usug, lecz rwnie danych
oraz interfejsu UI (ang. User Interface interfejs uytkownika). W efekcie Android posiada
szereg funkcji konstrukcyjnych, dziki ktrym sta si w rzeczywisty sposb otwarty.
Android wczenie przycign wielu zwolennikw. Utrzyma rwnie zainteresowanie programistw, gdy posiada w peni rozwinite narzdzia, dziki ktrym mona wykorzystywa
poprzez model przetwarzania w chmurze (ang. cloud computing) udostpnione zasoby
sieciowe. Twrcy Androida usprawnili rwnie funkcjonowanie lokalnych magazynw danych
w samym urzdzeniu przenonym. Na ciepe przyjcie Androida wpyna rwnie moliwo
obsugi relacyjnych baz danych przez urzdzenia przenone.
Android w wersjach 1.0 oraz 1.1 (2008 rok) nie posiada moliwoci obsugi klawiatury programowej, wic urzdzenia musiay by wyposaone w fizyczne przyciski. Funkcja ta zostaa
wprowadzona w zestawie Android SDK 1.5 w kwietniu 2009 roku wraz z innymi dodatkami,
takimi jak zaawansowane moliwoci nagrywania multimediw, widety oraz aktywne foldery.
We wrzeniu 2009 roku pojawia si wersja 1.6 systemu Android, a w przecigu miesica zostaa
wydana wersja opatrzona numerem 2.0, dziki czemu nastpi przedwiteczny wysyp urzdze obsugujcych ten system. W tej wersji zaprezentowano funkcje zaawansowanego wyszukiwania danych oraz przetwarzania tekstu na mow.
Dziki obsudze jzyka HTML 5 system Android 2.0 posiada interesujce moliwoci wykorzystania stron WWW. Interfejs API kontaktw uleg znacznemu usprawnieniu. Dodano obsug
formatu Flash. Codziennie wydaje si coraz wicej aplikacji opartych na Androidzie, pojawiaj
si rwnie coraz nowsze rodzaje niezalenych sieciowych sklepw z aplikacjami. Mona ju
zakupi od dawna wyczekiwane komputery typu tablet, bazujce na systemie Android.
W wersji 2.3 Androida wrd najwaniejszych funkcji mona znale takie, jak zdalne usuwanie
zabezpieczonych danych przez administratorw, moliwo korzystania z aparatu oraz kamery
w warunkach sabego owietlenia czy korzystanie z hotspotw WiFi. Warto te zwrci uwag
na znaczn popraw wydajnoci, usprawnione dziaanie interfejsu Bluetooth, moliwo opcjonalnej instalacji aplikacji na karcie SD, moliwo korzystania ze rodowiska OpenGL ES 2.0,
usprawnione tworzenie kopii zapasowych, poprawion funkcj wyszukiwania, obsug standardu NFC (ang. Near Field Communication komunikacja bliskiego pola) umoliwiajc
przeprowadzanie operacji na kartach kredytowych, znacznie usprawnion obsug czujnikw
oraz wykrywania ruchu (podobnie jak w przypadku konsoli Wii), czat wideo oraz poprawiony
Android Market.
2

Release Candidate to niemal finalna wersja oprogramowania, w ktrej mog jeszcze zosta
wprowadzone drobne poprawki przyp. tum.

36

Android 3. Tworzenie aplikacji

Najnowsze wcielenie Androida, oznaczone numerem 3.0, jest przeznaczone do obsugi urzdze typu tablet oraz o wiele potniejszych procesorw dwurdzeniowych, takich jak Nvidia
Tegra2. Najwaniejsz funkcj udostpnion w tej wersji jest obsuga wikszych wywietlaczy.
Wprowadzono zupenie now koncepcj prezentowania treci w aplikacjach, zwan fragmentami. Te cechy stanowi o atrakcyjnoci Androida 3.0. Wprowadzono rwnie wicej funkcji
spotykanych dotychczas w komputerach stacjonarnych, na przykad klas ActionBar lub moliwo przecigania elementw. Znacznej modernizacji ulegy widety ekranu startowego. Dostpnych jest teraz wicej kontrolek interfejsu uytkownika. W zakresie grafiki trjwymiarowej
rodowisko OpenGL zostao zaopatrzone w interfejs Renderscript, dalej rozwijajcy wersj ES
2.0. Jest to znakomite wprowadzenie na rynek dla tabletw pracujcych w systemie Android.

Zapoznanie si ze rodowiskiem Dalvik VM


Z uwagi na swoje zaangaowanie w projekt Android firma Google skoncentrowaa si na wdraaniu technik optymalizacyjnych dla niskonapiciowych urzdze typu handheld. Urzdzenia
te s opnione w stosunku do ich wikszych odpowiednikw o jakie osiem do dziesiciu lat,
jeli chodzi o pami oraz szybko. Maj take ograniczon moc obliczeniow. Wymagania
dotyczce wydajnoci s bardzo surowe, przez co projektanci musz optymalizowa wszystkie
moliwe elementy aplikacji. Jeeli przyjrze si licie pakietw Androida, mona zauway, e
s one doskonale wyposaone i e jest ich bardzo wiele.
Powysze problemy sprawiy, e firma Google musiaa ponownie przyjrze si w nowym wietle
standardowej implementacji JVM. Osob odpowiedzialn za implementacj JVM firmy Google
jest Dan Bornstein, twrca Dalvik VM (Dalvik jest nazw islandzkiego miasteczka). Dalvik VM
pobiera wygenerowane pliki klas Java i przetwarza je na jeden lub wicej plikw wykonywalnych Dalvik (.dex). Nastpnie wykorzystuje powtarzajce si informacje z rnych plikw klas
i w ten sposb wydajnie zmniejsza zuycie pamici o poow w stosunku do tradycyjnego pliku
.jar (nieskompresowanego).
Firma Google usprawnia take zarzdzanie zbdnymi plikami w rodowisku Dalvik JVM, jednak
we wczesnych edycjach zdecydowaa si pomin kompilator JIT (ang. Just-In-Time Compiler;
kompilatory tego typu dokonuj tzw. kompilacji w locie, tumaczc kod bajtowy na kod maszynowy danego procesora przyp. red.). Zosta on dodany w wersji 2.3. Z raportw wynika, e
w pewnych miejscach potrafi on przyspieszy wydajno nominaln od dwch do piciu razy,
a w przypadku aplikacji oglnego przeznaczenia od 10 do 20%.
Dalvik VM wykorzystuje inn metod generowania kodu maszynowego, w ktrej podstawowymi jednostkami przechowywania danych s rejestry, a nie stosy. Firma Google ma nadziej,
e w ten sposb liczba instrukcji zostanie zmniejszona o 30%. Naley zauway, e w przypadku
Androida ostateczny plik wykonywalny oparty jest nie na kodzie bajtowym Java, a na plikach
.dex, wanie dziki rodowisku Dalvik VM. Oznacza to, e nie mona bezporednio uruchomi
kodu bajtowego Java; najpierw naley uruchomi pliki klas Java, a nastpnie dokona ich konwersji na linkowalne pliki .dex.
Takie restrykcyjne podejcie do problemw z wydajnoci dotyczy take pozostaych elementw zestawu Android SDK. Na przykad szeroko wykorzystuje on jzyk XML do definiowania
wygldu interfejsu uytkownika, ale przed zapisaniem na urzdzeniu pliki XML zostaj przeksztacone na pliki binarne. Android posiada specjalne mechanizmy umoliwiajce korzystanie
z tych plikw XML.

Rozdzia 1 Wprowadzenie do platformy obliczeniowej Android

37

Stos programowy Androida


Do tej pory zajmowalimy si histori Androida oraz jego funkcjami optymalizacyjnymi, w tym
take rodowiskiem Dalvik VM; wspomnielimy rwnie o dostpnoci stosu programowego
Java. W tym podrozdziale zajmiemy si aspektem projektowania w Androidzie. Najlepszym
miejscem, od ktrego mona rozpocz, jest rysunek 1.4.

Rysunek 1.4. Szczegowy stos programowy zestawu Android SDK

Rdzeniem platformy Android jest jdro Linuksa, zapewniajce obsug sterownikw urzdzenia,
dostp do zasobw, zarzdzanie energi oraz innymi zadaniami systemu operacyjnego. Sterowniki urzdzenia obejmuj ekran, aparat fotograficzny, klawiatur, WiFi, pami flash, audio oraz
komunikacj IPC (ang. Inter-Process Communication komunikacja midzyprocesowa; pojcie
to oznacza wymian danych pomidzy procesami systemu operacyjnego). Chocia rdzeniem
systemu jest Linux, wikszo aplikacji jeli nie wszystkie w urzdzeniach takich jak Motorola Droid jest projektowana w jzyku Java oraz uruchamiana w rodowisku Dalvik VM.
Na kolejnym poziomie, ponad rdzeniem Linuksa, umieszczono du liczb bibliotek C/C++,
wrd ktrych znajduj si biblioteki OpenGL, WebKit, FreeType, SSL (ang. Secure Sockets Layer;
protok sucy do bezpiecznej transmisji zaszyfrowanego strumienia danych), biblioteka
wykonawcza jzyka C (libc), SQLite oraz Media. Biblioteka systemowa jzyka C oparta na systemie Berkeley Software Distribution (BSD) jest dopasowana (zmniejszono j o ponad poow
w stosunku do pierwotnego rozmiaru) do urzdze posiadajcych wbudowany system bazujcy na Linuksie. Biblioteki multimediw s oparte na standardzie OpenCORE PacketVideo
(www.packetvideo.com/). Zapewniaj one obsug nagrywania oraz odtwarzania formatw audio

38

Android 3. Tworzenie aplikacji

i wideo. Biblioteka Surface Manager kontroluje dostp do systemu wywietlania, a take obsuguje grafik dwu- oraz trjwymiarow. Prawdopodobnie wraz z nowymi wersjami systemu bd
dodawane kolejne biblioteki natywne.
Biblioteka WebKit odpowiada za obsug przegldarki; to wanie ona obsuguje przegldarki Google Chrome oraz Safari. Biblioteka FreeType zajmuje si obsug czcionek. SQLite
(www.sqlite.org/) jest relacyjn baz danych dostpn na samym urzdzeniu przenonym. Jest
to take forma niezalenej, posiadajcej jawny kod rdowy technologii relacyjnej bazy danych, niezwizanej bezporednio z Androidem. Mona rwnie pobra narzdzia przeznaczone
dla bazy SQLite i uywa ich do baz danych Androida.
Wikszo szkieletu aplikacji uzyskuje dostp do tych bibliotek podstawowych poprzez rodowisko Dalvik VM, ktre stanowi bram do platformy Android. Jak zostao wyjanione w poprzednich podrozdziaach, rodowisko Dalvik zostao zoptymalizowane do jednoczesnego uruchamiania wielu instancji wirtualnych maszyn. Podczas uzyskiwania dostpu do podstawowych
bibliotek przez aplikacje Java kada z tych aplikacji otrzymuje wasn instancj maszyny VM.
Gwne biblioteki interfejsu API rodowiska Java w Androidzie obejmuj telefoni, zasoby,
lokacje, interfejs uytkownika, dostawcw (dane) treci oraz menedery pakietw (instalacja,
zabezpieczenia i tak dalej). Programici projektuj aplikacje dla uytkownika kocowego w grnej warstwie tego interfejsu API. Przykadami takich aplikacji s Home, Contacts, Phone,
Browser i tak dalej.
Android posiada rwnie wasn bibliotek do obsugi grafiki Google 2D Skia, ktr napisano w jzykach C i C++. Skia jest rwnie elementem rdzenia przegldarki Google Chrome. Jednak interfejsy API odpowiedzialne za grafik trjwymiarow bazuj w Androidzie na implementacji pakietu OpenGL ES grupy Khronos (http://www.khronos.org). Pakiet ten zawiera
podzbiory funkcji OpenGL, ktrych adresatami s wbudowane systemy.
Jeli za chodzi o multimedia, Android obsuguje najpopularniejsze formaty obrazw, dwikw
oraz wideo. Z perspektywy sieci bezprzewodowych dostpne s interfejsy API obsugujce sieci
Bluetooth, EDGE, 3G, WiFi oraz telefoni GSM (ang. Global System for Mobile Communication
globalny system komunikacji mobilnej), w zalenoci od parametrw sprztowych urzdzenia.

Projektowanie aplikacji uytkownika kocowego


za pomoc zestawu Android SDK
W niniejszym podrozdziale zostan zaprezentowane wysokopoziomowe interfejsy Java, suce
do projektowania aplikacji przeznaczonych dla uytkownika kocowego na platformie Android.
Krtko omwimy emulator Androida, podstawowe skadniki rodowiska Android, programowanie interfejsu UI, usugi, multimedia, telefoni, animacje oraz technologi OpenGL.

Emulator Androida
Zestaw Android SDK wyposaono we wtyczk organizacji Eclipse, nazwan narzdziami ADT
(ang. Android Development Tools narzdzia projektowe dla rodowiska Android). To rodowisko IDE (ang. Integrated Development Environment zintegrowane rodowisko projektowe)
suy do projektowania, usuwania bdw oraz testowania aplikacji Java (szczegowe informacje
na temat narzdzi ADT znajduj si w rozdziale 2.). Mona rwnie uywa zestawu Android
SDK bez narzdzi ADT; wykorzystywane s wtedy narzdzia wiersza polece. Obydwie metody

Rozdzia 1 Wprowadzenie do platformy obliczeniowej Android

39

pozwalaj na uruchamianie, usuwanie bdw oraz testowanie aplikacji. W 90% przypadkw


do projektowania aplikacji nie bdzie potrzebne nawet fizyczne urzdzenie. Emulator Androida
naladuje wikszo funkcji urzdzenia. Ograniczenia emulatora s zwizane z poczeniami
USB, wykonywaniem zdj, nagrywaniem sekwencji wideo, obsug suchawek, symulacj baterii, poczenia Bluetooth, WiFi, NFC oraz z obsug rodowiska OpenGL ES 2.0.
Emulator Androida spenia swoje zadanie dziki posiadajcej jawny kod rdowy technologii emulacji procesora, nazwanej QEMU, zaprojektowanej przez Fabricea Bellarda
(http://bellard.org/qemu). Ta sama technologia umoliwia emulacj jednego systemu operacyjnego wewntrz drugiego bez wzgldu na dziaanie procesora. QEMU pozwala na emulacj
na poziomie jednostki centralnej.
Dziki emulatorowi Androida procesor przechodzi w tryb technologii ARM (ang. Advanced
RISC Machine zaawansowana maszyna RISC). ARM jest architektur procesora 32-bitowego,
dziaajcego w oparciu o technologi RISC (ang. Reduced Instruction Set Computer komputer z ograniczonym zestawem instrukcji). Technologia ta umoliwia uzyskanie prostoty
projektowania oraz szybkoci poprzez ograniczenie liczby instrukcji w zestawie. Emulator powoduje uruchomienie systemu Linux, dostpnego na platformie Android, na takim symulowanym procesorze.
Technologia ARM jest szeroko stosowana w handheldach oraz w innych urzdzeniach
elektronicznych, w ktrych istotne jest niskie zuycie energii. Wikszo rynku urzdze
przenonych wykorzystuje procesory oparte na tej architekturze.
Wicej informacji dotyczcych emulatora mona znale w dokumentacji zestawu Android SDK,
dostpnej na stronie http://developer.android.com/guide/developing/tools/emulator.html.

Interfejs uytkownika na platformie Android


Android wykorzystuje szkielet interfejsu UI bardzo podobny do analogicznych szkieletw
uywanych w komputerach osobistych, jednak jest on nowoczeniejszy i bardziej asynchroniczny. W istocie interfejs UI Androida stanowi czwart generacj szkieletw interfejsw
uytkownika, jeeli uzna tradycyjny interfejs API systemu Windows, napisany w jzyku C,
za pierwsz generacj, a stworzony w jzyku C++ zbir klas MFC (ang. Microsoft Foundation
Classes) za drug. Szkielet UI biblioteki Swing, napisany w jzyku Java, mona uzna za trzeci generacj. Zapewniono tu o wiele wiksz elastyczno projektowania, ni to miao miejsce w przypadku zbioru MFC. Interfejs UI Androida, JavaFX, Microsoft Silverlight oraz jzyk XUL (ang.
XML User Interface Language jzyk XML interfejsu uytkownika) nale do czwartego pokolenia szkieletu UI, w ktrym interfejs uytkownika jest deklaracyjny oraz tworzony niezalenie.
W Androidzie programujemy aplikacje za pomoc wspczesnego paradygmatu
interfejsu UI, nawet jeli urzdzenie docelowe jest handheldem.

Programowanie w interfejsie UI Androida wie si z zadeklarowaniem interfejsu przy uyciu


plikw XML. Nastpnie mona wczyta takie definicje widokw XML jako okna w aplikacji
interfejsu uytkownika. Nawet listy opcji aplikacji s wczytywane z plikw XML. Ekrany lub
okna w Androidzie s czsto nazywane aktywnociami, skadajcymi si z wielu widokw,
potrzebnych uytkownikowi do wykonania czynnoci. Widoki s podstawowymi blokami budulcowymi interfejsu uytkownika, mona je ze sob czy w celu uzyskania grup widokw.
Widoki wykorzystuj w swoim wntrzu znajome Czytelnikowi pojcia kanw, rysowania oraz
interakcji uytkownika. Aktywno obsugujca takie zoone widoki, zawierajca widoki lub

40

Android 3. Tworzenie aplikacji

grupy widokw, jest logicznym, zamiennym skadnikiem interfejsu UI w Androidzie. W wersji


Android 3.0 wprowadzono now koncepcj interfejsu uytkownika fragmenty, dziki ktrej
programici mog dostosowywa widoki i funkcje do wywietlaczy tabletw. Tablety posiadaj
na tyle due ekrany, aby mona byo przeprowadza czynnoci wykorzystujce wiele obszarw
na ekranie, a fragmenty wprowadzaj wanie taki podzia wywietlacza na czci.
Jedn z najwaniejszych koncepcji szkieletu Androida jest zarzdzanie cyklem ycia okien aktywnoci. Do tego su protokoy, dziki ktrym Android moe modyfikowa stan okien aktywnoci, kiedy uytkownik je ukrywa, przywraca, zatrzymuje i zamyka. Te podstawowe zasady
stan si bardziej zrozumiae po przeczytaniu rozdziau 2., stanowicego rwnie wprowadzenie
do konfigurowania rodowiska projektowego Android.

Podstawowe skadniki Androida


Szkielet interfejsu UI, wraz z innymi elementami platformy Android, opiera si na nowej koncepcji, zwanej intencj (ang. intent). Intencja jest poczeniem takich pomysw, jak informacje
wywoywane w oknach, dziaania, modele publikowania i (lub) subskrybowania, komunikacja
midzyprocesowa, a take rejestry aplikacji. Poniej przedstawiono przykad wykorzystania klasy
Intent do wywoania lub uruchomienia przegldarki internetowej:
public static void invokeWebBrowser(Activity activity)
{
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.google.com"));
activity.startActivity(intent);
}

Ten przykadowy kod powoduje, e Android poprzez intencj otwiera odpowiednie okno,
w ktrym bdzie wywietlana zawarto strony WWW. W zalenoci od listy dostpnych przegldarek zainstalowanych w urzdzeniu Android wybierze najodpowiedniejsz. Intencje zostay
szczegowo omwione w rozdziale 5.
Android zapewnia take rozbudowan obsug zasobw, obejmujcych znajome kategorie elementw oraz plikw, na przykad cigi tekstowe oraz mapy bitowe, jak rwnie mniej znane
skadniki, takie jak definicje widoku oparte na jzyku XML. S one wykorzystywane w nowoczesny, atwy, intuicyjny oraz wygodny dla uytkownika sposb. Poniej zosta zamieszczony
przykad, w ktrym identyfikatory zasobw zostaj automatycznie wygenerowane dla zasobw
zdefiniowanych w plikach XML:
public final class R {
public static final class attr { }
public static final class drawable {
public static final int myanimation=0x7f020001;
public static final int numbers19=0x7f02000e;
}
public static final class id {
public static final int textViewId1=0x7f080003;
}
public static final class layout {
public static final int frame_animations_layout=0x7f030001;
public static final int main=0x7f030002;
}
public static final class string {

Rozdzia 1 Wprowadzenie do platformy obliczeniowej Android

41

public static final int hello=0x7f070000;


}
}

Kady identyfikator wygenerowany w tej klasie odpowiada albo elementowi znajdujcemu si


w pliku XML, albo samemu plikowi. Mona teraz uywa tych wygenerowanych identyfikatorw
zamiast definicji XML. Taka porednia droga jest bardzo przydatna podczas procesu lokalizacji
(w rozdziale 3. zostay szczegowo omwione zasoby oraz plik R.java).
Kolejn now koncepcj w Androidzie jest dostawca treci. Jest to abstrakcyjne ujcie rda
danych, przyjmujce form wystawcy oraz adresata usug RESTful. Stanowica jego podstaw
baza danych SQLite sprawia, e jest to potne narzdzie dla projektantw aplikacji. W rozdziale 4.
omwimy zagadnienie dostawcw treci, za w rozdziaach 3., 4. i 5. wyjanimy, w jaki sposb
intencje, zasoby oraz dostawcy treci gwarantuj otwarto platformy Android.

Zaawansowane koncepcje interfejsu uytkownika


Stwierdzilimy ju, e decydujc rol w opisie interfejsu UI Androida stanowi jzyk XML. Zobaczmy, w jaki sposb mona dziki niemu stworzy prosty ukad graficzny, zawierajcy widok
tekstu:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android>
<TextView android:id="@+id/textViewId"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>

W celu zaadowania ukadu graficznego do okna aktywnoci wykorzystamy identyfikator wygenerowany dla tego pliku XML (proces ten zostanie przeanalizowany w rozdziale 6.). Android
obsuguje take menu (to zagadnienie zostanie rozwinite w rozdziale 7.) od standardowych
do kontekstowych. Praca przy takich menu jest bardzo wygodna, gdy s one rwnie wczytywane jako pliki XML, a ich identyfikatory zasobw s generowane automatycznie. Menu mona
deklarowa w pliku XML w nastpujcy sposb:
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa wykorzystuje domyln kategori. -->


<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/menu_clear"
android:orderInCategory="10"
android:title="wyczy" />
<item android:id="@+id/menu_show_browser"
android:orderInCategory="5"
android:title="wywietl przegldark" />
</group>
</menu>

Android obsuguje okna dialogowe, z ktrych wszystkie s asynchroniczne. Mog one stanowi
nowego rodzaju wyzwanie dla projektantw przyzwyczajonych do synchronicznych, modalnych okien dialogowych stosowanych w niektrych szkieletach okienkowych. Menu zajmiemy
si szerzej w rozdziale 7., a oknami dialogowymi w rozdziale 8., powiconym rwnie sposobom korzystania z protokow asynchronicznych okien dialogowych.

42

Android 3. Tworzenie aplikacji

Android obsuguje take animacje w formie stosu interfejsu UI, opartego na widokach oraz rysowanych obiektach. S dostpne dwa rodzaje animacji: animowane przejcia (ang. tweening)
oraz rysowane klatka po klatce. Animowane przejcia polegaj na rysowaniu obrazw znajdujcych si pomidzy kluczowymi klatkami animacji. Osiga si to poprzez zmian rednich wartoci
w regularnych odstpach czasu oraz ponowne rysowanie powierzchni. Animacja klatka po
klatce wystpuje wtedy, gdy jest rysowana seria klatek w regularnych odstpach czasowych.
Android umoliwia wykorzystanie obydwu technik animacji poprzez wywoywanie zwrotne,
stosowanie interpolatorw oraz macierzy transformacji.
Ponadto istnieje moliwo zdefiniowania tych animacji w pliku zasobw XML. W poniszym
przykadzie seria ponumerowanych obrazw jest odtwarzana w animacji klatka po klatce:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/numbers11" android:duration="50" />
...
<item android:drawable="@drawable/numbers19" android:duration="50" />
</animation-list>

Dostpne biblioteki graficzne obsuguj standardowe macierze transformacji, dziki czemu


otrzymujemy funkcje skalowania, przenoszenia oraz obracania. Klasa Camera w bibliotece graficznej posiada funkcje gbi oraz rzutowania, pozwalajce na symulacj trzech wymiarw w paszczynie dwuwymiarowej (animacja zostaa omwiona w rozdziale 16.).
W Androidzie jest dostpna rwnie grafika trjwymiarowa dziki zaimplementowaniu standardw OpenGL ES 1.0 oraz 2.0. Standardy te, podobnie jak OpenGL, s paskimi interfejsami
API opartymi na jzyku C. Poniewa interfejs API rodowiska Android SDK jest zaprogramowany w jzyku Java, musi korzysta z cznika Java, eby uzyska dostp do biblioteki OpenGL ES.
Taki cznik zosta ju zdefiniowany w rodowisku Java ME poprzez specyfikacj JSR 239 dla
biblioteki OpenGL ES, a w Androidzie zostaa zaimplementowana ta sama technologia. Dla
kogo niezaznajomionego z programowaniem OpenGL moe to by do trudne do nauczenia si,
jednak w rozdziale 20. zostay omwione podstawy, pozwalajce na rozpoczcie programowania
w OpenGL dla systemu Android. Poczwszy od wersji 3.0 Androida, zosta wprowadzony mechanizm skryptowy, rozbudowujcy rodowisko OpenGL do wersji ES 2.0.
Android jest zwizany z wieloma koncepcjami, ktre kr wok idei informacji w zasigu rki,
dostpnych na ekranie startowym. Pierwsz tak koncepcj s aktywne foldery. Dziki nim istnieje moliwo opublikowania zbioru elementw na ekranie startowym w formie folderu.
Zawarto tego zbioru jest odwieana zgodnie z aktualizacj tworzcych go danych. Dane te
mog si znajdowa w telefonie lub pochodzi z internetu (aktywne foldery zostay omwione
w rozdziale 21.).
Drug koncepcj s widety ekranu startowego. S one stosowane do zobrazowania informacji
na ekranie startowym za pomoc interfejsu UI widetu. Informacje te mog ulega zmianie
w regularnych odstpach czasu. Za przykad moe posuy wywietlanie liczby wiadomoci
e-mail w bazie danych. W rozdziale 22. przedstawiono tematyk widetw ekranu startowego.
W wersji 3.0 widety ekranu startowego zostay bardziej rozbudowane i zawieraj widoki w postaci list, ktre s aktualizowane w trakcie modyfikowania danych zwizanych z tymi widetami.
Usprawnienia te zostay omwione w rozdziale 31.
Zintegrowane przeszukiwanie Androida jest trzeci koncepcj zwizan z ekranem
startowym. Za pomoc zintegrowanego przeszukiwania mona wyszukiwa zawarto zarwno w urzdzeniu, jak i w internecie. Przeszukiwanie Androida to take moliwo uruchamiania polece w oknie wyszukiwania. Koncepcj t zajmiemy si w rozdziale 23.

Rozdzia 1 Wprowadzenie do platformy obliczeniowej Android

43

W Androidzie zostaa rwnie udostpniona obsuga ekranu dotykowego oraz gestw opartych
na ruchach palcw po wywietlaczu urzdzenia. Kady rodzaj ruchu po ekranie mona zapisa
jako gest. Nastpnie taki gest zostaje powizany w aplikacji z okrelonymi czynnociami. Ekrany
dotykowe oraz gesty zostay dokadnie przeanalizowane w rozdziale 25.
Coraz waniejszym elementem obsugi urzdze mobilnych staj si czujniki. S one omwione w rozdziale 26.
Kolejn niezbdn innowacj wymagan dla urzdze mobilnych jest dynamiczna natura ich
konfiguracji. Na przykad bardzo atwo zmieni tryb przegldania pomidzy orientacj pionow
a poziom. Mona rwnie zadokowa urzdzenie przenone i korzysta z niego jak z laptopa.
W Androidzie 3.0 zostao wprowadzone pojcie fragmentw, dziki ktrym mona skutecznie
definiowa takie rnorodne zachowania. Rozdzia 29. zosta powicony fragmentom.
Omwilimy rwnie now funkcj paskw menu, zaprezentowan w wersji 3.0, ktrej przyjrzymy si w rozdziale 30. Koncepcja paskw menu w Androidzie zrwnuje j z paradygmatem
paskw menu znanych z komputerw stacjonarnych. W rozdziale 25. omwilimy funkcj przecigania elementw (dawny sposb), temat ten poruszono take w rozdziale 31. (przeciganie
elementw sposobem wprowadzonym w Androidzie 3.0).
Poza rodowiskiem Android SDK dostpnych jest wiele niezalenych innowacji, usprawniajcych oraz uatwiajcych proces projektowania. Przykadami s narzdzia XML/VM, PhoneGap oraz Titanium.

Skadniki usug w Androidzie


Podstawowym zaoeniem twrcw platformy Android jest bezpieczestwo. Zabezpieczenia s
uwzgldniane na wszystkich etapach cyklu ycia aplikacji poczwszy od rozwaa na temat
regu czasu projektowania, a skoczywszy na testach granicy rozruchowej. Kwesti bezpieczestwa
i uprawnie zajmiemy si w rozdziale 10.
W rozdziale 11. zaprezentujemy sposoby tworzenia oraz wykorzystywania usug w Androidzie,
w szczeglnoci usug HTTP. Zostanie w nim rwnie omwiona komunikacja midzyprocesowa (komunikowanie si aplikacji pomidzy sob w obrbie jednego urzdzenia).
Jednym z ciekawszych skadnikw zestawu Android SDK jest usuga zorientowana na pooenie.
Dziki niej projektanci interfejsw API mog wywietla oraz kontrolowa mapy, a take
otrzymywa w czasie rzeczywistym informacje o lokalizacji urzdzenia. Koncepcje te zostay
omwione w rozdziale 17.

Skadniki multimediw oraz telefonii w Androidzie


Android zosta zaopatrzony w interfejsy API pozwalajce na wykorzystywanie skadnikw
audio, wideo oraz telefonii. Interfejs API telefonii zostanie omwiony w rozdziale 18. W rozdziale 19. przyjrzymy si dokadnie interfejsom API audio i wideo. Wraz z wersj 2.0 do Androida
wprowadzono silnik przetwarzania tekstu na mow Pico. Zosta mu powicony rozdzia 24.
Ostatnia, lecz nie najmniej istotna rzecz, o ktrej naley wspomnie, to fakt, e Android czy te
wszystkie koncepcje w aplikacji poprzez utworzenie pliku XML, definiujcego zawarto pakietu
tej aplikacji. Plik ten jest znany jako manifest aplikacji (AndroidManifest.xml). Przykad:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ai.android.HelloWorld"

44

Android 3. Tworzenie aplikacji

android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".HelloWorld"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

W pliku manifecie zostaj zdefiniowane aktywnoci, nastpuje rejestracja dostawcw treci oraz
usug, a take s deklarowane uprawnienia. W dalszej czci ksiki podczas omawiania rnorodnych koncepcji bd pojawiay si kolejne szczegy dotyczce pliku manifestu.

Pakiety Java w Androidzie


Mona szybko oceni zawarto platformy Android, przyjrzawszy si strukturze pakietw Java.
Poniewa Android rni si od standardowej wersji JDK, warto wiedzie, jakie skadniki s obsugiwane, a jakie zostay pominite. Ponisza lista stanowi krtkie omwienie wanych pakietw doczonych do zestawu Android SDK:

android.app. Jest to implementacja modelu Application dla Androida. Wrd


podstawowych klas mona znale Application, definiujc semantyk rozpoczcia
oraz zatrzymania aplikacji. Dostpnych jest rwnie wiele innych klas zwizanych
z aktywnoci, fragmentw, kontrolek, okien dialogowych, ostrzee oraz powiadomie.

android.bluetooth. Dostarcza du liczb klas pozwalajcych na wykorzystanie


funkcji Bluetooth. Wrd gwnych klas naley wymieni BluetoothAdapter,
BluetoothDevice, BluetoothSocket, BluetoothServerSocket oraz BluetoothClass.
Klasa BluetoothAdapter pozwala kontrolowa lokalnie zainstalowany adapter
Bluetooth. Mona na przykad go wczy, wyczy lub skonfigurowa w stan
wykrywania. Klasa BluetoothDevice symbolizuje zdalne urzdzenie Bluetooth,
do ktrego nastpuje podczenie. S wykorzystywane dwa gniazda Bluetooth do
ustanowienia poczenia pomidzy urzdzeniami. Klasa BluetoothClass reprezentuje
rodzaj urzdzenia, z ktrym zostaje ustanowione poczenie.

android.content. Pakiet ten stanowi implementacj koncepcji dostawcw treci.


Dziki nim mona wyodrbni dostp do danych z magazynu danych. W pakiecie tym
mieszcz si rwnie podstawowe koncepcje dotyczce intencji oraz identyfikatorw
URI (ang. Uniform Resource Identifier ujednolicony identyfikator zasobw) Androida.

android.content.pm. Implementacja klas zwizanych z menederem pakietw.


Meneder pakietw zbiera informacje na temat uprawnie, zainstalowanych
pakietw, dostawcw oraz usug, aplikacji oraz skadnikw, takich jak aktywnoci.

android.content.res. Zapewnia dostp do ustrukturyzowanych oraz


nieustrukturyzowanych plikw zasobw. Podstawowymi klasami s AssetManager
(dla nieustrukturyzowanych zasobw) oraz Resources.

Rozdzia 1 Wprowadzenie do platformy obliczeniowej Android

45

android.database. Wprowadza koncepcj abstrakcyjnej bazy danych.


Podstawowym interfejsem jest biblioteka Cursor.

android.database.sqlite. Wprowadza koncepcje z pakietu android.database w formie


fizycznej bazy danych SQLite. Podstawowymi klasami s tutaj SQLiteCursor,
SQLiteDatabase, SQLiteQuery, SQLiteQueryBuilder oraz SQLiteStatement,
jednak wiksza cz interakcji bdzie przeprowadzana z klasami abstrakcyjnego
pakietu android.database.

android.gesture. Pakiet ten obejmuje wikszo klas i interfejsw wymaganych do


pracy ze zdefiniowanymi gestami. Podstawowe klasy to: Gesture, GestureLibrary,
GestureOverlayView, GestureStore, GestureStroke i GesturePoint. Klasa
Gesture jest zbiorem klas GestureStroke oraz GesturePoint. Gesty s przechowywane
w klasie GestureLibrary. Biblioteki gestw znajduj si w klasie GestureStore.
Kady gest posiada nazw, dziki czemu mona go zidentyfikowa jako dziaanie.

android.graphics. Zostay tu umieszczone klasy Bitmap, Canvas, Camera, Color,


Matrix, Movie, Paint, Path, Rasterizer, Shader, SweepGradient oraz TypeFace.

android.graphics.drawable. Wprowadza implementacj protokow rysowania


oraz rysunki ta, a take umoliwia animowanie rysowanych obiektw.

android.graphics.drawable.shapes. Wprowadza takie biblioteki ksztatw,


jak ArcShape, OvalShape, PathShape, RectShape oraz RoundRectShape.

android.hardware. Zaimplementowane zostaj klasy zwizane z fizycznym


aparatem fotograficznym. Klasa Camera jest reprezentacj sprztowego aparatu,
podczas gdy klasa android.graphics.Camera dotyczy koncepcji graficznej,
zupenie niezwizanej z fizycznym urzdzeniem.

android.location. Przechowuje klasy Address, GeoCoder, Location, LocationManager


oraz LocationProvider. Klasa Address reprezentuje uproszczony jzyk XAL (ang.
Extensible Address Language rozszerzalny jzyk adresw). Dziki klasie GeoCoder
mona uzyska wsprzdne dugoci oraz szerokoci geograficznej po wpisaniu
adresu oraz odwrotnie. Klasa Location symbolizuje dugo i szeroko geograficzn.

android.media. Zawiera klasy MediaPlayer, MediaRecorder, Ringtone, AudioManager


oraz FaceDetector. Klasa MediaPlayer, zapewniajca obsug strumienia danych,
suy do odtwarzania plikw audio i wideo. Z kolei klasa MediaRecorder umoliwia
rejestrowanie dwiku oraz obrazu. Dziki klasie Ringtone istnieje moliwo nagrywania
krtkich fragmentw dwikowych, sucych jako dzwonki lub powiadomienia.
Do kontroli poziomu gonoci wykorzystuje si klas AudioManager, natomiast
do rozpoznawania twarzy na mapie bitowej uywana jest klasa FaceDetector.

android.net. Dziki temu pakietowi zostaj zaimplementowane podstawowe


sieciowe interfejsy API poziomu gniazda. Wrd podstawowych klas znajduj si
Uri, ConnectivityManager, LocalSocket oraz LocalServerSocket. Warto rwnie
zauway, e Android obsuguje protok HTTPS na poziomie przegldarki oraz
w warstwie sieciowej. W przegldarce jest rwnie obsugiwany jzyk JavaScript.

android.net.wifi. Zarzdza poczeniami WiFi. Podstawowymi klasami s WifiManager


oraz WifiConfiguration. Klasa WifiManager jest odpowiedzialna za wywietlanie
listy skonfigurowanych sieci oraz obecnie aktywnej sieci WiFi.

46

Android 3. Tworzenie aplikacji

android.opengl. Zawiera uytkowe klasy, powizane z operacjami OpenGL ES


w wersjach 1.0 i 2.0. Podstawowe klasy OpenGL ES s zaimplementowane w rnych
zestawach pakietw, zapoyczonych ze specyfikacji JSR 239. Tymi pakietami
s javax.microedition.khronos.opengles, javax.microedition.khronos.egl oraz
javax.microedition.khronos.nio. S to cienkie pakiety otaczajce implementacj
OpenGL ES grupy Khronos, napisan w jzykach C oraz C++.

android.os. Pakiet ten reprezentuje usugi systemowe dostpne poprzez jzyk


programowania Java. Niektre z istotnych klas to BatteryManager, Binder,
FileObserver, Handler, Looper oraz PowerManager. Binder jest klas umoliwiajc
komunikacj midzyprocesow. Dziki klasie FileObserver system ledzi zmiany
dokonywane w plikach. Klasy typu Handler s wykorzystywane do uruchamiania
zada w wtkach wiadomoci, natomiast klas Looper uywa si do uruchamiania
wtkw wiadomoci.

android.preference. Dziki temu pakietowi uytkownicy mog zarzdza ustawieniami


danej aplikacji w jednolity sposb. Podstawowymi klasami s PreferenceActivity,
PreferenceScreen, a take rne klasy zwizane z samymi ustawieniami, na przykad
CheckBoxPreference oraz SharedPreferences.

android.provider. Skada si z zestawu predefiniowanych dostawcw treci,


przylegajcych do interfejsu android.content.ContentProvider. Do tego pakietu
przynale klasy Contacts, MediaStore, Browser oraz Settings. W tym zestawie
interfejsw oraz klas s przechowywane metadane przygotowane dla podstawowych
struktur danych.

android.sax. Znajduje si tu wydajny zestaw analitycznych klas SAX (ang. Simple


API for XML prosty interfejs API dla jzyka XML). Podstawowymi klasami s
Element, RootElement oraz pewna liczba interfejsw ElementListener.

android.speech. Znajduj si tu stae stosowane przy rozpoznawaniu mowy.

android.speech.tts. Umoliwia konwersj tekstu na mow. Gwn klas jest


TextToSpeech. Istnieje moliwo wpisania tekstu oraz przekazania wystpieniu tej
klasy instrukcji przeczytania tekstu. Mona skonfigurowa wywoywanie monitora,
na przykad po zakoczeniu czytania. W Androidzie jest zastosowany silnik Pico
TTS (ang. Text to Speech) firmy SVOX.

android.telephony. Obejmuje klasy CellLocation, PhoneNumberUtils oraz


TelephonyManager. Klasa CellLocation pozwala na okrelenie lokalizacji urzdzenia,
numeru telefonu, nazwy operatora sieci, rodzaju sieci, rodzaju telefonu oraz numeru
seryjnego SIM (ang. Subscriber Identity Module modu identyfikacji abonenta).

android.telephony.gsm. Pozwala na uzyskanie pooenia geograficznego urzdzenia


na podstawie odlegoci od wiey operatora, a take przechowuje klasy obsugujce
wysyanie wiadomoci SMS. W nazwie pakietu widnieje skrt GSM, gdy ta
wanie technologia (globalnego systemu komunikacji mobilnej) jako pierwsza
zdefiniowaa standard wysyania danych w formacie SMS.

android.telephony.cdma. Wprowadza obsug telefonii CDMA.

android.text. Zawiera klasy przetwarzania tekstu.

Rozdzia 1 Wprowadzenie do platformy obliczeniowej Android

47

android.text.method. Obecne tu klasy umoliwiaj wprowadzanie tekstu w celu


kontroli zmian.

android.text.style. Dostarcza wiele klas, dziki ktrym mona zastosowa style


do wprowadzanego tekstu.

android.utils. Mieci klasy Log, DebugUtils, TimeUtils oraz Xml.

android.view. S tu umieszczone klasy Menu, View, ViewGroup, zestaw obiektw


nasuchujcych oraz metod zwrotnych.

android.view.animation. Zawiera obsug animacji przej. Wrd gwnych klas


znajduj si: klasa Animation, zestaw interpolatorw animacji oraz zestaw klas
przeznaczonych dla animatora, midzy innymi AlphaAnimation, ScaleAnimation,
TranslationAnimation i RotationAnimation. W wersji Android 3.0 zosta wprowadzony
pakiet android.animation, ktry peni podobn rol, ale posiada szersze
zastosowanie, poniewa dziaa na obiektach, a nie na widokach.

android.view.inputmethod. Zostaje dziki niemu zaimplementowana architektura


szkieletu metody wprowadzania danych.

android.webkit. Przechowuje klasy reprezentujce przegldark internetow.


Podstawowymi klasami s WebView, CacheManager oraz CookieManager.

android.widget. Znajduj si tu wszystkie kontrolki interfejsu UI, przeniesione


w wikszoci z klasy View. Gwnymi klasami s Button, Checkbox, Chronometer,
AnalogClock, DatePicker, DigitalClock, EditText, ListView, FrameLayout,
GridView, ImageButton, MediaController, ProgressBar, RadioButton, RadioGroup,
RatingButton, Scroller, ScrollView, Spinner, TabWidget, TextView, TimePicker,
VideoView oraz ZoomButton.

com.google.android.maps. Posiada klasy niezbdne do obsugi Google Maps,


czyli MapView, MapController oraz MapActivity.

To cz z najwaniejszych pakietw, specyficznych dla Androida. Patrzc na t list, mona


dostrzec gbi podstawowej platformy Androida.
cznie interfejs API Androida zawiera powyej 40 pakietw oraz ponad 700 klas,
a z kad now wersj te liczby stale rosn.

Ponadto Android posiada olbrzymi liczb pakietw w przestrzeni nazw java.*. Wyrni mona
pakiety awt.font, io, lang, lang.annotation, lang.ref, lang.reflect, math, net, nio, nio.channels,
nio.channels.spi, nio.charset, security, security.acl, security.cert, security.interfaces, security.spec,
sql, text, util, util.concurrent, util.concurrent.atomic, util.concurrent.locks, util.jar, util.logging,
util.prefs, util.regex oraz util.zip. Nastpne pakiety pochodz z przestrzeni nazw javax: crypto,
crypto.spec, microedition.khronos.egl, microedition.khronos.opengles, net, net.ssl, security.auth,
security.auth.callback, security.auth.login, security.auth.x500, security.cert, sql, xml oraz xmlparsers.
Jakby tego byo mao, Android zosta wyposaony w wiele pakietw z takich przestrzeni nazw, jak
org.apache.http.*, a take org.json, org.w3c.dom, org.xml.sax, org.xml.sax.ext, org.xml.sax.helpers,
org.xmlpull.v1 oraz org.xmlpull.v1.sax2. Razem te liczne pakiety tworz rozbudowan platform
obliczeniow, umoliwiajc pisanie aplikacji dla urzdze typu handheld.

48

Android 3. Tworzenie aplikacji

Wykorzystanie zalet kodu rdowego Androida3


We wczesnych edycjach Androida jego dokumentacja bya miejscami nieco niezadowalajca.
Kod rdowy Androida mg zosta wykorzystany do uzupenienia brakw.
Szczegy dotyczce dystrybucji rda Androida zostay opublikowane na stronie http://source.
android.com. Kod rdowy zosta ujawniony w padzierniku 2008 roku. Jednym z celw zrzeszenia OHA byo uczynienie z Androida darmowej oraz cakowicie konfigurowalnej platformy
dla urzdze przenonych.
Jak zostao wyjanione, Android jest platform, a nie pojedynczym projektem. Zakres oraz liczb
projektw mona zobaczy na stronie http://source.android.com/projects.
Kod rdowy Androida oraz wszystkie projekty z nim zwizane s zarzdzane przez system
kontroli kodu rdowego Git. Git (http://git.or.cz/) jest systemem o jawnym kodzie rdowym, zaprojektowanym tak, eby szybko i wygodnie mc zajmowa si duymi oraz maymi
projektami. Pod kontrol systemu Git znajduje si take jdro Linuksa oraz projekt Ruby on
Rails. Pen list projektw zwizanych z Androidem, znajdujcych si w repozytorium Git, mona
znale na stronie http://android.git.kernel.org/.
Te projekty mona pobiera za pomoc narzdzi oferowanych przez system Git, opisanych na stronie produktu. Niektre z najwaniejszych projektw dotycz rodowiska Dalvik,
frameworks/base (plik android.jar), jdra Linuksa oraz wielu zewntrznych bibliotek, takich jak
biblioteki Apache HTTP (apache-http). Przechowywane s tu rwnie podstawowe aplikacje
Androida. Oto niektre z nich: AlarmClock, Browser, Calculator, Calendar, Camera, Contacts,
Email, GoogleSearch, HTML Viewer, IM, Launcher, Mms, Music, PackageInstaller, Phone,
Settings, SoundRecorder, Stk, Sync, Updater oraz VoiceDialer.
Wrd projektw Androida znajduj si take projekty Provider. Projekty Provider s niczym
bazy danych Androida, czce dane z usugami RESTful. Wrd tych projektw mona znale
takie, jak CalendarProvider, ContactsProvider, DownloadProvider, DrmProvider, GoogleContactsProvider, GoogleSubscribedFeedsProvider, ImProvider, MediaProvider, SettingsProvider,
Subscribed FeedsProvider oraz TelephonyProvider.
Programici bd najbardziej zainteresowani kodem rdowym, znajdujcym si w pliku
android.jar (w przypadku pobrania caej platformy naley zajrze do dokumentacji dostpnej pod
adresem http://source.android.com/source/downloading.html). Kod rdowy tego pliku mona
pobra z nastpujcego adresu: http://git.source.android.com/?p=platform/frameworks/base.git;
a=snapshot; h=HEAD;sf=tgz.
Z powyszego adresu mona pobiera inne projekty Git. W systemie Windows do rozpakowania
tego pliku suy aplikacja pkzip. Chocia mona pobra i rozpakowa pliki zawierajce kod rdowy, by moe wygodniej bdzie przejrze je w internecie, jeeli nie ma potrzeby sprawdzania tego kodu w rodowisku IDE. System Git pozwala przeglda pliki online. Na przykad
3

W trakcie tumaczenia ksiki strona http://android.git.kernel.org zostaa zaatakowana przez nieznanych


sprawcw i od tego czasu wszelkie dostpne na niej zasoby s niedostpne dla uytkownikw.
Prawdopodobnie strona ta zostanie ponownie oddana do uytku w niedalekiej przyszoci, do tego
czasu osoby pragnce przejrze kod rdowy Androida musz skorzysta z alternatywnego rda.
Na stronie http://source.android.com/source/downloading.html znajdziemy instrukcj korzystania
z narzdzia Repo, pozwalajcego na pobieranie i przegldanie wspomnianego kodu rdowego
przyp. tum.

Rozdzia 1 Wprowadzenie do platformy obliczeniowej Android

49

pliki rdowe android.jar mona przejrze pod adresem http://android.git.kernel.org/


?p=platform/frameworks/base.git;a=summary.
Jednak po odwiedzeniu tej strony naley wykona jeszcze kilka czynnoci. Trzeba wybra opcj
grep z rozwijanego menu i wpisa nazw w polu wyszukiwania. Nastpnie na licie wynikw
naley klikn nazw pliku, eby otworzy jego kod rdowy w przegldarce. Mona w ten
sposb szybko zajrze do kodu rdowego.
Czasami poszukiwanego pliku moe nie by w katalogu frameworks/base lub okrelonym projekcie. W takim wypadku naley znale list projektw i przeszukiwa kady z nich krok po
kroku. Taka lista jest dostpna tutaj: http://android.git.kernel.org/.
Nie mona przeszukiwa wszystkich projektw za pomoc polecenia grep, naley zatem si
dowiedzie, do jakich kategorii Androida nale poszczeglne projekty. Na przykad biblioteki
graficzne projektu Skia s dostpne tutaj: http://android.git.kernel.org/?p=platform/external/
skia.git;a=summary.
Plik SkMatrix.cpp zawiera kod rdowy macierzy transformacyjnej, przydatnej w animacji:
http://android.git.kernel.org/?p=platform/external/skia.git;a=blob;f=src/core/SkMatrix.cpp.

Przykadowe projekty zawarte w ksice


W niniejszej ksice umiecilimy bardzo duo dziaajcych przykadowych projektw. Od rozdziau 2. a do 28. wszystkie informacje dotycz aplikacji tworzonych dla smartfonw, i pod tym
wanie ktem wszelkie zawarte w nich projekty byy testowane na rnych wersjach Androida,
skoczywszy na wersji 2.3. Jakby nie patrze, na rynku istnieje nieprzyzwoicie wiele rodzajw
smartfonw obsugujcych system Android.
Wikszo projektw, jeli nie wszystkie, bdzie dziaaa w niezmienionej formie na tabletach
obsugujcych system Android 3.0, chocia mog one wyglda niezgodnie z oczekiwaniami.
W trakcie tworzenia projektw naszym gwnym celem byo zaprezentowanie poszczeglnych
koncepcji oraz pakietw systemu Android, a w niektrych przypadkach rwnie zademonstrowanie dziaania pewnych funkcji w starszych wersjach Androida. atwo wdroy te koncepcje
podczas tworzenia aplikacji dla tabletw. W razie potrzeby nasze projekty bez problemu zintegrowayby si z innymi funkcjami specyficznymi dla wersji 3.0 Androida, jednak doczenie tych
nowych funkcji w projektach odcignoby nasz uwag od koncepcji, ktre pragniemy objani.
Rozdziay 29. 31. zostay powicone Androidowi w wersji 3.0, wic zawarte w nich projekty
zostay specjalnie zaprojektowane i przetestowane w tym systemie.

Podsumowanie
W tym rozdziale chcielimy wzbudzi u Czytelnika zainteresowanie Androidem. Osoby programujce w rodowisku Java maj znakomit okazj, aby wycign korzyci z tej ekscytujcej,
rozbudowanej platformy obliczeniowej oglnego przeznaczenia. Zapraszamy w podr przez
reszt ksiki celem tej wdrwki jest dogbne zrozumienie zestawu Android SDK.

50

Android 3. Tworzenie aplikacji

R OZDZIA

2
Konfigurowanie rodowiska
programowania

W poprzednim rozdziale omwilimy histori Androida oraz zarysowalimy koncepcje, ktre zostan omwione w dalszej czci ksiki. W tym momencie Czytelnik
prawdopodobnie moe zechcie ju zaj si kodem. Rozpoczniemy od przedstawienia elementw potrzebnych do tworzenia aplikacji w rodowisku Android
SDK oraz od przygotowania tego rodowiska. Nastpnie szczegowo przeanalizujemy aplikacj Witaj, wiecie! oraz rozoymy na czynniki pierwsze nieco bardziej
zoony fragment kodu. W dalszej kolejnoci objanimy cykl ycia aplikacji w Androidzie, a na kocu powicimy chwil tematowi wyszukiwania bdw w aplikacji
za pomoc narzdzi AVD (ang. Android Virtual Devices wirtualne urzdzenia
Androida).
Do tworzenia aplikacji przeznaczonych dla Androida wymagane jest posiadanie
zestawu JDK (ang. Java SE Development Kit zestaw do projektowania w rodowisku Java SE), rodowiska Android SDK oraz rodowiska projektowego. Inaczej mwic, mona pisa aplikacje za pomoc najprostszego edytora tekstowego, ale na
potrzeby tworzenia projektw omwionych w tej ksice lepsze bdzie powszechnie
dostpne rodowisko IDE Eclipse. Android SDK wymaga zestawu JDK w wersji co
najmniej 5 (korzystalimy z JDK 6) oraz rodowiska Eclipse w wersji nie wczeniejszej ni 3.4 (uywalimy wersji Eclipse 3.5, noszcej nazw Galileo, oraz wersji 3.6,
nazwanej Helios).
eby uatwi sobie ycie, mona zainstalowa narzdzia ADT (ang. Android Development Tools narzdzia projektowe Androida). Jest to wtyczka rodowiska Eclipse,
umoliwiajca tworzenie aplikacji przeznaczonych dla Androida w rodowisku IDE
Eclipse. W istocie wszystkie przykady w tej ksice zostay zaprojektowane w rodowisku Eclipse za pomoc narzdzi ADT.
Zestaw Android SDK skada si z dwch gwnych skadnikw. S to narzdzia i pakiety. Podczas jego pierwszej instalacji otrzymujemy do dyspozycji wycznie podstawowe narzdzia. S to przewanie pliki wykonywalne oraz pomocnicze, wspierajce proces tworzenia aplikacji. Pakietami nazywamy pliki, ktre s unikatowe
dla danej wersji Androida (nazywanej platform), lub dodatki przeznaczone dla
okrelonej platformy. Do platform zaliczamy Androida w wersjach od 1.5 do 3.0.

52

Android 3. Tworzenie aplikacji

Na dodatki skadaj si takie narzdzia, jak interfejs API Google Maps, walidator licencji
przeznaczonych dla Android Market (ang. Market License Validator), a nawet dodatki pochodzce od producentw telefonw, jak na przykad wtyczka Galaxy Tab firmy Samsung.
Po zainstalowaniu pakietu SDK bdzie mona nastpnie wykorzysta jedno z narzdzi do
pobrania i skonfigurowania platform oraz dodatkw. Zaczynajmy!

Konfigurowanie rodowiska
eby mc tworzy aplikacje dla Androida, naley zapewni sobie rodowisko projektowe.
W tym podrozdziale zajmiemy si omwieniem procesu pobierania aplikacji JDK 6, rodowiska
Eclipse, zestawu Android SDK (narzdzia i pakiety) oraz dodatku ADT. Pomoemy take skonfigurowa rodowisko Eclipse, tak aby mona byo w nim tworzy aplikacje dla Androida.
rodowisko Android SDK jest kompatybilne z systemami Windows (Windows XP, Windows
Vista oraz Windows 7), Mac OS X (jedynie z procesorami Intel) oraz Linux (rwnie wycznie
z procesorami Intel). W tym rozdziale omwimy proces konfigurowania rodowiska we wszystkich wymienionych rodzajach systemw (w przypadku Linuksa jedynie dla wariantu Ubuntu).
W kolejnych rozdziaach nie bdziemy si zajmowa rnicami pomidzy poszczeglnymi
systemami operacyjnymi.

Pobieranie zestawu JDK 6


Pierwszym potrzebnym skadnikiem jest zestaw Java Development Kit. rodowisko Android
SDK wymaga co najmniej wersji 5 zestawu JDK; przykady w ksice byy tworzone z wykorzystaniem wersji 6. Dla systemw Windows aplikacja JDK 6 jest dostpna na oficjalnej
stronie firmy Sun (www.oracle.com/technetwork/java/javase/downloads/index.html) naley j pobra i zainstalowa. Wystarczy edycja standardowa aplikacji JDK, jej wersje Bundle nie s wymagane. Zestaw JDK dla systemu Mac OS X mona znale w witrynie Apple
(http://developer.apple.com/java/download/); naley stamtd wybra plik dla odpowiedniej
wersji systemu i zainstalowa go. Aby uzyska dostp do zestawu JDK, naley bezpatnie zarejestrowa si jako programista, a na stronie pobra klikn odnonik do Javy znajdujcy si
po prawej stronie okna. eby zainstalowa JDK w systemie Linux, naley otworzy okno terminalu i wpisa nastpujce polecenie:
sudo apt-get install sun-java6-jdk

Polecenie to spowoduje zainstalowanie aplikacji JDK oraz wszystkich wymaganych dodatkowych skadnikw, takich jak rodowisko JRE (ang. Java Runtime Environment rodowisko uruchomieniowe Java). Jeeli tak si nie stanie, oznacza to prawdopodobnie, e naley
doda nowe rdo oprogramowania (ang. software source) i sprbowa wykona powysze
polecenie ponownie. Na stronie https://help.ubuntu.com/community/Repositories/Ubuntu wyjaniono zasad dziaania rde oprogramowania oraz sposb dodawania poczenia do oprogramowania pochodzcego z niezalenego rda. Proces ten jest odmienny dla rnych wersji
Linuksa. Po jego przeprowadzeniu naley sprbowa ponownie wykona widoczne powyej
polecenie.
Wraz z wprowadzeniem wersji Ubuntu 10.04 (Lucid Lynx) zalecane jest korzystanie raczej
z zestawu OpenJDK, a nie Oracle/Sun JDK. Aby go zainstalowa, stosujemy nastpujce polecenie:
sudo apt-get install openjdk-6-jdk

Rozdzia 2 Konfigurowanie rodowiska programowania

53

Jeeli aplikacja nie zostanie znaleziona, naley tak jak zostao wczeniej wspomniane skonfigurowa oprogramowanie wydane przez niezalenego wydawc i ponownie uruchomi polecenie. Spowoduje to automatyczne dodanie wszystkich pakietw wymaganych przez zestaw JDK.
Istnieje moliwo jednoczesnego posiadania zestaww OpenJDK oraz Oracle/Sun JDK. W celu
przeczania pomidzy aktywnymi wersjami rodowiska Java zainstalowanymi w systemie
Ubuntu uruchamiamy ponisze polecenie w interpreterze powoki:
sudo update-alternatives --config java

a nastpnie wybieramy wersj rodowiska Java, ktra ma pozosta domyln.


Po zainstalowaniu rodowiska JDK naley skonfigurowa zmienn rodowiskow JAVA_HOME,
tak eby wskazywaa folder instalacyjny JDK. Na komputerze z zainstalowanym systemem
Windows XP mona tego dokona, otwierajc menu Start i klikajc prawym przyciskiem
myszy ikon Mj komputer. Z menu naley wybra opcj Waciwoci, a nastpnie przej do
zakadki Zaawansowane i klikn przycisk Zmienne rodowiskowe. W dalszej kolejnoci
trzeba klikn przycisk Nowa, eby doda zmienn, lub Edycja, by poprawi istniejc zmienn.
Warto zmiennej JAVA_HOME bdzie wygldaa mniej wicej nastpujco: C:\Program Files\Java\jdk1.6.0_23. W systemach Windows Vista oraz Windows 7 uzyskiwanie dostpu do
okna Zmienne rodowiskowe wyglda nieco inaczej. Trzeba wybra menu Start, prawym przyciskiem myszy klikn ikon Komputer i wybra z menu opcj Waciwoci, wybra cze Zaawansowane ustawienia systemu, a nastpnie uy przycisku Zmienne rodowiskowe. Kolejne
czynnoci wymagane do ustanowienia lub zmiany zmiennej rodowiskowej JAVA_HOME s
identyczne jak w systemie Windows XP. W systemie Mac OS X zmienn rodowiskow
JAVA_HOME konfiguruje si w pliku .profile, umieszczonym w katalogu HOME. Naley utworzy
lub edytowa ten plik i doda nastpujcy wiersz:
export JAVA_HOME=cieka_do_katalogu_JDK

gdzie w miejscu cieka_do_katalogu_JDK bdzie prawdopodobnie /Library/Java/Home.


W systemie Linux naley edytowa plik .profile oraz doda taki sam wiersz jak w przypadku systemu Mac OS X, z tym e docelow ciek, ktr naley doda, bdzie najprawdopodobniej
/usr/lib/jvm/java-6-sun lub /usr/lib/jvm/java-6-openjdk. Niektrzy wol stosowa plik
.bashrc zamiast pliku .profile; w obydwu przypadkach powysze polecenie powinno dziaa.

Pobieranie rodowiska Eclipse 3.6


Po zainstalowaniu pakietu JDK mona pobra rodowisko Eclipse IDE for Java Developers
(edycja dla Java EE nie jest wymagana; bdzie dziaa, lecz zajmuje o wiele wicej miejsca
i zawiera elementy, ktrych nie bdziemy potrzebowa). Przykady przygotowane z myl
o tej ksice zostay napisane z wykorzystaniem rodowiska Eclipse 3.6 (w systemie Windows). Wszystkie wersje Eclipse s dostpne pod adresem www.eclipse.org/downloads/. Pliki
rodowiska s skompresowane w formacie .zip, mona je wypakowa w dowolnym miejscu.
Najprociej jest wypakowa je do partycji C:\, co spowoduje utworzenie katalogu C:\Eclipse.
Mona w nim znale plik wykonywalny eclipse.exe. W przypadku systemu Mac OS X pliki
mona rozpakowa do katalogu Applications, za w Linuksie do katalogu HOME lub poprosi
administratora o ich umieszczenie w publicznym miejscu, do ktrego istnieje atwy dostp.
We wszystkich przypadkach plik wykonywalny rodowiska Eclipse zostaje umieszczony
w utworzonym folderze. Mona rwnie znale i zainstalowa rodowisko Eclipse poprzez
Centrum Oprogramowania Linuksa, w ktrym dodawane s nowe aplikacje, chocia by moe
nie uda si tam znale jego najnowszej wersji.

54

Android 3. Tworzenie aplikacji

Podczas pierwszego uruchomienia rodowiska Eclipse pojawi si monit o okrelenie cieki do


przestrzeni roboczej. eby nie komplikowa sobie zbytnio ycia, warto wpisa jak najprostszy
adres, jak na przykad C:\android, lub umieci t ciek w podkatalogu swojego profilowego
katalogu. Jeeli komputer jest wspuytkowany przez kilka osb, dobrze jest umieci folder
przestrzeni roboczej gdzie w katalogu swojego profilu.

Pobieranie zestawu Android SDK


Zasadniczym skadnikiem, niezbdnym w trakcie tworzenia aplikacji dla Androida, jest rodowisko programistyczne Android SDK. Jak ju zostao wczeniej wspomniane, zestaw SDK posiada pewne podstawowe narzdzia, do ktrych nastpnie doczamy kolejne skadowe zoone
z potrzebnych czy przydatnych pakietw. Wrd narzdzi jest dostpny emulator, zatem nie
trzeba posiada urzdzenia przenonego z systemem Android do projektowania aplikacji.
Wrd nich mona rwnie znale program instalacyjny, pozwalajcy na wybranie pakietw,
ktre maj zosta pobrane.
Zestaw Android SDK jest dostpny pod adresem http://developer.android.com/sdk. Podobnie
jak w przypadku rodowiska Eclipse, jest on skompresowany w formie pliku .zip, zatem naley
go wypakowa do wybranej lokacji. W systemach Windows mona umieci te pliki na przykad
w partycji C:, a po rozpakowaniu powinien pojawi si folder android-sdk-windows (lub podobnie nazwany), w ktrym bd pliki przedstawione na rysunku 2.1. W przypadku systemw
Mac OS X oraz Linux zestaw Android SDK mona wypakowa do katalogu HOME. atwo
zauway, e wersja przeznaczona dla systemw Mac OS X i Linux nie posiada pliku wykonywalnego SDK Manager. W przypadku wspomnianych systemw zamiast tego pliku uruchamiamy program znajdujcy si w katalogu tools/android.

Rysunek 2.1. Zawarto folderu Android SDK

Alternatywnym rozwizaniem (wycznie w przypadku systemu Windows) jest pobranie instalatora w formacie .exe, a nie skompresowanego do formatu .zip. Instalator sprawdzi obecno
zestawu Java JDK, wypakuje wymagane pliki oraz uruchomi aplikacj SDK Manager, co uatwi
pobranie pozostaych plikw.
Bez wzgldu na to, czy korzystamy z instalatora, czy bezporednio uruchamiamy aplikacj SDK
Manager, nastpnym etapem jest zainstalowanie niektrych pakietw. W trakcie instalacji
zestawu Android SDK nie zawiera on adnej platformy (na przykad rnych wersji systemu
Android). Instalowanie platform jest bardzo proste. Po uruchomieniu aplikacji SDK Manager
naley wybra Window/Android SDK and AVD Manager, klikn element Available Packages,
zaznaczy adres rda https://dl-ssl.google.com/android/repository/repository.xml, a nastpnie

Rozdzia 2 Konfigurowanie rodowiska programowania

55

wybra potrzebne platformy i dodatki, na przykad Android 2.3.3 (rysunek 2.2). Aby rodowisko
dziaao, naley koniecznie doda narzdzia powizane z dan platform. Poniewa ju niebawem bdziemy z niej korzysta, warto teraz doda platform przynajmniej w wersji 1.6.

Rysunek 2.2. Dodawanie pakietw do rodowiska Android SDK

Teraz wystarczy klikn przycisk Install Selected. Trzeba zatwierdzi kady element, zaznaczajc
opcj Accept1, a nastpnie zatwierdzi przyciskiem Install Accepted. Android pobierze wybrane
pakiety i platformy. Dodatki Google APIs su do projektowania aplikacji wykorzystujcych
Google Maps. Istnieje moliwo przegldania zainstalowanych dodatkw po klikniciu opcji
Installed Packages, widocznej na rysunku 2.2 w lewym grnym rogu okna. Jeli bdzie trzeba,
w kadej chwili mona tu wrci i zainstalowa nastpne pakiety.

Aktualizowanie zmiennej rodowiskowej PATH


rodowisko Android SDK zawiera katalog narzdzi, ktry warto umieci w zmiennej systemowej
PATH. Trzeba bdzie rwnie doda do niej katalog z narzdziami obsugujcymi platformy. Instalacj tego katalogu omwilimy przed chwil. Dodajmy teraz te narzdzia lub upewnijmy si, e
s waciwie umieszczone. Przy okazji uatwimy sobie prac, dodajc take katalog bin zestawu
JDK. W systemie Windows naley wrci do okna zmiennych rodowiskowych. Nastpnie trzeba
edytowa zmienn PATH, doda na kocu rednik (;), wpisa ciek do folderu tools Androida
SDK, po kolejnym redniku wpisa ciek do folderu zawierajcego narzdzia obsugujce platformy, a po nastpnym redniku umieci wpis %JAVA_HOME%\bin. Nastpnie wystarczy klikn
przycisk OK. W przypadku systemw Mac OS X oraz Linux naley edytowa plik .profile i doda
ciek do folderu tools, a take ciek do narzdzi powizanych z platformami oraz parametr
$JAVA_HOME/bin do zmiennej PATH. W systemie Linux powinno to wyglda mniej wicej tak:
export PATH=$PATH:$HOME/android-sdk-linux_x86/tools:$HOME/android-sdklinux_x86/
platform-tools:$JAVA_HOME/bin

Naley si jeszcze upewni, e cz polecenia, ktra dotyczy cieki do katalogu tools, bdzie wskazywaa miejsce zainstalowania katalogu.
1

Lub zaakceptowa wszystkie jednoczenie, zaznaczajc Accept All przyp. tum.

56

Android 3. Tworzenie aplikacji

Okno narzdzi
W dalszej czci ksiki pojawi si momenty, gdy trzeba bdzie uruchamia pewne programy
z wiersza polece. S one czci rodowiska JDK lub Android SDK. Dziki umieszczeniu ich
w zmiennej systemowej PATH nie bdzie trzeba wpisywa penej cieki do nich, jednak do
uruchomienia tych programw konieczne jest otwarcie okna narzdzi. W nastpnych rozdziaach bdziemy korzysta z takiego okna. Najprostszym sposobem jego uruchomienia w systemie
Windows jest kliknicie menu Start/Wyszukaj, a nastpnie wpisanie cmd w polu tekstowym
i kliknicie przycisku OK. W systemie Mac OS X naley wybra aplikacj Terminal w folderze
Applications z poziomu menedera plikw Finder lub z poziomu Dock. W systemie Linux aplikacja Terminal znajduje si w menu Applications/Accessories.
Trzeba wspomnie o jeszcze jednej sprawie dotyczcej rnic pomidzy systemami operacyjnymi: niekiedy trzeba zna adres IP stacji roboczej. W systemie Windows naley uruchomi
wiersz polece i wpisa polecenie ipconfig. Wrd wynikw bdzie widnia wpis dotyczcy
IPv4 (lub podobny), a obok zostanie wywietlony adres IP danego komputera. Wyglda on mniej
wicej tak: 192.168.1.25. W systemach Mac OS X oraz Linux naley uruchomi wiersz polece
i wpisa ifconfig. Adres IP jest umieszczony obok wpisu inet addr. Moe te by widoczne
poczenie sieciowe przy nazwie localhost lub lo. Adres IP tego poczenia to 127.0.0.1. Jest to
specjalny typ poczenia sieciowego, wykorzystywany przez system operacyjny, i nie ma nic
wsplnego z adresem IP stacji roboczej. Naley poszuka wiersza, w ktrym widoczny jest
inny adres IP.

Instalowanie narzdzi ADT


Teraz naley zainstalowa narzdzia ADT wtyczk rodowiska Eclipse usprawniajc tworzenie aplikacji dla Androida. Dokadniej mwic, narzdzia ADT cz si ze rodowiskiem
Eclipse, dziki czemu mona tworzy i testowa aplikacje przeznaczone dla systemu Android
oraz wyszukiwa w nich bdy. eby zainstalowa t wtyczk, naley skorzysta z funkcji Install
New Software, dostpnej w aplikacji Eclipse (instrukcje dotyczce aktualizacji wtyczek mona
znale w dalszej czci podrozdziau). eby zainstalowa narzdzia ADT, naley uruchomi rodowisko Eclipse i wykona nastpujce czynnoci:
1. W pasku narzdzi wybierz opcj Help, a nastpnie kliknij opcj Install New Software
(w poprzednich wersjach aplikacji Eclipse bya ona nazwana Software Updates).
2. Zaznacz pole tekstowe Work with, wpisz https://dl-ssl.google.com/android/
eclipse/ i nacinij klawisz Enter/Return. Aplikacja poczy si z witryn i wywietli
list pokazan na rysunku 2.3.
3. Powinien si pojawi wze Developer Tools, podzielony na trzy podrzdne kategorie:
Android DDMS, Android Development Tools oraz Android Hierarchy Viewer. Zaznacz
wze nadrzdny oraz upewnij si, e elementy podrzdne s rwnie zaznaczone,
a nastpnie kliknij przycisk Next. Prawdopodobnie numery wersji bd wysze ni
przedstawione na rysunku, ale to nie szkodzi. Mog si tutaj rwnie pojawi dodatkowe
narzdzia.
4. Ukae si okno potwierdzenia instalacji wtyczek. Kliknij Next. Odnosi si to rwnie
do aplikacji Android Traceview, dodanej w wersji Android 3.0.
5. W kolejnym oknie trzeba bdzie zapozna si z licencj narzdzi ADT, a take z licencjami
dotyczcymi narzdzi potrzebnych do zainstalowania wtyczki. Przejrzyj licencje,
zaznacz opcj I accept the terms of license agreements i kliknij przycisk Finish.

Rozdzia 2 Konfigurowanie rodowiska programowania

57

Rysunek 2.3. Instalacja narzdzi ADT za pomoc funkcji Install New Software w rodowisku Eclipse

W tym momencie aplikacja Eclipse zacznie pobiera i instalowa narzdzia programistyczne.


eby nowe wtyczki pojawiy si w oknie Eclipse, naley ponownie uruchomi aplikacj.
W przypadku posiadania starszej wersji narzdzi ADT naley otworzy menu Help i wybra opcj
Check for Updates. Powinna zosta wywietlona aktualna wersja wtyczek ADT, ktrych instalacja przebiega tak, jak opisano, poczwszy od punktu 3. powyszych instrukcji.
Aplikacja Android Hierarchy Viewer zostaa dodana do narzdzi Developer Tools wraz
z wersj 2.3 Androida. Bdzie wic ona dostpna w trakcie przeprowadzania nowej
instalacji. Jeli jednak aktualizujemy narzdzia ADT, by moe nie bdzie jej wida na
licie. Jeli nie jest widoczna, po zaktualizowaniu pozostaych elementw naley przej do
zakadki Install New Software i wybra adres https://dl-ssl.google.com/android/eclipse/
z menu Works With. W rodkowym oknie powinien si pojawi wze Android Hierarchy
Viewer, ktry mona teraz osobno zainstalowa.

Ostatnim etapem aktywacji narzdzi ADT w obrbie rodowiska Eclipse jest odniesienie ich do
zestawu Android SDK. W tym celu naley w rodowisku Eclipse otworzy menu Window
i wybra opcj Preferences (w systemie Mac OS X opcja ta jest dostpna w menu Eclipse). W oknie
dialogowym Preferences naley wybra wze Android i wpisa ciek katalogu Android SDK
(rysunek 2.4), a nastpnie klikn przycisk Apply. W midzyczasie moe si pojawi okno dialogowe, w ktrym mona zaznaczy opcj wysyania do firmy Google statystyk dotyczcych
wykorzystania programu Android SDK. Wybr naley do Czytelnika. Teraz wystarczy klikn
OK, eby zamkn okno Preferences.

58

Android 3. Tworzenie aplikacji

Rysunek 2.4. Powizanie narzdzi ADT z zestawem Android SDK

SDK Manager mona uruchomi z poziomu rodowiska Eclipse. Naley w tym celu wybra zakadk Window/Android SDK and AVD Manager. Powinno zosta wywietlone okno przedstawione na rysunku 2.2, chocia prawdopodobnie nie bd widoczne wszystkie opcje dostpne
podczas osobnego uruchamiania aplikacji SDK Manager.
Ju niemal nadszed czas na zapoznanie si z pierwsz aplikacj dla Androida najpierw jednak
musimy zapozna si z podstawowymi pojciami odnoszcymi si do aplikacji tworzonych dla
tej platformy.

Przedstawienie podstawowych skadnikw


Szkielet kadej aplikacji zawiera pewne kluczowe skadniki, z ktrymi musz si zapozna projektanci, zanim zaczn pisa programy oparte na tym szkielecie. Na przykad do napisania
aplikacji w rodowisku J2EE (Java 2 Platform Enterprise Edition) wymagana jest znajomo
technologii JSP (JavaServer Pages) oraz serwletw. Podobnie w przypadku aplikacji pisanych
dla Androida naley zna pojcia aktywnoci, widokw, intencji, dostawcw treci, usug oraz
przeznaczenie pliku AndroidManifest.xml. W tym podrozdziale omwimy krtko kade z tych
poj, a bardziej szczegowe informacje zostan przedstawione w kolejnych rozdziaach.

Widok
Widoki s elementami interfejsu uytkownika tworzcymi jego podstawowe bloki budulcowe.
Mog one przybra ksztat przycisku, etykiety, pola tekstowego oraz wielu innych skadnikw
interfejsu UI. Jeeli Czytelnik wie, czym s widoki w platformach J2EE oraz Swing, szybko zrozumie widoki w Androidzie. Widoki czsto s wykorzystywane jako kontenery dla innych widokw, co zazwyczaj oznacza istnienie hierarchii widokw w interfejsie uytkownika. Ostatecznie
wszystkie elementy widoczne na ekranie s widokami.

Rozdzia 2 Konfigurowanie rodowiska programowania

59

Aktywno
Aktywno jest pojciem interfejsu uytkownika. Aktywno przewanie jest reprezentacj pojedynczego okna aplikacji. Zazwyczaj zawarty jest w niej przynajmniej jeden widok, ale niekoniecznie musi tak by. Okrelenie aktywno do dokadnie wskazuje jej przeznaczenie jest
to obiekt pomagajcy uytkownikowi wykona dan czynno. Tak czynnoci moe by przegldanie, tworzenie lub edycja danych. Wikszo aplikacji tworzonych dla systemu Android
zawiera kilka aktywnoci.

Intencja
Uoglniajc, sowo intencja oznacza intencj, zamiar wykonania jakiej pracy. W terminie tym
mieci si kilka poj, wic najlepszym sposobem jego zrozumienia jest wykorzystanie intencji
w praktyce. Intencje s wykorzystywane w nastpujcych celach:
nadawanie komunikatu,
uruchamianie usugi,
rozpoczynanie aktywnoci,
wywietlanie strony WWW lub listy kontaktw,
wybieranie lub odbieranie poczenia telefonicznego.
Intencje nie zawsze s inicjowane przez aplikacj s take wykorzystywane przez system do
powiadamiania aplikacji o okrelonych zdarzeniach (na przykad o otrzymaniu wiadomoci
tekstowej).
Intencje mona podzieli na jawne oraz niejawne. Jeeli zostanie wyranie okrelone, e adres
URL ma by widoczny, system automatycznie zdecyduje, jaki skadnik bdzie dotyczy intencji.
Istnieje take moliwo okrelenia konkretnej informacji, w jaki sposb powinna by potraktowana intencja. Intencje luno cz dziaanie z jego uchwytem.

Dostawca treci
Wspdzielenie danych pomidzy aplikacjami urzdzenia przenonego jest powszechnie
stosowan praktyk. Android definiuje wic standardowy mechanizm wspuytkowania danych (takich jak listy kontaktw) przez aplikacje bez koniecznoci odsaniania podstawowych magazynw, struktury oraz implementacji. Dziki dostawcom treci mona ujawnia
dane oraz pozwala jednym aplikacjom korzysta z zasobw innych programw.

Usuga
Usugi Androida s podobne do usug obecnych w systemie Windows lub na innych platformach s to procesy dziaajce w tle, ktre potencjalnie mog trwa przez dugi czas. W Androidzie s zdefiniowane dwa rodzaje usug: usugi lokalne oraz usugi zdalne. Usugi lokalne s
elementami dostpnymi wycznie dla aplikacji je obsugujcej. Z drugiej strony usugi zdalne
s przeznaczone dla innych aplikacji, czcych si z nimi w sposb zdalny.
Przykadem usugi jest skadnik wykorzystywany przez aplikacj pocztow do sprawdzania, czy
pojawiy si nowe wiadomoci. Usuga ta jest lokalna, jeeli nie jest uywana przez inne aplikacje
znajdujce si w urzdzeniu. Jeeli korzysta z niej kilka usug, mona j zaimplementowa
w formie usugi zdalnej. Jak zostanie wyjanione w rozdziale 11., jest to zwizane z rnic pomidzy funkcjami startService() oraz bindService().

60

Android 3. Tworzenie aplikacji

Istnieje moliwo stosowania istniejcych usug, jak rwnie pisania wasnych za pomoc
rozszerzania klasy Service.

AndroidManifest.xml
Plik AndroidManifest.xml, podobny do pliku web.xml w wiecie J2EE, okrela zawarto
oraz zachowanie aplikacji. Na przykad znajduje si w nim lista aktywnoci oraz usug danej
aplikacji, a take uprawnie i waciwoci wymaganych do jej uruchomienia.

Urzdzenia AVD
Urzdzenie AVD (ang. Android Virtual Device wirtualne urzdzenie Androida) pozwala
programistom na przetestowanie aplikacji bez koniecznoci poczenia si z rzeczywistym
urzdzeniem (zazwyczaj telefonem lub tabletem). Mona tworzy rne konfiguracje urzdze AVD, zdolne do emulowania rnych modeli istniejcych urzdze.

Witaj, wiecie!
Teraz moemy rozpocz pisanie pierwszej aplikacji dla Androida. Na pocztek utworzymy
prosty program Witaj, wiecie!. Szkielet aplikacji zbudujemy w nastpujcy sposb:
1. Uruchom rodowisko Eclipse i wybierz File/New/Project. W oknie dialogowym
New Project otwrz wze Android, a nastpnie wybierz opcj Android Project, po
czym kliknij przycisk Next. Ujrzysz okno New Android Project, zaprezentowane na
rysunku 2.5 (by moe dostp do projektu Android istnieje w menu New, dziki
czemu mona nieco szybciej otwiera nowe projekty). Jeeli istnieje taka moliwo,
moesz rwnie skorzysta z przycisku New Android Project na pasku narzdzi.
2. Wpisz, zgodnie z rysunkiem 2.5, nazw projektu HelloAndroid. Musimy w jaki
sposb odrnia nazw tego projektu od innych projektw tworzonych w rodowisku
Eclipse, naley wic wybiera takie nazwy, ktre na pierwszy rzut oka na list projektw
bd atwe do rozpoznania. Warto rwnie zauway, e domylne umiejscowienie
projektu jest zwizane z lokalizacj przestrzeni roboczej rodowiska Eclipse. Kreator
nowego projektu doda nazw nowej aplikacji do obszaru roboczego. W naszym
przypadku, jeli przestrzeni robocz jest C:\android, nowy projekt zostanie umieszczony
w katalogu C:\android\HelloAndroid.
3. Na razie zostaw sekcj Contents bez zmian, poniewa w przestrzeni roboczej chcemy
utworzy nowy projekt umieszczony w domylnej lokacji.
4. Zaznacz Android 1.6 w oknie Build Target, tak jak zostao to pokazane na rysunku 2.5.
Ta wersja Androida bdzie suya za baz naszej aplikacji. Program ten bdzie mona
uruchomi na pniejszych wersjach platformy, na przykad 2.1 albo 2.3, ale wersja
1.6 posiada wszystkie wymagane funkcje, zatem zostanie ona nasz wersj docelow.
Zasadniczo najlepiej jest wybiera najnisz dopuszczaln wersj, poniewa w ten
sposb maksymalizujemy liczb urzdze, na ktrych tworzona aplikacja zadziaa
zgodnie z oczekiwaniami.
5. Wprowad Witaj, Androidzie jako nazw aplikacji. Nazwa ta bdzie pojawiaa si
wraz z ikon aplikacji, w pasku tytuowym oraz na listach aplikacji. Powinna by
opisowa, ale nie za duga.

Rozdzia 2 Konfigurowanie rodowiska programowania

61

Rysunek 2.5. Okno kreatora New Android Project

6. Jako nazw pakietu wykorzystaj com.androidbook.hello. Aplikacja musi mie nazw


podstawowego pakietu, a w naszym przykadzie wyglda ona wanie tak. Nazwa ta
bdzie suya jako identyfikator aplikacji i nie moe si ona powtarza wrd innych
programw. Z tego powodu najlepiej rozpocz nazw pakietu od nazwy swojej
domeny. Jeeli nie posiadasz domeny, postaraj si by jak najbardziej kreatywny,
aby jak najskuteczniej unika moliwoci zduplikowania nazwy pakietu przez innych
twrcw. Nie naley jednak korzysta z nazw pakietu rozpoczynajcych si od czonw
com.google, com.android, android lub com.example, gdy zostay one zastrzeone
przez firm Google i nie bdzie mona umieci takich aplikacji w sklepie Android Market.
7. W polu Create Activity wprowad nazw aktywnoci HelloActivity. Android zostaje
w ten sposb poinformowany, e naley wywoa t aktywno w momencie uruchomienia
aplikacji. Aplikacja moe zawiera inne aktywnoci, jednak to ta bdzie pierwsza,
jak ujrzy uytkownik po uruchomieniu aplikacji.
8. Na koniec, dziki wartoci 4 w polu Min SDK Version Android wie, e aplikacja
wymaga co najmniej wersji 1.6 systemu operacyjnego. Z technicznego punktu widzenia
mona okreli minimaln wersj nisz od wartoci Build Target. Jeeli aplikacja
wymaga funkcji niedostpnych w starszych wersjach Androida, trzeba rozwiza ten
problem w delikatny sposb, ale jest to moliwe. W przypadku wikszoci aplikacji
warto pola Min SDK Version bdzie zgodna z wartoci Build Target.

62

Android 3. Tworzenie aplikacji

9. Kliknij przycisk Finish, dziki czemu narzdzia ADT wygeneruj szkielet projektu.
Teraz otwrz plik HelloActivity.java w folderze src i zmodyfikuj metod onCreate()
w nastpujcy sposb:
/** Called when activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

/** Tworzy deklaracj widoku TextView i wywietla napis Witaj, wiecie! */


TextView tv = new TextView(this);
tv.setText("Witaj, wiecie!");

/** Przycza widok treci do deklaracji widoku TextView */


setContentView(tv);
}

Prawdopodobnie trzeba bdzie wasnorcznie doda w kodzie instrukcj import android.


widget.TextView;, aby pozby si komunikatu o bdzie, wywietlanego przez rodowisko
Eclipse. Teraz wystarczy zapisa plik HelloActivity.java.
eby uruchomi aplikacj, naley utworzy konfiguracj uruchomieniow rodowiska Eclipse;
bdzie te potrzebne wirtualne urzdzenie do przetestowania aplikacji. Szybko opiszemy wymagane czynnoci, a nastpnie zajmiemy si bardziej szczegowo urzdzeniami AVD. Konfiguracj uruchomieniow tworzy si w nastpujcy sposb:
1. Wybierz gwne menu Run, a nastpnie podelement Run Configurations.
2. W oknie dialogowym Run Configurations kliknij dwukrotnie opcj Android Application,
znajdujc si w panelu po lewej stronie. Kreator utworzy now konfiguracj nazwan
New Configuration.
3. Zmie nazw tej konfiguracji na UruchomWitajwiecie
4. Kliknij przycisk Browse i zaznacz projekt HelloAndroid.
5. W czci okna nazwanej Launch Action zaznacz opcj Launch i z rozwijanej listy
wybierz com.androidbook.HelloActivity. Okno powinno wyglda podobnie jak
na rysunku 2.6.
6. Kliknij Apply, a nastpnie Run. Ju niemal gotowe! rodowisko Eclipse jest przygotowane
do uruchomienia aplikacji, ale potrzebuje jeszcze urzdzenia, na ktrym zostanie ona
sprawdzona. Pojawi si okno z ostrzeeniem, takie jak na rysunku 2.7, e nie zostay
znalezione kompatybilne urzdzenia. Kliknij Yes, aby stworzy wasne urzdzenie.
7. Nastpnie zostanie wywietlone okno zawierajce list dostpnych urzdze AVD
(rysunek 2.8). Zwr uwag, e mamy do czynienia z tym samym oknem co
przedstawione na rysunku 2.2. Poprzednio zostao wywietlone podczas instalowania
pakietw, tym razem jednak mamy do czynienia z wirtualnymi urzdzeniami.
Musisz tu doda urzdzenie pasujce do aplikacji. Kliknij przycisk New.
8. Wypenij pola w oknie Create new Android Virtual Device AVD, tak jak zostao pokazane
na rysunku 2.9. Podaj nazw urzdzenia Gingerbread, jako docelowy system z listy
Target wybierz platform Android 2.3 API Level 9 (lub jak inn wersj), ustaw
rozmiar pamici karty na 10 MB, wcz moliwo wykonywania migawek (opcja
Enabled w polu Snapshot) oraz wybierz skrk (ang. Skin) urzdzenia w formacie
HVGA. Kliknij Create AVD. Meneder moe powiadomi Ci o udanym utworzeniu
urzdzenia AVD. Kliknij krzyyk w prawym grnym rogu okna, aby zamkn okno
narzdzia Android SDK and AVD Manager.

Rozdzia 2 Konfigurowanie rodowiska programowania

Rysunek 2.6. Tworzenie konfiguracji uruchomieniowej rodowiska Eclipse, pozwalajcej


na uruchomienie aplikacji Witaj, wiecie!

Rysunek 2.7. Informacja o bdzie: ostrzeenie o braku kompatybilnych urzdze


i zapytanie o utworzenie nowego

Rysunek 2.8. Okno zawierajce list istniejcych urzdze AVD

63

64

Android 3. Tworzenie aplikacji

Rysunek 2.9. Konfigurowanie wirtualnego urzdzenia AVD


Wybralimy nowsz wersj rodowiska SDK dla naszego urzdzenia AVD, ale rwnie
dobrze mona skorzysta ze starszej wersji. Urzdzenia AVD oparte na nowszym
zestawie SDK wsppracuj z aplikacjami napisanymi w starszym rodowisku
programistycznym. Odwrotna moliwo nie wchodzi oczywicie w rachub: aplikacja
wymagajca funkcji zawartych w nowszym rodowisku SDK nie zadziaa na
urzdzeniu AVD opartym na starszej wersji SDK.

9. Teraz wybierz utworzone urzdzenie AVD z listy. Zwr uwag, e po klikniciu


przycisku Refresh lista zostanie odwieona. Kliknij OK.
10. W ten sposb uruchomisz swoj pierwsz aplikacj na emulatorze (pokazalimy j
na rysunku 2.10)!
Emulacja rozruchu urzdzenia moe zaj emulatorowi kilka minut. Zazwyczaj po
zaadowaniu systemu operacyjnego pojawi si ekran blokady systemu. Naley wtedy
klikn przycisk Menu lub przewin suwak blokady, aby odblokowa urzdzenie
AVD. Po odblokowaniu systemu powinna si pojawi aplikacja HelloAndroidApp
na wirtualnym ekranie, co zostao przedstawione na rysunku 2.10. Dodatkowo naley
te mie wiadomo, e emulator uruchamia w tle rwnie inne aplikacje, wic co
jaki czas moe wyskakiwa informacja o bdzie lub ostrzeenie. Jeeli pojawi si
komunikat o bdzie, zazwyczaj mona go zignorowa i przej do kolejnego etapu
rozruchu. Na przykad jeeli pojawi si informacja application abc is not responding
(aplikacja abc przestaa odpowiada), mona albo zaczeka na jej uruchomienie,
albo po prostu zmusi emulator do jej zamknicia. Zasadniczo warto poczeka i pozwoli,
eby emulator uruchomi si bez bdw.

Rozdzia 2 Konfigurowanie rodowiska programowania

65

Rysunek 2.10. Aplikacja HelloAndroidApp uruchomiona na emulatorze

Wiadomo ju, w jaki sposb utworzy now aplikacj w Androidzie oraz jak j uruchomi na
emulatorze. Teraz przyjrzymy si uwaniej urzdzeniom AVD, po czym zagbimy si w wiat
artefaktw oraz struktury aplikacji Androida.

Wirtualne urzdzenia AVD


Wirtualne urzdzenie Androida (ang. Android Virtual Device AVD) reprezentuje konfiguracj wybranego modelu urzdzenia. Na przykad mona utworzy urzdzenie AVD symbolizujce telefon starszego rodzaju, dziaajcy zgodnie z wersj 1.5 rodowiska SDK oraz posiadajcy
kart SD 32 MB. Caa koncepcja oparta jest na moliwoci tworzenia urzdze AVD obsugujcych tworzone aplikacje oraz emulowania tych urzdze w celu projektowania i testowania
aplikacji. Definiowanie (oraz zmienianie) urzdze AVD jest bardzo atwym procesem do przeprowadzenia oraz umoliwia byskawiczne testowanie aplikacji w rnych konfiguracjach.
W poprzednim podrozdziale przedstawilimy sposb tworzenia urzdzenia AVD w rodowisku
Eclipse. Mona stworzy wiksz liczb urzdze AVD, klikajc Window/Android SDK and AVD
Manager, a nastpnie wybierajc wze Virtual Devices w panelu po lewej stronie ekranu. Poniej
zosta take opisany sposb tworzenia tych urzdze z poziomu wiersza polece.
Do utworzenia urzdzenia AVD wykorzystywany jest plik wsadowy android, umieszczony
w katalogu tools (c:\android-sdk-windows\tools\). Dziki temu plikowi moliwe jest take
zarzdzanie utworzonymi urzdzeniami AVD. Mona je na przykad przeglda oraz przenosi.
Spis polece dostpnych dziki plikowi android zostaje wywietlony po wpisaniu w wierszu
polecenia android help. Na razie stwrzmy urzdzenie AVD.
Pliki urzdze AVD domylnie s przechowywane w katalogu profilu uytkownika (na wszystkich platformach) w folderze .android\AVD. Znajduje si tam nasze urzdzenie AVD, stworzone do uruchomienia aplikacji Witaj, wiecie!. Istnieje rwnie moliwo przeniesienia

66

Android 3. Tworzenie aplikacji

(lub edytowania) urzdze AVD do innej lokalizacji. Stwrzmy teraz folder, w ktrym bdzie
przechowywany obraz naszego urzdzenia AVD, na przykad c:\avd\. Kolejnym etapem jest
utworzenie listy dostpnych docelowych wersji Androida za pomoc nastpujcego polecenia,
wprowadzonego w oknie narzdzi:
android list target

W wyniku tego polecenia zostanie wygenerowana lista wszystkich zainstalowanych wersji


Androida, gdzie kady jej element bdzie posiada wasny identyfikator. Teraz naley ponownie
uruchomi plik android w celu wygenerowania urzdzenia AVD. Trzeba otworzy wiersz polece i wpisa nastpujce polecenie (naley wprowadzi wasn ciek do plikw AVD oraz
poda warto argumentu t, odpowiadajcego identyfikatorowi wersji zainstalowanego rodowiska SDK):
android create avd -n CupcakeMaps -t 2 -c 16M -p C:\avd\CupcakeMaps\

W tabeli 2.1 zostay objanione parametry narzdzia android.


Tabela 2.1. Parametry przypisane do pliku android.bat
Argument/polecenie

Opis

create avd

Polecenie utworzenia urzdzenia AVD.

Nazwa urzdzenia AVD.

Wersja rodowiska SDK. Dla kadej docelowej wersji naley skorzysta


z polecenia android list target, aby pozna jej identyfikator.

Pojemno karty SD wyraona w bajtach. Warto K oznacza kilobajty,


a M megabajty.

cieka tworzonego urzdzenia. Ten argument nie jest wymagany.

Umoliwia wykonywanie migawek. Jest to argument dodatkowy.


Migawki zostan omwione w podrozdziale Uruchamianie emulatora.

Wykonanie powyszego polecenia spowoduje wygenerowanie pliku urzdzenia AVD; powinno


zosta wywietlone okno podobne do pokazanego na rysunku 2.11. Warto zwrci uwag,
e po wpisaniu polecenia create avd system zapyta, czy utworzy niestandardowy profil
sprztowy. Na razie wpiszmy No, dobrze jest jednak wiedzie, e po udzieleniu odpowiedzi
Yes bdzie mona skorzysta z wielu opcji konfiguracji urzdzenia AVD, takich jak rozmiar
ekranu, obecno aparatu i tak dalej.
Nawet jeeli zostaa okrelona alternatywna cieka dla pliku CupcakeMaps w programie
android.bat, istnieje take kopia pliku CupcakeMaps.ini w folderze macierzystym .android\AVD.
Jest to przemylane dziaanie, dziki ktremu po klikniciu Window/Android SDK and AVD
Manager w rodowisku Eclipse bd dostpne wszystkie urzdzenia AVD, take te utworzone
w wierszu polece.
Spjrzmy ponownie na rysunek 2.4. Kada wersja Androida posiada okrelony poziom interfejsu
API. Android 1.6 posiada poziom 4. interfejsu API, natomiast w Androidzie 2.1 przybiera on
warto 7. Wartoci poziomw API nie s tosame z identyfikatorami wersji docelowych, ktre
s okrelane za pomoc parametru t w poleceniu android create avd. Naley zawsze stosowa polecenie android list target w celu uzyskania waciwej wartoci identyfikatora, ktra
bdzie wykorzystana w poleceniu android create avd.

Rozdzia 2 Konfigurowanie rodowiska programowania

67

Rysunek 2.11. Ekran wynikowy utworzenia urzdzenia AVD za pomoc pliku android.bat

Naley rwnie mie na uwadze, e wybr interfejsu Google API z listy SDK Target daje dostp
do funkcji korzystania z map w urzdzeniu AVD, podczas gdy wybranie interfejsu Android
1.5 lub pniejszego nie zapewni takiej moliwoci. O wiele wicej uwagi powicimy mapom
w rozdziale 17.

Poznanie struktury aplikacji Androida


Chocia poszczeglne aplikacje Androida bd si rniy rozmiarami oraz zoonoci, ich
struktura bdzie podobna. Na rysunku 2.12 przedstawiono struktur utworzonej niedawno
aplikacji Witaj, wiecie!.

Rysunek 2.12. Struktura aplikacji Witaj, wiecie!

Aplikacje dla Androida skadaj si z elementw niezbdnych oraz opcjonalnych. W tabeli 2.2
zostay wymienione skadniki aplikacji tworzonej dla Androida.

68

Android 3. Tworzenie aplikacji

Tabela 2.2. Elementy skadowe aplikacji systemu Android


Element skadowy

Opis

Wymagany?

AndroidManifest.xml

Plik deskryptora aplikacji. S w nim zdefiniowane


aktywnoci, dostawcy usug, usugi oraz adresaci
intencji, czyli elementy zwizane z dan aplikacj.
Mona w nim rwnie zadeklarowa uprawnienia
wymagane przez aplikacj, a take przydzieli
okrelone uprawnienia dla innych aplikacji,
korzystajcych z usug danego programu. Ponadto
moe by tu zamieszczona instrumentacja
wykorzystywana do testowania danej aplikacji
lub innych programw.

Tak

src

Folder przechowujcy kod rdowy aplikacji.

Tak

assets

Luny zbir plikw i folderw.

Nie

res

Folder zawierajcy zasoby aplikacji. Jest to folder


nadrzdny wobec wzw drawable, anim, layout,
menu, values, xml oraz raw.

Tak

drawable

Folder mieszczcy w sobie pliki obrazw lub


deskryptorw obrazw uywanych przez aplikacj.

Nie

anim

W folderze tym s umieszczone pliki deskryptora


napisane w jzyku XML, opisujce animacje
wykorzystywane przez aplikacj.

Nie

layout

Mieszcz si tu widoki aplikacji. Bardziej opaca si


tworzenie widokw poprzez deskryptory jzyka
XML ni poprzez pisanie kodu.

Nie

menu

Folder zawierajcy pliki deskryptorw list menu


aplikacji.

Nie

values

Przechowywane s w nim pozostae zasoby


wykorzystywane przez aplikacj. Przykadowymi
zasobami mog by cigi znakw, tablice, style
oraz kolory.

Nie

xml

Znajduj si tu dodatkowe pliki XML


wykorzystywane przez aplikacj.

Nie

raw

Folder z dodatkowymi danymi prawdopodobnie


nieopisanymi w jzyku XML wymaganymi przez
aplikacj.

Nie

Jak zostao pokazane w tabeli 2.2, aplikacja systemu Android skada si z trzech zasadniczych
elementw: deskryptora aplikacji, zbioru zasobw oraz kodu rdowego aplikacji. Jeeli zignorowa na chwil plik AndroidManifest.xml, mona zauway prostot aplikacji: logika biznesowa
przybiera form kodu, a caa reszta to zasoby. Taka nieskomplikowana struktura przypomina
szkielet aplikacji J2EE, w ktrym zasobom odpowiadaj strony JSP, logice biznesowej serwlety,
a odpowiednikiem pliku AndroidManifest.xml jest plik web.xml.

Rozdzia 2 Konfigurowanie rodowiska programowania

69

Mona rwnie porwna modele projektowania w rodowiskach J2EE oraz Android. W przypadku J2EE widoki s budowane za pomoc jzyka znacznikw. W Androidzie wykorzystano t
sam filozofi, ale stosowanym jzykiem jest XML. Jest to korzystne rozwizanie, gdy nie ma koniecznoci wplatania widoku do gwnego kodu; wygld i zachowanie aplikacji mona zmienia
poprzez edytowanie znacznikw.
Naley rwnie pamita o kilku ograniczeniach dotyczcych zasobw. Po pierwsze, Android obsuguje jedynie liniow list plikw, znajdujc si w predefiniowanych plikach umieszczonych
w folderze res. Na przykad nie moe uzyska dostpu do zagniedonych folderw znajdujcych
si w katalogu layout (tak samo w przypadku pozostaych folderw podrzdnych do folderu res).
Po drugie, istniej pewne podobiestwa pomidzy folderem assets oraz folderem raw, umieszczonym w katalogu res. W obydwu katalogach mog by przechowywane nieskompresowane pliki, ale dane znajdujce si w folderze raw s uznawane za zasoby, a w folderze assets ju
nie. Zatem pliki z katalogu raw bd zlokalizowane, dostpne poprzez identyfikatory zasobw
i tak dalej. Jednak informacje znajdujce si w katalogu assets s traktowane jako dane oglnego
przeznaczenia, pozbawione ogranicze oraz obsugi zasobw. Warto zwrci na to uwag, gdy
pozbawienie danych znajdujcych si w katalogu assets miana zasobw umoliwia utworzenie
wasnej hierarchii plikw i folderw w jego wntrzu (wicej informacji na temat zasobw znajduje si w rozdziale 3.).
Dosy wyranie wida, e w Androidzie cakiem czsto stosuje si jzyk XML. Powszechnie
wiadomo, e jest to do rozbudowany jzyk, rodzi si zatem pytanie, czy korzystanie
z niego, gdy celem jest urzdzenie posiadajce ograniczone zasoby, ma sens. Okazuje si,
e kod XML, uywany podczas projektowania aplikacji, jest w rzeczywistoci kompilowany
do kodu binarnego przy uyciu narzdzia AAPR (ang. Android Asset Packaging Tool
narzdzie pakowania zasobw Androida). Zatem podczas instalowania aplikacji na
urzdzeniu pliki s konwertowane i przechowywane w formie kodu binarnego. Podczas
uruchomienia plik jest odczytywany w tej formie i nie jest konwertowany ponownie
na plik XML. Ta metoda czy zalety obydwu technologii mona pracowa z jzykiem
XML i nie martwi si o ilo cennych zasobw urzdzenia.

Analiza aplikacji Notepad


Do tej pory nie tylko pokazalimy, w jaki sposb utworzy oraz uruchomi w emulatorze now
aplikacj dla Androida, lecz staralimy si, eby Czytelnik zrozumia elementy jej struktury.
Teraz przyjrzymy si aplikacji Notepad, umieszczonej w pakiecie Android SDK. Jej poziom
zoonoci plasuje si pomidzy nasz aplikacj Witaj, wiecie! a w peni rozwinit aplikacj
dla Androida, zatem analiza jej skadnikw pozwoli niejako poj realny proces projektowania
w rodowisku SDK. Bdzie to szybka analiza aplikacji Notepad. Na pocztku moe by trudno
zrozumie niektre z poj, ale bez obaw w nastpnych rozdziaach powicimy im znacznie
wicej uwagi.

Wczytanie oraz uruchomienie aplikacji Notepad


W tym podrozdziale wyjanimy, w jaki sposb zaadowa aplikacj Notepad w rodowisku
Eclipse i uruchomi j na emulatorze. Przed rozpoczciem naley wiedzie, e w aplikacji
Notepad zaimplementowano kilka rnych opcji. Uytkownik moe na przykad utworzy now
notatk, edytowa istniejc, usun j, przejrze list notatek i tak dalej. Po uruchomieniu

70

Android 3. Tworzenie aplikacji

aplikacji nie bdzie w niej adnych zapisanych notatek, wic uytkownik ujrzy pust list. Po
wciniciu przycisku Menu zostanie wywietlona lista czynnoci, a wrd nich opcja dodania nowej notatki. Po utworzeniu nowego pliku mona go edytowa lub usun za pomoc odpowiedniej opcji.
eby wczyta przykadow aplikacj Notepad w rodowisku Eclipse, naley wykona nastpujce
czynnoci:
1. Uruchom program Eclipse.
2. Otwrz File/New/Project.
3. W oknie dialogowym New Project wybierz Android/Android Project i kliknij Next.
4. W nastpnym oknie wpisz NotesList jako nazw projektu, wybierz opcj Create project
from existing sample, nastpnie zaznacz pole Android 1.6 na licie Build Target.
Z rozwijanej listy wybierz aplikacj Notepad. Zwr uwag, e jest ona umiejscowiona
w folderze platforms\android-1.6\samples pakietu Android SDK, ktry wczeniej pobrae.
Po wybraniu tej aplikacji zostanie automatycznie odczytany plik AndroidManifest.xml
i zostan wypenione pozostae pola w tym oknie dialogowym (rysunek 2.13).

Rysunek 2.13. Tworzenie aplikacji Notepad

5. Kliknij przycisk Finish.

Rozdzia 2 Konfigurowanie rodowiska programowania

71

Teraz aplikacja NotesList powinna by dostpna w rodowisku Eclipse. Jeeli zostan wywietlone jakie informacje o problemach zwizanych z tym projektem, mona sprbowa uy opcji
Clean z menu Project, aby je usun. eby uruchomi aplikacj, mona utworzy aplikacj uruchomieniow (podobnie jak to zrobilimy przy okazji programu Witaj, wiecie!) lub klikn
prawym przyciskiem ikon projektu, wybra opcj Run As, a nastpnie Android Application.
Spowoduje to uruchomienie emulatora i zainstalowanie na nim aplikacji. Po wczytaniu emulatora wystarczy odblokowa ekran emulatora, eby zostaa wywietlona aplikacja NotesList.
Aby si z ni zaznajomi, mona po niej pomyszkowa przez kilka minut.

Rozoenie kodu na czynniki pierwsze


Przyjrzyjmy si teraz strukturze aplikacji (rysunek 2.14).

Rysunek 2.14. Struktura aplikacji Notepad

Jak wida, program zawiera kilka plikw .java, obrazw .png, trzy widoki (w folderze layout)
oraz plik AndroidManifest.xml. Gdyby to bya aplikacja wiersza polece, naleaoby poszuka pliku, w ktrym jest umieszczona metoda Main. Zatem co jest odpowiednikiem metody Main
w Androidzie?
W rodowisku Android jest definiowana pocztkowa aktywno, zwana take aktywnoci szczytowego poziomu. Jeeli przyjrze si zawartoci pliku AndroidManifest.xml, mona tam znale

72

Android 3. Tworzenie aplikacji

jednego dostawc oraz trzy aktywnoci. Aktywno NotesList wyznacza filtr intencji dla
akcji android.intent.action.MAIN, a take dla kategorii android.intent.category.LAUNCHER.
Po uruchomieniu aplikacji Androida zostaje ona wczytana przez urzdzenie i jest odczytywany
plik AndroidManifest.xml. Zostaj wyszukane i uruchomione aktywnoci posiadajce filtr intencji, ktry skada si z aktywnoci MAIN oraz kategorii LAUNCHER, tak jak pokazano poniej.
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

Po odnalezieniu waciwej aktywnoci urzdzenie musi powiza j z rzeczywist klas. Dokonuje tego poprzez poczenie nazwy gwnego pakietu z nazw aktywnoci, w naszym przypadku bdzie to com.example.android.notepad.NotesList (listing 2.1).
Listing 2.1. Plik AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.notepad"
>
<application android:icon="@drawable/app_notes"
android:label="@string/app_name"
>
<provider android:name="NotePadProvider"
android:authorities="com.google.provider.NotePad"
/>
<activity android:name="NotesList" android:label="@string/title_notes_list">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
</intent-filter>
</activity>
...
</manifest>

Nazwa gwnego pakietu aplikacji jest zdefiniowana jako atrybut elementu <manifest> w pliku
AndroidManifest.xml, a kada aktywno posiada atrybut nazwy.
Po okreleniu pocztkowej aktywnoci zostaje ona uruchomiona. Nastpuje rwnie wywoanie
metody onCreate(). Przyjrzyjmy si elementowi NotesList.onCreate(), przedstawionemu
na listingu 2.2.

Rozdzia 2 Konfigurowanie rodowiska programowania

73

Listing 2.2. Metoda onCreate


public class NotesList extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(Notes.CONTENT_URI);
}
getListView().setOnCreateContextMenuListener(this);
Cursor cursor = managedQuery(getIntent().getData(),PROJECTION, null, null,
Notes.DEFAULT_SORT_ORDER);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.noteslist_item, cursor, new String[] { Notes.TITLE },
new int[] { android.R.id.text1 });
setListAdapter(adapter);
}
}

Aktywnoci w Androidzie s przewanie uruchamiane przez intencje, a take przez inne aktywnoci. Metoda onCreate() sprawdza, czy intencja biecej aktywnoci zawiera dane (notatki).
Jeeli nie zawiera, zostaje ustanowiony identyfikator URI, dziki ktremu zostaj pobrane dane.
W rozdziale 4. zademonstrujemy, e Android uzyskuje dostp do danych poprzez dostawcw
treci korzystajcych z identyfikatorw URI. W tym przypadku identyfikator URI dostarcza
wystarczajco wiele informacji, eby pobra dane z bazy danych. Staa Notes.CONTENT_URI jest
zdefiniowana jako element static final w pliku Notepad.java, na przykad w taki sposb:
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/notes");

Klasa Notes znajduje si wewntrz klasy Notepad. Na razie wystarczy wiedzie, e przedstawiony
powyej identyfikator URI sprawia, e dostawca treci pobiera wszystkie notatki. Gdyby identyfikator ten wyglda nastpujco:
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/notes/11");

to uywany dostawca treci zwrciby notatk posiadajc identyfikator o wartoci 11. Temat
dostawcw treci oraz identyfikatorw URI zostanie poruszony w rozdziale 4.
Klasa NotesList jest dopenieniem klasy ListActivity, definiujcej sposb wywietlania
danych w postaci listy. Skadniki listy s zarzdzane poprzez wewntrzn klas ListView
(element interfejsu UI), wywietlajc notatki w oknie listy. Po wstawieniu identyfikatora
URI do intencji danej aktywnoci aktywno ta zgasza gotowo do zbudowania kontekstowego menu dla notatek. Jeeli Czytelnik stara si pozna t aplikacj, zauway zapewne,
e w zalenoci od wybranego elementu wywietlane jest menu kontekstowe. Jeli na przykad zostanie zaznaczona notatka, zostan wywietlone opcje Edit note oraz Edit title. Jeeli
notatka nie zostanie zaznaczona, dostpna bdzie opcja Add note.

74

Android 3. Tworzenie aplikacji

Widzimy nastpnie, e aktywno wykonuje zarzdzan kwerend, w wyniku czego pojawia si


kursor. Okrelenie zarzdzana kwerenda oznacza, e Android bdzie zarzdza przywoanym
kursorem. Innymi sowy, w razie usunicia lub ponownego wczytania do pamici ani aplikacja,
ani aktywno nie bd musiay obsugiwa takich aspektw zarzdzania kursorem, jak jego
pozycjonowanie, wczytywanie lub usunicie z pamici. Interesujce parametry elementu
managedQuery() zostay opisane w tabeli 2.3.
Tabela 2.3. Parametry elementu Activity.managedQuery
Parametr

Typ danych

Opis

URI

Uri

Identyfikator URI dostawcy treci

projection

String[]

Zwracana kolumna (nazwy kolumn)

selection

String

Opcjonalna klauzula where

selectionArgs

String[]

Wybierane argumenty, w przypadku gdy kwerenda


zawiera znaki zapytania

sortOrder

String

Kolejno sortowania zestawu wynikowego

Elementy managedQuery() oraz bliniaczy query() omwimy w dalszej czci tego podrozdziau
oraz w rozdziale 4. Na razie istotna jest informacja, e kwerendy w Androidzie zwracaj dane tabelaryczne. Parametr projection pozwala okreli interesujce nas kolumny. Mona take
ograniczy wynikowy zestaw oraz posortowa go za pomoc klauzul sortowania, uywanych
w jzyku SQL (na przykad asc lub desc). Naley zauway take, e kwerenda w Androidzie
musi zwrci kolumn o nazwie _ID, eby mc obsugiwa wywietlanie pojedynczych rekordw. Ponadto naley zna typ danych zwracanych przez dostawc treci czy kolumna
zawiera dane typu string, int, binary i tak dalej.
Po wykonaniu kwerendy zwrcony kursor jest przekazywany konstruktorowi elementu
SimpleCursorAdapter, przeksztacajcemu rekordy zestawu danych w elementy interfejsu
uytkownika (ListView). Przyjrzyjmy si bliej parametrom przekazywanym do konstruktora elementu SimpleCursorAdapter:
SimpleCursorAdapter adapter =
new SimpleCursorAdapter(this, R.layout.noteslist_item,
cursor, new String[] { Notes.TITLE }, new int[] { android.R.id.text1 });

W szczeglnoci zwrmy uwag na drugi parametr: identyfikator widoku reprezentujcego


elementy w metodzie ListView. Jak si bdzie mona przekona w rozdziale 3., Android
zawiera automatycznie generowan klas uytkow, w ktrej znajduj si odniesienia do zasobw projektu. Jest to tak zwana klasa R (ang. resources zasoby). Mieci si ona w pliku
R.java, ktry mona dostrzec na rysunku 2.14. Podczas kompilowania projektu narzdzie
AAPT tworzy klas R z zasobw umieszczonych w folderze res. Na przykad mona umieci
wszystkie zasoby skadajce si z cigw znakw w folderze values, a narzdzie AAPT wygeneruje identyfikator public static dla kadego z tych zasobw. Android obsuguje w ten
sposb wszystkie zasoby. Na przykad w konstruktorze elementu SimpleCursorAdapter aktywno NotesList przekazuje identyfikator widoku, ktry umoliwia wywietlanie elementu
listy notatek. Dziki tej klasie uytkowej nie ma potrzeby umieszczania zasobw wewntrz
gwnego kodu oraz uzyskuje si moliwo sprawdzania odniesie w trakcie kompilacji. Inaczej mwic, jeeli zasb zostanie usunity, klasa R straci do niego odniesienie i aden kod
powizany z tym zasobem nie zostanie skompilowany.

Rozdzia 2 Konfigurowanie rodowiska programowania

75

Przyjrzyjmy si kolejnej koncepcji Androida, o ktrej wspomnielimy nieco wczeniej: metodzie onListItemClick() w klasie NotesList (listing 2.3).
Listing 2.3. Metoda onListItemClick
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);
String action = getIntent().getAction();
if (Intent.ACTION_PICK.equals(action) ||
Intent.ACTION_GET_CONTENT.equals(action)) {
setResult(RESULT_OK, new Intent().setData(uri));
} else {
startActivity(new Intent(Intent.ACTION_EDIT, uri));
}
}

Metoda onListItemClick() jest wywoywana po zaznaczeniu notatki przez uytkownika.


Metoda ta implementuje dwa sposoby jej wykorzystania. W przypadku pierwszego z nich aktywno zwizana z list notatek moe zosta wywoana za pomoc intencji, zatem uytkownik
moe wybra okrelon notatk, ktra bdzie zwrcona do aktywnoci wywoujcej. W drugim
przypadku wystarczy po prostu spojrze na list notatek; po zaznaczeniu notatki bieca
aktywno wywoa aktywno odpowiedzialn za edycj wybranej notatki. Po zaznaczeniu
notatki metoda ta tworzy identyfikator URI poprzez dodanie identyfikatora danej notatki do
bazowego identyfikatora URI. Jeeli nasza aktywno zostaa wywoana za pomoc intencji
w celu zaznaczenia lub pobrania zawartoci notatki, wywoujemy metod setResult()
zwracajc obiektowi wywoujcemu identyfikator URI danej notatki. W drugim przypadku
identyfikator ten zostaje przekazany metodzie startActivity() wraz z now intencj. Uycie
metody startActivity() jest jednym ze sposobw uruchomienia aktywnoci: aktywno zostaje rozpoczta, jednak po jej zakoczeniu nie zostaje wywietlony raport z wynikami. Inn
moliwoci uruchomienia aktywnoci jest uycie metody startActivityForResult(). Za jej
pomoc mona rozpocz aktywno i wykorzysta wywoywanie zwrotne, aby zosta poinformowanym o jej zakoczeniu, w celu uzyskania wynikw. Na przykad aktywno wywoujca proces zaznaczania notatki w aplikacji NotesList mogaby wykorzysta metod
startActivityForResult(), dziki czemu istnieje moliwo powiadomienia w momencie,
gdy aktywno aplikacji NotesList bdzie wywoywa metod setResult().
W tym momencie mona zacz si zastanawia, jak wyglda interakcja uytkownika wzgldem aktywnoci. Na przykad jeeli uruchomiona aktywno uruchamia nastpn aktywno,
a ta z kolei uruchamia jeszcze inn aktywno (i tak dalej), to z ktr aktywnoci pracuje
uytkownik? Czy moe kontrolowa jednoczenie wszystkie aktywnoci, czy moe jest ograniczony do jednej? Okazuje si, e aktywnoci posiadaj zdefiniowany cykl ycia. S one utrzymywane w stosie aktywnoci, na ktrego szczycie znajduje si uruchomiona aktywno. Jeeli
aktywno uruchomi inn aktywno, pierwsza uruchomiona aktywno przesunie si w d
stosu, a nowa zostanie umieszczona na jego szczycie. Aktywnoci znajdujce si na niszych
poziomach stosu mog si znajdowa w stanie wstrzymania lub zatrzymania. Wstrzymana
aktywno jest czciowo lub cakowicie widoczna dla uytkownika; aktywno zatrzymana
jest dla niego niewidoczna. System moe usun ze stosu wstrzymane lub zatrzymane aktywnoci, jeeli si okae, e trzeba zwolni zasoby zajmowane przez te aktywnoci.

76

Android 3. Tworzenie aplikacji

Przejdmy teraz do trwaoci danych. Notatki tworzone przez uytkownika zapisywane s


w rzeczywistej bazie danych urzdzenia. cilej mwic, magazynem notatek programu Notepad jest baza danych SQLite. Wczeniej wspomniana metoda managedQuery() suy do okrelania danych w bazie danych poprzez dostawc treci. Przeledmy, w jaki sposb identyfikator
URI, dostarczony metodzie managedQuery(), powoduje wykonanie kwerendy w bazie SQLite.
Przypomnijmy, e identyfikator URI przekazany metodzie managedQuery() wyglda nastpujco:
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/notes");

Identyfikatory URI treci zawsze przybieraj nastpujc form: content://, nastpnie uprawnienie (AUTHORITY), a na kocu segment oglny (zaleny od kontekstu). Poniewa identyfikator
URI nie zawiera rzeczywistych informacji, w jaki sposb musi wpywa na wykonanie kodu
generujcego dane. Jaki jest zwizek pomidzy tym identyfikatorem a kodem? W jaki sposb
odniesienie URI wpywa na kod produkujcy informacje? Czy identyfikator URI jest usug
HTTP lub sieciow? Okazuje si, e identyfikator URI, a dokadniej jego cz zwizana
z uprawnieniami, jest skonfigurowany w pliku AndroidManifest.xml jako dostawca treci, na
przykad nastpujco:
<provider android:name="NotePadProvider"
android:authorities="com.google.provider.NotePad"/>

Kiedy Android trafi na identyfikator URI, ktry naley przeanalizowa, odczytuje jego cz
zwizan z uprawnieniami i sprawdza klas ContentProvider skonfigurowan dla tych uprawnie. Aplikacja Notepad posiada klas NotePadProvider, umieszczon w pliku AndroidManifest.xml, skonfigurowan dla uprawnienia com.google.provider.NotePad. Na listingu
2.4 zosta przedstawiony niewielki wycinek tej klasy.
Listing 2.4. Klasa NotePadProvider
public class NotePadProvider extends ContentProvider
{
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs,String sortOrder) {}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {}
@Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {}
@Override
public String getType(Uri uri) {}
@Override
public boolean onCreate() {}

private static class DatabaseHelper extends SQLiteOpenHelper {}

Rozdzia 2 Konfigurowanie rodowiska programowania

77

@Override
public void onCreate(SQLiteDatabase db) {}
@Override
public void onUpgrade(SQLiteDatabase db,
int oldVersion, int newVersion) {

//
}
}
}

Klasa NotePadProvider rozszerza funkcjonalno klasy ContentProvider. Ta druga klasa


definiuje sze abstrakcyjnych metod, z ktrych cztery s operacjami CRUB (ang. Create,
Read, Update, Delete tworzenie, odczyt, aktualizacja, usuwanie). Pozostae dwie metody
to onCreate() oraz getType(). Zwrmy uwag, e metoda onCreate() jest wywoywana podczas
pierwszego utworzenia dostawcy treci. Z kolei metoda getType() dostarcza typ MIME dla zestawu wynikw (po przeczytaniu rozdziau 3. znaczenie typw MIME stanie si zrozumiae).
Innym interesujcym skadnikiem klasy NotePadProvider jest wewntrzna klasa Database
Helper, stanowica rozwinicie klasy SQLiteOpenHelper. Rol obydwu klas jest inicjalizacja,
otwieranie oraz zamykanie bazy danych aplikacji Notepad, a take wykonywanie innych operacji
bazodanowych. Co ciekawe, klasa DatabaseHelper skada si wycznie z kilku wierszy kodu (listing 2.5), podczas gdy wikszo pracy wykonuje implementacja klasy SQLiteOpenHelper.
Listing 2.5. Klasa DatabaseHelper
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + NOTES_TABLE_NAME + " ("
+ Notes._ID + " INTEGER PRIMARY KEY,"
+ Notes.TITLE + " TEXT,"
+ Notes.NOTE + " TEXT,"
+ Notes.CREATED_DATE + " INTEGER,"
+ Notes.MODIFIED_DATE + " INTEGER"
+ ");");
}

//
}

Jak zostao przedstawione na listingu 2.5, metoda onCreate() generuje tabel aplikacji Notepad. Naley zwrci uwag, e konstruktor klasy wywouje konstruktor superklasy za pomoc
nazwy tabeli. Superklasa wywoa metod onCreate() jedynie w wypadku, gdy taka tabela nie
istnieje w bazie danych. Warto rwnie zauway, e jedn z kolumn w tabeli aplikacji Notepad
jest _ID, omwiona kilka stron wczeniej.

78

Android 3. Tworzenie aplikacji

Przyjrzyjmy si teraz jednej z operacji CRUD: metodzie


stingu 2.6.

insert(),

przedstawionej na li-

Listing 2.6. Metoda insert()


//
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(NOTES_TABLE_NAME, Notes.NOTE, values);
if (rowId > 0) {
Uri noteUri = ContentUris.withAppendedId(
NotePad.Notes.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}

Metoda insert() wykorzystuje swoje wewntrzne wystpienie DatabaseHelper, eby uzyska


dostp do bazy danych, a nastpnie wstawia rekord notatek. Zwrcony identyfikator krotki
zostaje nastpnie doczony do identyfikatora URI, a taki nowy identyfikator zostaje zwrcony
aplikacji wywoujcej.
Czytelnik do tej pory powinien ju by zaznajomiony ze struktur aplikacji Androida. Poruszanie si w aplikacji Notepad, a take innych przykadowych programach nie powinno sprawia
problemw. Dobrym pomysem jest uruchomienie przykadowych aplikacji i zapoznanie si
z ich dziaaniem. Zajmijmy si teraz oglnym cyklem ycia aplikacji dla systemu Android.

Badanie cyklu ycia aplikacji


Cykl ycia aplikacji utworzonej na Androida jest cile zarzdzany przez system na podstawie potrzeb uytkownika, dostpnych zasobw i tak dalej. Uytkownik moe zechcie otworzy
na przykad przegldark internetow, ale ostatecznie to system decyduje, czy aplikacja zostanie uruchomiona. Chocia system jest gwnym zarzdc, postpuje zgodnie z pewnymi
zdefiniowanymi oraz logicznymi wytycznymi, pozwalajcymi okreli, czy aplikacja ma zosta wczytana, wstrzymana lub zatrzymana. Jeeli uytkownik korzysta aktualnie z aktywnoci, system wyznaczy tej aplikacji wysoki priorytet. Z drugiej strony, jeeli aktywno nie
jest widoczna, a system zdecyduje, e naley zamkn aplikacj w celu zwolnienia zasobw,
to zostanie zamknity program posiadajcy mniejszy priorytet.
Porwnajmy to z cyklem ycia aplikacji sieciowych, stworzonych w rodowisku J2EE. S one
w sposb luny zarzdzane przez kontener, w ktrym s uruchomione. Na przykad aplikacja moe
zosta usunita z pamici, w przypadku gdy jest ona bezczynna przez okrelony czas. Z reguy
jednak kontener nie bdzie umieszcza aplikacji w pamici oraz usuwa jej stamtd ze wzgldu
na obcienie oraz (lub) dostpno zasobw. Zazwyczaj dostpna jest wystarczajca ilo zasobw, eby jednoczenie mogo pozostawa uruchomionych wiele aplikacji. W przypadku Androida zasoby s bardziej ograniczone, wic system musi posiada wiksz kontrol nad aplikacjami.
W Androidzie kada aplikacja jest uruchomiona w oddzielnym procesie, posiadajcym
wasn wirtualn maszyn. W ten sposb zapewniono rodowisko chronionej pamici.
Poprzez przydzielenie aplikacji do indywidualnych procesw system moe okrela ich
priorytet. Na przykad uruchomiony w tle proces wykonujcy zadanie znacznie pochaniajce
zasoby procesora nie moe blokowa przychodzcego poczenia telefonicznego.

Rozdzia 2 Konfigurowanie rodowiska programowania

79

Koncepcja cyklu ycia aplikacji jest logiczna, jednak podstawowa struktura aplikacji systemu
Android komplikuje spraw. Gwoli cisoci, architektura aplikacji jest zorientowana na skadniki oraz integracj. Pozwala to na wzbogacenie dozna uytkownika, moliwo bezproblemowego wielokrotnego korzystania z aplikacji oraz atwo jej integracji, jednak przed menederem cyklu ycia stoi bardzo skomplikowane zadanie.
Rozwamy typowy scenariusz. Uytkownik rozmawia z kim przez telefon i musi otworzy
wiadomo e-mail, eby odpowiedzie na zadane przez rozmwc pytanie. Przechodzi do ekranu gwnego, otwiera aplikacj pocztow, klika adres cza do witryny zawierajcej poszukiwan wiadomo i przytacza jej fragment ze strony internetowej. W takim przypadku wymagane s cztery aplikacje: ekranu gwnego, telefonu, pocztowa oraz przegldarka. Uytkownik
w sposb cigy moe zmienia te aplikacje, jednak w tle system zapisuje oraz przywraca ich stan.
Przykadowo po klikniciu adresu cza w wiadomoci e-mail system zapisuje metadane uruchomionej aktywnoci tej wiadomoci, zanim przekae aktywnoci przegldarki dane potrzebne
do przekierowania na adres URL. Tak naprawd system zapisuje metadane kadej aktywnoci
przed uruchomieniem nastpnej, dziki czemu moe do niej wrci (na przykad gdy uytkownik wraca do poprzedniej strony). Jeeli wystpi problem z iloci pamici, zostanie zamknity
proces wykonujcy aktywno, a w razie koniecznoci zostanie wznowiony.
System Android jest wraliwy na cykl ycia aplikacji oraz jej elementw skadowych. Zatem eby
stworzy stabiln aplikacj, naley zrozumie zdarzenia cyklu ycia oraz nauczy si nimi posugiwa. Procesy korzystajce z danej aplikacji oraz jej skadnikw natrafiaj na rnorodne
zdarzenia cyklu ycia i istnieje moliwo zaimplementowania wywoa zwrotnych, zajmujcych
si zmianami ich stanu. Na pocztek warto zapozna si z wywoaniami cyklu ycia aktywnoci
(listing 2.7).
Listing 2.7. Metody cyklu ycia aktywnoci
protected
protected
protected
protected
protected
protected
protected

void
void
void
void
void
void
void

onCreate(Bundle savedInstanceState);
onStart();
onRestart();
onResume();
onPause();
onStop();
onDestroy();

Na listingu 2.7 zostay wypisane metody, ktre s wywoywane podczas cyklu ycia aktywnoci.
Dla stworzenia stabilnej struktury aplikacji istotne jest zrozumienie, kiedy dana metoda jest
wywoywana przez system. Nie wszystkie metody musz by implementowane. Jeeli zostan uyte
wszystkie wywoania, naley rwnie stworzy analogiczne wersje dla superklas. Na rysunku
2.15 zostay pokazane przejcia pomidzy stanami aktywnoci.
System moe uruchamia oraz zatrzymywa aktywnoci w zalenoci od tego, co si w nim
dzieje. Metoda onCreate() jest wywoywana podczas pierwszego utworzenia aktywnoci.
Po tej metodzie zawsze pojawia si metoda onStart(), jednak wywoanie metody onCreate()
nie zawsze nastpuje przed wywoaniem onCreate(), gdy metoda ta moe zosta wywoana
w przypadku zatrzymania aplikacji. Po wywoaniu metody onStart() aktywno nie jest jeszcze
dostpna dla uytkownika. Po metodzie onStart() wywoywana jest metoda onResume(),
w momencie gdy aktywno znajduje si na pierwszym planie i jest dostpna dla uytkownika.
To wanie teraz uytkownik moe bezporednio korzysta z aplikacji.

80

Android 3. Tworzenie aplikacji

Rysunek 2.15. Zmiany stanw aktywnoci

Kiedy uytkownik zdecyduje si skorzysta z innej aktywnoci, system przywoa metod


onPause() dla opuszczanej aktywnoci. Z tego miejsca moe zosta wywoana metoda
onResume() lub onStop(). Ta pierwsza metoda jest wywoywana na przykad wtedy, gdy
uytkownik przywrci aktywno na pierwszy plan. Jeeli stanie si ona niewidoczna dla
uytkownika, zostanie wywoana metoda onStop(). Jeeli po tym wywoaniu aktywno zostanie przywrcona na pierwszy plan, nastpi przywoanie metody onRestart(). Jeeli aktywno znajduje si w stosie uywanych aktywnoci, lecz jest niewidoczna dla uytkownika, a system postanowi j zakoczy, zostanie wywoana metoda onDestroy().
Omwiony model stanw aktywnoci moe wydawa si skomplikowany, jednak umieszczenie
wszystkich metod nie jest konieczne. Tak naprawd najczciej bd wykorzystywane metody
onCreate(), onResume() oraz onPause(). Pierwsza metoda bdzie suy do tworzenia interfejsu UI danej aktywnoci. W tej metodzie dane bd wizane z widetami, a procedury obsugi zdarze z elementami interfejsu uytkownika. Metoda onPause() jest wykorzystywana
w przypadku koniecznoci przechowywania istotnych danych w magazynie aplikacji. Jest to
ostatnia bezpieczna metoda wywoywana przed zamkniciem aplikacji. Metody onStop() oraz
onDestroy() nie zawsze s wywoywane, wic nie naley na nie liczy w przypadku tworzenia
szczeglnie wanych programw.
Jakie wnioski powinny si nasuwa z powyszych wywodw? System zarzdza aplikacj i moe
w kadej chwili uruchomi, zatrzyma lub przywrci kady z jej skadnikw. Chocia skadniki
te s kontrolowane przez system, nie s one cakowicie oddzielone od aplikacji. Innymi sowy,
jeeli system uruchomi aktywno w aplikacji, mona liczy na kontekst aplikacji w tej aktywnoci. Na przykad istnieje moliwo posiadania zmiennych globalnych, wspdzielonych
przez aktywnoci w aplikacji. Tak zmienn globaln tworzy si poprzez napisanie rozszerzenia klasy android.app.Application, a nastpnie inicjowanie jej w metodzie onCreate()
(listing 2.8). Aktywnoci oraz inne skadniki aplikacji bd uzyskiway dostp do tych odniesie
bez obaw, e nie zostan uruchomione. Koncepcja ta zostanie szczegowo omwiona
w rozdziale 11.

Rozdzia 2 Konfigurowanie rodowiska programowania

81

Listing 2.8. Rozszerzenie klasy Application


public class MyApplication extends Application
{

// zmienna globalna
private static final String myGlobalVariable;
@Override
public void onCreate()
{
super.onCreate();

// ...tutaj nastpuje inicjacja zmiennych globalnych


myGlobalVariable = loadCacheData();
}
public static String getMyGlobalVariable() {
return myGlobalVariable;
}
}

Do tej pory omwilimy podstawy tworzenia aplikacji w systemie Android, uruchamianie programu na emulatorze, ogln budow aplikacji oraz kilka najpowszechniejszych funkcji spotykanych w tych programach. Nie pokazalimy jednak, w jaki sposb naley rozwizywa problemy
pojawiajce si podczas pisania aplikacji. W ostatnim podrozdziale omwimy usuwanie bdw
z programu.

Usuwanie bdw w aplikacji


Po napisaniu kilku wierszy pierwszej aplikacji wiele osb z pewnoci zacznie si zastanawia,
czy bdzie moliwe przeprowadzenie sesji usuwania bdw podczas korzystania z aplikacji.
Odpowied brzmi: tak. Zestaw Android SDK zosta zaopatrzony w wiele narzdzi pozwalajcych na sprawdzanie aplikacji pod ktem bdw. S one zintegrowane ze rodowiskiem Eclipse
(niewielki przykad mona ujrze na rysunku 2.16).

Rysunek 2.16. Narzdzia do usuwania bdw, ktre mona wykorzysta podczas tworzenia aplikacji

Jednym z takich narzdzi jest LogCat. Aplikacja ta wywietla komunikaty dziennika tworzone
podczas korzystania z klas android.util.Log, System.out.println, wyjtkw i tak dalej.
Podczas gdy klasa System.out.println dziaa i informacje s wywietlane w oknie LogCat,

82

Android 3. Tworzenie aplikacji

do wywietlenia komunikatw z aplikacji naley uy klasy android.util.Log. S w niej


zdefiniowane znajome metody informacyjne, ostrzee oraz bdw, ktre mona filtrowa
w oknie LogCat. Przykadem polecenia Log jest:
Log.v("string TAG", "Ta rozwlekla wiadomosc zostanie zapisana w dzienniku");

Szczeglnie interesujc funkcj narzdzia LogCat jest moliwo przegldania komunikatw


dziennika podczas testowania aplikacji na emulatorze. Nic jednak nie stoi na przeszkodzie,
aby przeglda je w przypadku podczonego rzeczywistego urzdzenia do stacji roboczej, gdy
znajduje si w trybie debugowania. W rzeczywistoci s one przechowywane w taki sposb, e
moemy odzyska wikszo najnowszych wiadomoci ju po odczeniu urzdzenia, w trakcie
ich rejestrowania. W momencie podczenia urzdzenia przy wczonym oknie LogCat ujrzymy kilkaset najnowszych wpisw dziennika.
Naley zdawa sobie spraw z dwch faktw dotyczcych debugowania aplikacji na fizycznym
urzdzeniu. Po pierwsze, aplikacja musi by ustawiona w trybie usuwania bdw w pliku
AndroidManifest.xml. W tym celu naley doda atrybut android:debuggable="true" w znaczniku <application>. Na szczcie wtyczka ADT wykona to automatycznie. Podczas tworzenia aplikacji testowych uruchamianych na emulatorze albo w przypadku bezporedniego
wdraania aplikacji ze rodowiska Eclipse na urzdzenie fizyczne atrybut ten uzyskuje warto true. W przypadku eksportowania aplikacji przeznaczonej ju do uytkowania wtyczka
ADT nie uruchomi trybu debugowania. Warto zauway, e po samodzielnym ustawieniu
wartoci tego atrybutu w pliku AndroidManifest.xml nie ulegnie on zmianie, bez wzgldu na
okolicznoci. Druga wana informacja jest taka, e urzdzenie musi znajdowa si w trybie
debugowania USB. Opcj t znajdziemy, wybierajc z menu Ustawienia opcj Aplikacje/Dla
programistw. Naley tutaj zaznaczy opcj Debugowanie USB.
Chocia narzdzie LogCat jest bardzo pomocne podczas przegldania komunikatw dziennika,
w trakcie dziaania aplikacji zdecydowanie warto mie nad ni wicej kontroli oraz informacji na
jej temat. W rodowisku Eclipse istniej dwie perspektywy, z ktrymi warto si zaznajomi:
DDMS i Debug. Skrt DDMS mona rozwin jako Dalvik Debug Monitor Server (serwer monitora debugowania w rodowisku Dalvik). Za pomoc tej perspektywy moemy obserwowa
poszczeglne elementy aplikacji uruchomionej na emulatorze lub urzdzeniu fizycznym
obserwowa jej wtki, stert (lub pami) wewntrz aplikacji, a take uzyska dostp do eksploratora plikw oraz kontrolera emulatora, dziki ktremu moemy symulowa zdarzenia generowane przez system GPS, przychodzce poczenia telefoniczne lub wiadomoci SMS. Eksplorator plikw umoliwia przegldanie systemu plikw w urzdzeniu, a nawet przenoszenie
danych pomidzy urzdzeniem (lub emulatorem) a stacj robocz. Mona rwnie wymusza
odzyskiwanie pamici, usuwanie aplikacji z pamici oraz wykonywa migawki.
Z poziomu perspektywy DDMS moemy wybra jedn z uruchomionych aplikacji i podczy
j w trybie testowania. Zostanie wtedy uruchomiona perspektywa Debug. Debugowanie aplikacji
rozpoczynamy rwnie z poziomu perspektywy Java poprzez kliknicie w jej obszarze prawym
przyciskiem myszy i wybranie opcji Debug As/Android Application; w ten sposb rwnie zyskamy dostp do perspektywy Debug. W kadym bd razie rodowisko Eclipse posiada funkcje
umoliwiajce ledzenie wtkw, ustanawianie i usuwanie punktw kontrolnych w kodzie,
kontrolowanie zmiennych oraz sprawdzanie lub pomijanie instrukcji. Jest to potne narzdzie,
pozwalajce na rozwizywanie problemw z aplikacjami.
Te narzdzia mona przeglda poprzez zaznaczenie perspektywy DDMS lub Debug w rodowisku Eclipse. Kade z tych narzdzi mona rwnie uruchomi, wybierajc Window/Show
View/Other/Android. Jeeli na przykad chcemy umieci okno LogCat lub File Explorer w perspektywie Java, wystarczy wybra Window/Show View, aby je doda.

Rozdzia 2 Konfigurowanie rodowiska programowania

83

Istnieje take moliwo dokadnego ledzenia aplikacji za pomoc klasy android.os.Debug,


zawierajcej metod rozpoczcia ledzenia (Debug.startMethodTracing(nazwa_bazowa))
oraz zakoczenia ledzenia (Debug.stopMethodTracing()). W urzdzeniu (lub w emulatorze)
zostanie utworzony plik ledzenia, dokadniej powstanie on na karcie SD. Nazwa tego pliku
powstaje wedug wzorca nazwa_bazowa.trace. Mona go nastpnie skopiowa do stacji roboczej i obserwowa dane wyjciowe znacznika za pomoc narzdzia traceview, znajdujcego si
w katalogu tools zestawu SDK, gdzie jedynym argumentem jest nazwa pliku ledzenia. Rozdzia 19. zosta powicony kartom SD oraz metodom kopiowania z nich plikw.
Mamy rwnie do dyspozycji kilka innych narzdzi debugowania, ktre mona wykorzysta
z poziomu wiersza polece (lub okna narzdzi). Polecenie adb (ang. Android Debug Bridge
most debugowania w systemie Android) pozwala na instalowanie, aktualizowanie oraz usuwanie aplikacji. Mona uruchomi powok na emulatorze lub w urzdzeniu i wyprowadzi
stamtd szereg linuksowych polece, zrozumiaych dla systemu Android. Na przykad w ten
sposb przegldamy system plikw, list procesw, czytamy wpisy dziennika, a nawet czymy
si z bazami danych SQLite i wykonujemy polecenia jzyka SQL.
Kolejn przydatn technik jest uruchomienie narzdzia Emulator Control, ktre z oczywistych
wzgldw wsppracuje wycznie z emulatorem. Aby je uruchomi (gdy emulator jest ju wczony), naley wpisa nastpujce polecenie w oknie narzdzi:
telnet localhost port#

gdzie port# jest numerem portu, na ktrym nasuchuje emulator. Warto tego parametru jest
zazwyczaj podana w pasku tytuowym emulatora i czsto wynosi ona 5554. Po uruchomieniu
konsoli emulatora moemy wpisywa polecenia pozwalajce na symulowanie zdarze zwizanych z systemem GPS, wiadomociami SMS, a nawet na zmian sieci i poziomu naadowania baterii.

Uruchamianie emulatora
Pokazalimy wczeniej, w jaki sposb mona uruchomi emulator z poziomu projektu w rodowisku Eclipse. W wikszoci przypadkw chcemy najpierw wczy emulator, a nastpnie wdroy i przetestowa aplikacj w ju uruchomionym emulatorze. Aby go uruchomi w dowolnym
momencie, musimy najpierw przej do narzdzia Android SDK and AVD Manager, albo uruchamiajc je bezporednio w katalogu tools pakietu Android SDK, albo wybierajc je w oknie
Window rodowiska Eclipse. Gdy ju uruchomimy meneder, klikamy zakadk Virtual devices,
widoczn w panelu po lewej stronie, wybieramy waciwe urzdzenie AVD z listy w prawym
oknie i klikamy przycisk Start.
Po jego wciniciu pojawi si okno dialogowe Launch Options (rysunek 2.17). Moemy w nim
definiowa rozmiar okna emulatora oraz zmienia opcje jego rozruchu i zamykania. Podczas
pracy z urzdzeniami AVD imitujcymi urzdzenia posiadajce mae lub rednie wywietlacze
bdziemy czsto ogranicza si do domylnego rozmiaru ekranu. Jednak w przypadku duych
i bardzo duych rozmiarw ekranu, na przykad takich jak w tabletach, domylne wymiary
wywietlacza mog nie pasowa do rozmiaru monitora stacji roboczej. W takim przypadku
moemy zaznaczy opcj Scale display to real size (skaluj wywietlacz do rzeczywistego rozmiaru) i wstawi odpowiedni warto. Nazwa tej opcji moe by nieco mylca, poniewa tablety
mog posiada inne gstoci wywietlacza od stacji roboczej, natomiast emulator nie potrafi
dokadnie odwzorowa fizycznych parametrw wywietlacza na ekranie monitora. Przykadowo na mojej stacji roboczej, podczas symulowania tabletu obsugujcego platform Honeycomb

84

Android 3. Tworzenie aplikacji

Rysunek 2.17. Okno dialogowe Launch Options

o rozmiarach 10 cali, rzeczywisty rozmiar 10 cali zostaje przeskalowany o wspczynnik


0,64 i rozmiar mojego monitora, ktry jest nieco wikszy od 10 cali. Na podstawie wielkoci
i gstoci monitora naley wybra najbardziej odpowiedni warto.
Okno dialogowe Launch Options pozwala rwnie konfigurowa migawki. Zapisanie migawki
(Save to snapshot) nieco wyduy czas zamykania emulatora. Jak sama nazwa wskazuje, biecy
stan emulatora zostaje zapisany w pliku-obrazie migawki, ktry mona wykorzysta podczas
nastpnego uruchomienia emulatora, w celu pominicia caej sekwencji rozruchu systemu.
Uruchamianie emulatora przebiega znacznie szybciej w obecnoci migawki i rekompensuje
czas potrzebny na jej zapisanie zasadniczo rozpoczynamy prac od miejsca, w ktrym j
zakoczylimy. Jeeli chcemy uruchomi emulator w jego pierwotnym stanie, zaznaczamy
opcj Wipe user data. Moemy rwnie usun zaznaczenie opcji Launch from snapshot,
aby umoliwi zapisywanie danych wraz z jednoczesnym przeprowadzaniem caego procesu
rozruchu. Ewentualnie istnieje moliwo utworzenia optymalnej migawki i pozostawienia
wycznie opcji Lauch from snapshot; w ten sposb dana migawka bdzie cigle wykorzystywana, co spowoduje przyspieszenie zarwno procesu uruchamiania emulatora, jak te
jego zamykania, poniewa nie bdzie za kadym razem tworzony nowy obraz migawki. Plik
migawki jest przechowywany w tym samym katalogu co reszta plikw-obrazw urzdzenia
AVD. Aby mie moliwo korzystania z funkcji migawek, musimy zaznaczy odpowiedni
opcj podczas tworzenia urzdzenia AVD.

StrictMode
Wraz z wydaniem Androida w wersji 2.3 zostaa wprowadzona nowa funkcja debugowania,
nazwana StrictMode. Opcja ta wedug firmy Google zostaa wykorzystana do wprowadzenia setek usprawnie w aplikacjach tej firmy stworzonych z myl o tym systemie. Do
czego wic waciwie ona suy? Bdzie powiadamiaa o naruszeniach zasad powizanych
z wtkami oraz wirtualn maszyn. Po wykryciu naruszenia zasad funkcja wywietli alert
z odniesieniem do stosu, w ktrym znajdowaa si aplikacja w momencie naruszenia zabezpiecze. Za pomoc alertu mona wymusi zamknicie aplikacji lub jedynie zapisa tre
alertu w dzienniku i pozwoli aplikacji na dalsze dziaanie. Obecnie trudno okreli szczegy
wspomnianych zasad, spodziewamy si te, e firma Google bdzie dodawaa kolejne zasady
wraz z rozwojem Androida.

Rozdzia 2 Konfigurowanie rodowiska programowania

85

Obecnie s dostpne dwa rodzaje zasad w obrbie funkcji StrictMode. Pierwszy z nich jest
zwizany z wtkami i jego podstawowym zadaniem jest wsppraca z gwnym wtkiem (zwanym rwnie wtkiem interfejsu uytkownika). Prowadzenie zapisu oraz odczytu danych dyskowych w obrbie gwnego wtku nie jest dobrym rozwizaniem, podobnie jak uzyskiwanie
za jego pomoc dostpu do sieci. Firma Google dodaa punkty zaczepienia funkcji StrictMode
do kodu odpowiedzialnego za operacje zapisu-odczytu oraz operacje sieciowe. Jeeli uruchomimy funkcj StrictMode w jednym z wtkw, ktry prbuje uzyska dostp do przestrzeni
dyskowej lub sieci, zostaniemy o tym powiadomieni. Musimy wybra, ktre aspekty zasad
ThreadPolicy spowoduj wywoanie alertu, oraz rodzaj alertu. Wrd narusze zasad, na ktre
moemy zwraca uwag, znajdziemy takie jak niestandardowo powolne wywoania, odczyty
danych z dysku, zapisy danych na dysku oraz dostp do sieci. Spord rodzajw alertw mamy
do wyboru zapis komunikatu w narzdziu LogCat, wywietlenie okna dialogowego, bynicie
wywietlacza, zapis komunikatu w pliku dziennika DropBox lub zawieszenie dziaania aplikacji.
Najczciej spotykamy si z zapisywaniem informacji w narzdziu LogCat oraz zawieszeniem
dziaania aplikacji. Na listingu 2.9 przedstawiono przykadowy sposb konfigurowania funkcji
StrictMode pod ktem zasad dotyczcych wtkw.
Listing 2.9. Konfigurowanie zasad ThreadPolicy funkcji StrictMode
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());

Zwrmy uwag, e za pomoc klasy Builder mona w naprawd prosty sposb ustanowi
funkcj StrictMode. Wszystkie metody tej klasy, definiujce zasady, zwracaj odniesienie do
obiektu Builder, zatem mona stworzy z nich acuch, podobnie jak na listingu 2.9. Ostatnia
z wywoywanych metod, build(), zwraca obiekt ThreadPolicy, ktry jest argumentem oczekiwanym przez metod setThreadPolicy() funkcji StrictMode. Zwrmy uwag, e metoda
setThreadPolicy() jest statyczna, zatem tak naprawd nie musimy tworzy obiektu StrictMode.
Metoda setThreadPolicy() bada biecy wtek pod ktem zasad, zatem wszystkie nastpne
dziaania wtku zostan porwnane z obiektem ThreadPolicy i w razie potrzeby zostanie wywietlony alert. W powyszym kodzie zasady s tak zdefiniowane, e alert zostanie wygenerowany w przypadku odczytywania i zapisywania danych dyskowych oraz dostpu do sieci
i zostanie on zapisany w dzienniku LogCat. Zamiast wypisywania poszczeglnych metod
wykrywania moemy zastosowa metod detectAll(). Nie ma rwnie przeszkd, by dodawa lub wymienia metody odpowiedzialne za ostrzeenia. Na przykad moemy wprowadzi metod penaltyDeath(), ktra spowoduje zawieszenie dziaania aplikacji zaraz po zapisaniu komunikatu w narzdziu LogCat (z kolei za to zdarzenie jest odpowiedzialna metoda
penaltyLog()).
Poniewa ten rodzaj funkcji StrictMode dotyczy wtku, dla danego wtku funkcja ta uruchamia si jednorazowo. Z tego powodu mona uruchomi funkcj StrictMode na pocztku
metody onCreate(), przypisanej do gwnej aktywnoci, dziaajcej w gwnym wtku, i od tego
momentu funkcja ta ledziaby wszystkie dziaania przeprowadzane w tym wtku. W zalenoci
od rodzaju poszukiwanego naruszenia pierwsza aktywno moe do szybko uruchomi funkcj StrictMode. Moemy j rwnie uruchomi dla aplikacji poprzez rozszerzenie klasy
Application i dodanie konfiguracji funkcji StrictMode do metody onCreate() caej aplikacji.

86

Android 3. Tworzenie aplikacji

Potencjalnie kady element obecny w wtku moe uruchomi funkcj StrictMode, zdecydowanie jednak nie musimy wywoywa kodu konfiguracyjnego we wszystkich miejscach; jeden raz
cakowicie wystarczy.
Analogicznie do zasad ThreadPolicy, funkcja StrictMode zawiera zasady VmPolicy. Su one
do sprawdzania wyciekw pamici, w przypadku gdy obiekt bazy SQLite lub dowolny inny
obiekt typu Closeable zostanie zakoczony przed zamkniciem. Zasady VmPolicy s tworzone
w podobny sposb za pomoc klasy Builder, co zostao przedstawione na listingu 2.10. Jedyna
rnica pomidzy zasadami ThreadPolicy a VmPolicy polega na niemonoci wywietlenia
alertu jako okna dialogowego w tym drugim przypadku.
Listing 2.10. Konfigurowanie zasad VmPolicy funkcji StrictMode
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.penaltyLog()
.penaltyDeath()
.build());

Poniewa proces konfiguracji jest przeprowadzany w wtku, funkcja StrictMode bdzie wykrywaa naruszenia nawet w przypadku kontrolnego przepywu pomidzy obiektami. Po
wystpieniu naruszenia zasad bezpieczestwa moemy si zdziwi, gdy zauwaymy, e kod
jest cigle przetwarzany w gwnym wtku, ale otrzymujemy te lad stosu, dziki ktremu
moemy odkry, co si stao. Nastpnie mona sprbowa rozwiza problem poprzez przeniesienie danego fragmentu kodu do osobnego wtku. Mona rwnie pozostawi kod bez
zmian. Wszystko zaley od programisty. Oczywicie, najprawdopodobniej bdzie trzeba wyczy funkcj StrictMode tu przed wydaniem aplikacji na rynek; nie byoby korzystne, aby
programy zawieszay si uytkownikom z powodu alertw.
Istnieje kilka sposobw wyczenia funkcji StrictMode przed wdroeniem aplikacji do uytkowania. Najprostszym rozwizaniem jest usunicie wywoa, jednak w pniejszych etapach staje
si ono coraz bardziej skomplikowane. Mona rwnie wprowadzi logik dwuwartociow na
poziomie aplikacji i przeprowadzi test przed wywoaniem kodu funkcji StrictMode. W takim
przypadku ustanowienie wartoci false tu przed okazaniem aplikacji wiatu w skuteczny
sposb wyczyoby t funkcj. Bardziej eleganckim rozwizaniem jest wykorzystanie trybu
debugowania aplikacji, zdefiniowanego w pliku AndroidManifest.xml. Jednym z atrybutw
znacznika <application> w tym pliku jest android:debuggable. Jego warto mona ustawi
jako true w trakcie debugowania aplikacji, w wyniku czego na obiekcie ApplicationInfo
zostaje ustanowiona flaga, co mona nastpnie odczyta w kodzie. Na listingu 2.11 pokazano,
w jaki sposb mona skorzysta z tej informacji, aby w trybie debugowania aplikacja posiadaa
aktywn funkcj StrictMode (a jeli aplikacja nie bdzie w trybie debugowania, funkcja ta
zostanie zdezaktywowana).
Listing 2.11. Ustanawianie funkcji StrictMode wycznie w trybie debugowania
// Wraca tutaj, jeli aplikacja nie znajduje si w trybie debugowania
ApplicationInfo appInfo = context.getApplicationInfo();
int appFlags = appInfo.flags;
if ((appFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {

// Tutaj przeprowadzana jest konfiguracja funkcji StrictMode


}

Rozdzia 2 Konfigurowanie rodowiska programowania

87

Podczas pisania aplikacji w rodowisku Eclipse wtyczka ADT automatycznie ustanawia atrybut
debugowania, co stanowi spore uatwienie. W trakcie wdraania aplikacji ze rodowiska Eclipse do emulatora lub bezporednio do urzdzenia fizycznego atrybut ten otrzyma warto
true, co spowodowaoby uruchomienie kodu funkcji StrictMode w powyszym kodzie.
Podczas eksportowania aplikacji do wersji przeznaczonej do uytkowania warto atrybutu
debuggable zostanie zmieniona na false. Naley jednak pamita, e po rcznej zmianie
tego atrybutu nie bdzie on automatycznie modyfikowany.
Wszystko to brzmi bardzo adnie i elegancko, ale nie zadziaa na wersji Androida starszej od
2.3. Aby mc jawnie uywa funkcji StrictMode, musimy wykorzystywa rodowisko obsugujce Androida w wersji 2.3 lub nowsze. W przypadku wprowadzenia powyszych kodw
do rodowiska starszego od wersji 2.3 zaczn powstawa bdy weryfikacji, poniewa ta klasa po
prostu nie istnieje w tym rodowisku.
Aby wykorzystywa funkcj StrictMode w starszych wersjach Androida (do wersji 2.3), naley
zastosowa mechanizm refleksji. Dziki temu mona wywoa metody tej funkcji w sposb
poredni, jeli s dostpne. Jeli nie s dostpne, to moesz ponie sromotn porak. Najprostsze rozwizanie zostao przedstawione na listingu 2.12; wywoujemy specjaln metod,
stworzon wycznie dla starszych wersji Androida.
Listing 2.12. Wykorzystanie funkcji StrictMode za pomoc refleksji
try {
Class sMode = Class.forName("android.os.StrictMode");
Method enableDefaults = sMode.getMethod("enableDefaults");
enableDefaults.invoke(null);
}
catch(Exception e) {

// Funkcja StrictMode nieobsugiwana na tym urzdzeniu; zaniechanie


Log.v("StrictMode", "... nieobsugiwana. Pomijanie...");
}

W ten sposb mona okreli, czy klasa StrictMode istnieje. Jeeli istnieje, nastpi wywoanie
metody enableDefaults(). Jeeli klasa ta nie zostanie znaleziona, zostaje wywoany nasz
blok catch wraz z wyjtkiem ClassNotFoundException. Jeeli funkcja StrictMode istnieje, nie
powinny pojawia si wyjtki, poniewa jedn z jej metod jest enableDefaults(). Metoda
ta sprawia, e funkcja StrictMode wyapuje wszystkie naruszenia zasad i zapisuje je w dzienniku LogCat. Poniewa ta metoda jest statyczna, pierwszy argument przyjmuje warto null
podczas jej wywoywania.
Mog si zdarza sytuacje, w ktrych zapisywanie wszystkich narusze jest niepodane. Nic nie
stoi na przeszkodzie, aby docza funkcj StrictMode do wtkw innych od gwnego, i to
wanie wtedy moemy ustanowi mniejsz liczb alertw. Dobrym przykadem byoby monitorowanie wtku, ktry suy do odczytu danych. W takim przypadku moemy albo nie wywoywa metody detectDiskReads() w obiekcie Builder, albo wywoa metod detectAll(), a nastpnie permitDiskReads() w tym obiekcie. Istniej rwnie analogiczne metody zezwole
dla pozostaych opcji zasad. Ale gdybymy chcieli dokona czego podobnego w wersjach
Androida starszych od 2.3, to czy istnieje na to jaki sposb? Oczywicie, e tak!
Jeeli funkcja StrictMode nie jest dostpna dla danej aplikacji, w przypadku prby jej aktywowania zostanie wywietlony komunikat o bdzie VerifyError. Jeli umiecimy t funkcj
w klasie i nastpnie otrzymamy taki komunikat o bdzie, nie musimy si przejmowa, gdy nie

88

Android 3. Tworzenie aplikacji

bdzie ona dostpna, a gdy bdzie dostpna wykorzystamy j. Na listingu 2.13 widzimy
przykadow klas StrictModeWrapper, ktr mona doda do aplikacji, natomiast listing
2.14 przedstawia kod wewntrz aplikacji sucy do konfigurowania funkcji StrictMode.
Listing 2.13. Stosowanie funkcji StrictMode w Androidzie starszym od wersji 2.3
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.StrictMode;
public class StrictModeWrapper {
public static void init(Context context) {

// sprawdza, czy atrybut android:debuggable posiada warto true


int appFlags = context.getApplicationInfo().flags;
if ((appFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
}
}

Jak wida, mamy tu do czynienia z takim samym kodem jak wczeniej, tutaj jednak czymy
w cao wszystkie zdobyte wczeniej informacje. Wreszcie, aby skonfigurowa funkcj
StrictMode w aplikacji, wystarczy doda do niej kod widoczny na listingu 2.14:
Listing 2.14. Wywoywanie funkcji StrictMode w Androidzie starszym od wersji 2.3
try {
StrictModeWrapper.init(this);
}
catch(Throwable throwable) {
Log.v("StrictMode", "... jest nieosigalna. Zaniechanie...");
}

Zwrmy uwag, e this jest lokalnym kontekstem dowolnego obiektu, ktrym si zajmujemy,
na przykad z wntrza metody onCreate() bdcej czci gwnej aktywnoci. Kod z listingu
2.14 bdzie dziaa z dowoln wersj systemu Android.
W ramach wiczenia Czytelnik moe uruchomi rodowisko Eclipse i stworzy kopi aplikacji
Notepad, wygenerowanej we wczeniejszej czci rozdziau. Nastpnie mona doda now
klas w katalogu src, wykorzystujc kod z listingu 2.13. Wewntrz metody onCreate() w pliku
NotesList.java naley teraz doda taki kod jak na listingu 2.14, po czym uruchomi program
na emulatorze obsugujcym Androida w wersji starszej od 2.3. To samo mona nastpnie
sprawdzi dla wersji 2.3 lub pniejszej. Jeeli funkcja StrictMode bdzie niedostpna, w oknie

Rozdzia 2 Konfigurowanie rodowiska programowania

89

LogCat pojawi si informacja o jej braku, jednak aplikacja powinna dalej nieprzerwanie
dziaa. W przypadku obecnoci funkcji StrictMode w oknie LogCat powinny od czasu do czasu
pojawia si informacje o naruszeniach zasad podczas korzystania z aplikacji Notepad.

Odnoniki
Poniej przedstawiamy pomocne odnoniki do tematw, ktre Czytelnik moe zechcie pozna
dokadniej.
http://developer.motorola.com/docstools/ jest witryn firmy Motorola, na ktrej
mona znale dodatki do urzdze oraz inne narzdzia programistyczne przystosowane
do mikrotelefonw tego producenta, w tym takie jak MOTODEV Studio alternatyw
dla rodowiska Eclipse.
http://developer.htc.com/ to witryna firmy HTC przeznaczona dla programistw
w systemie Android.
http://innovator.samsungmobile.com/platform.main.do?platformId=1 stanowi stron
firmy Samsung dla programistw w systemie Android, na ktrej mona znale dodatek
zestawu Android SDK dla tabletu Samsung Galaxy Tab.
http://developer.android.com/guide/developing/tools/index.html zawiera dokumentacj
programistyczn dla uprzednio opisanych narzdzi debugujcych.
http://appinventor.googlelabs.com/about/index.html jest stron rodowiska App
Inventor, kolejnej alternatywy sucej do tworzenia aplikacji dla systemu Android.
Za stworzenie tego rodowiska odpowiada firma Google Labs i jest ono przeznaczone
dla osb niebdcych programistami. Aplikacje s tutaj tworzone w sposb graficzny,
podobnie jak logika stojca za interfejsem uytkownika.
http://code.google.com/p/android-ui-utils/ zawiera cza do uytecznych narzdzi,
takich jak Android Asset Studio, ktre jest aplikacj sieciow suc do tworzenia
rnorodnych rodzajw ikon dla systemu Android. Warto zwrci uwag, e do
obsugi aplikacji Android Asset Studio wymagane jest uruchomienie przegldarki
Google Chrome.
http://www.droiddraw.org/ narzdzie do projektowania interfejsw uytkownika,
w ktrym do tworzenia ukadw graficznych jest wykorzystywana funkcja przecigania.

Podsumowanie
W tym rozdziale zademonstrowalimy, w jaki sposb naley skonfigurowa rodowisko
projektowe do tworzenia aplikacji dla systemu Android. Opisalimy podstawowe elementy
budulcowe interfejsu API Androida, a take wprowadzilimy pojcia widokw, aktywnoci,
intencji, dostawcw treci oraz usug. W dalszej czci przeanalizowalimy struktur aplikacji
Notepad pod ktem wspomnianych ju blokw budulcowych oraz skadnikw aplikacji. Nastpnie omwilimy istot cyklu ycia aplikacji pisanych na Androida. Na kocu wspomnielimy
o narzdziach do usuwania bdw zaimplementowanych w zestawie Android SDK, zintegrowanych ze rodowiskiem Eclipse.
A teraz wprowadzimy podstawy projektowania dla Androida. Nastpny rozdzia zosta powicony zasobom.

90

Android 3. Tworzenie aplikacji

R OZDZIA

3
Korzystanie z zasobw

W rozdziale 2. skrtowo omwilimy struktur aplikacji tworzonej dla Androida


oraz wspomnielimy o pewnych kluczowych pojciach. Opisalimy take zestaw
Android SDK, narzdzia ADT rodowiska Eclipse oraz moliwo uruchamiania
aplikacji na emulatorach urzdze AVD.
W tym oraz w kilku nastpnych rozdziaach bdziemy kontynuowa szczegowe przedstawianie podstaw pracy z zestawem Android SDK. Zajmiemy si zasobami, dostawcami treci oraz intencjami. S to trzy elementy niezbdne do nauczenia si programowania dla systemu Android, a ich solidne opanowanie
pozwoli na zrozumienie materiau opisanego w nastpnych rozdziaach.
Android wymaga dostpu do zasobw w celu definiowania elementw interfejsu
uytkownika w deklaracyjny sposb. Metoda ta nie rni si wiele od stosowania
znacznikw w jzyku HTML. W tym sensie projektowanie interfejsu UI w Androidzie jest dosy nowatorskie. Moliwe jest take okrelanie lokalizacji tych zasobw.
W tym rozdziale zajmiemy si opisem wielkiej rnorodnoci zasobw dostpnych
w Androidzie oraz wyjanimy, w jaki sposb mona ich uywa.

Zasoby
Zasoby odgrywaj kluczow rol w architekturze Androida. W Androidzie zasobem moe by plik (na przykad plik muzyczny) lub warto (przykadowo nazwa
okna dialogowego), powizane z wykonywaln aplikacj. Te pliki i wartoci s z ni
powizane w sposb umoliwiajcy ich modyfikowanie bez koniecznoci ponownego kompilowania aplikacji.
Znanymi Czytelnikowi rodzajami zasobw s cigi znakw, kolory oraz mapy bitowe.
Zamiast umieszcza na przykad cigi znakw w kodzie aplikacji, mona wykorzysta ich identyfikatory. W ten sposb moliwe staje si zmienianie tekstu w zasobie
bez potrzeby ingerowania w kod rdowy.
Istnieje bardzo wiele rnorodnych rodzajw zasobw w Androidzie. W tym rozdziale postaramy si omwi wikszo z nich. Rozpocznijmy od przedyskutowania
bardzo powszechnego rodzaju zasobw: cigu znakw.

92

Android 3. Tworzenie aplikacji

Zasoby typu string


Android umoliwia definiowanie cigw znakw (ang. string) w co najmniej jednym pliku
zasobw XML. Pliki te, zawierajce definicje zasobw typu string, s umieszczone w podkatalogu /res/values. Nazwy plikw XML mog by dowolne, jednak najczciej spotykany bdzie plik zatytuowany strings.xml. Listing 3.1 przedstawia przykad pliku zawierajcego zasb
typu string.
Listing 3.1. Przykadowy plik strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Witaj</string>
<string name="app_name">Witaj, nazwo aplikacji</string>
</resources>

Zwrmy uwag, e w pewnych edycjach rodowiska Eclipse wze <resources>


musi zosta zdefiniowany pod specyfikacj xmlns. Wydaje si, e nie ma znaczenia,
na co ta specyfikacja wskazuje; wystarczy, e istnieje. Ponisze dwie wariacje tej specyfikacji
powinny dziaa bez zarzutu:
<resources xmlns="http://schemas.android.com/apk/res/android" >

lub
<resources xmlns="domylna przestrze nazw" >

Po utworzeniu lub zaktualizowaniu takiego pliku narzdzie ADT automatycznie utworzy lub
zaktualizuje klas Java, umieszczon w gwnym pakiecie aplikacji nazwanym R.java, o unikalne
identyfikatory dwch widocznych na listingu cigw znakw.
Zwrmy uwag na lokalizacj pliku R.java w poniszym przykadzie. Utworzylimy wysokopoziomow struktur katalogw dla projektu nazwanego MyProject:
\MyProject
\src
\com\mycompany\android\my-root-package
\com\mycompany\android\my-root-package\another-package
\gen
\com\mycompany\android\my-root-package\R.java
\assets
\res
\AndroidManifest.xml
...itd.

Bez wzgldu na liczb plikw zasobw istnieje tylko jeden plik R.java.

Plik R.java zaktualizowany o zasoby z listingu 3.1 zostaby wzbogacony o wpisy widoczne na listingu 3.2:

Rozdzia 3 Korzystanie z zasobw

93

Listing 3.2. Przykadowa zawarto pliku R.java


package com.mycompany.android.my-root-package;
public final class R {
...inne wpisy w zalenoci od projektu i aplikacji
public static final class string
{
...inne wpisy w zalenoci od projektu i aplikacji
public static final int hello=0x7f040000;
public static final int app_name=0x7f040001;
...inne wpisy w zalenoci od projektu i aplikacji
}
...inne wpisy w zalenoci od projektu i aplikacji
}

Przede wszystkim naley zwrci uwag, w jaki sposb zdefiniowano szczytow klas gwnego
pakietu w pliku R.java: public static final class R. W tej zewntrznej klasie R Android
definiuje klas wewntrzn, dokadniej static final class string. Klasa ta suy plikowi
R.java jako przestrze nazw do przechowywania identyfikatorw zasobw typu string.
Dwie klasy static final ints, okrelone nazwami zmiennych hello oraz app_name, s identyfikatorami zasobw reprezentujcymi odpowiednie zasoby typu string. Mona stosowa
te identyfikatory w dowolnym miejscu kodu rdowego za pomoc nastpujcej struktury:
R.string.hello

Naley zwrci uwag, e te wygenerowane identyfikatory wskazuj typ danych int, a nie
string. Wikszo metod korzystajcych z cigw znakw uznaje take identyfikatory zasobw
za dane wejciowe. W razie koniecznoci Android przeksztaci dane int w dane typu string.
Jest jedynie kwesti ustalonej konwencji, e wikszo przykadowych aplikacji zestawu Android
SDK definiuje cigi znakw w jednym pliku strings.xml. Android radzi sobie z dowoln liczb
takich plikw, pod warunkiem e ich struktura wyglda tak, jak przedstawiono na listingu 3.1,
oraz e znajduj si w podkatalogu /res/values.
Mona atwo przeledzi struktur takiego pliku. Obecny jest gwny wze <resources>, pod
ktrym umieszczane s podrzdne elementy <string>. Kady element <string> lub wze
posiada waciwo name, ktra staje si atrybutem id w pliku R.java.
eby si przekona, co si dzieje z plikami zasobw typu string w tym podkatalogu, mona
w nim umieci plik zawierajcy kod pokazany poniej i nazwa go strings1.xml (listing 3.3):
Listing 3.3. Przykad dodatkowego pliku strings1.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello1">Witaj 1</string>
<string name="app_name1">Witaj, nazwo aplikacji 1</string>
</resources>

W trakcie kompilacji narzdzie ADT zweryfikuje unikalno tych identyfikatorw i umieci je


jako dwie dodatkowe stae w pliku R.java: R.string.hello1 oraz R.string.app_name1.

94

Android 3. Tworzenie aplikacji

Zasoby typu layout


W Androidzie wygld ekranu czsto jest wczytywany z pliku XML w formie zasobu. Pliki te s
nazywane zasobami typu layout (ang. layout ukad graficzny). Zasoby typu layout s kluczowymi elementami programowania interfejsu uytkownika w aplikacji tworzonej dla Androida.
Spjrzmy na fragment kodu z przykadowej aktywnoci Androida, widoczny na listingu 3.4:
Listing 3.4. Stosowanie pliku ukadu graficznego
public class HelloWorldActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView tv = (TextView)this.findViewById(R.id.text1);
tv.setText("Wpisz tu jaki tekst");
}
...
}

Wiersz setContentView(R.layout.main) wskazuje na istnienie statycznej klasy R.layout,


w ktrej znajduje si staa main (typ integer) odnoszca si do widoku zdefiniowanego
przez plik XML stanowicy zasb ukadu graficznego. Plik ten nosi nazw main.xml. Musi
on zosta umieszczony w podkatalogu zasobw layout. Innymi sowy, powysza instrukcja
wymaga od programisty utworzenia pliku /res/layout/main.xml i umieszczenia w nim niezbdnych definicji dotyczcych ukadu graficznego. Zawarto pliku main.xml moe wyglda
tak jak na listingu 3.5.
Listing 3.5. Przykadowy plik ukadu graficznego main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<Button android:id="@+id/b1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@+string/hello"
/>
</LinearLayout>

Plik ukadu graficznego zaprezentowany na listingu 3.5 definiuje gwny wze, nazwany
LinearLayout, w ktrym umieszczony jest element TextView, a po nim Button. Wze ten
rozmieszcza elementy podrzdne w pionie lub w poziomie w tym przypadku w pionie.

Rozdzia 3 Korzystanie z zasobw

95

Dla kadego ekranu (lub aktywnoci) trzeba definiowa oddzielne pliki ukadu graficznego.
Gwoli cisoci, kady ukad graficzny wymaga oddzielnego pliku. W przypadku tworzenia
dwch ekranw prawdopodobnie potrzebne bd dwa pliki ukadu graficznego, na przykad
/res/layout/screen1_layout.xml oraz /res/layout/screen2_layout.xml.
Kady plik umieszczony w podkatalogu /res/layout/ generuje unikatow sta na podstawie
nazwy tego pliku (rozszerzenie zostaje pominite). W przypadku zasobw typu layout
istotna jest liczba plikw, w przypadku zasobw typu string wana jest liczba
stanowicych zasoby poszczeglnych cigw znakw, znajdujcych si
wewntrz plikw.

Jeeli na przykad w podkatalogu /res/layout/ zostay utworzone dwa pliki o nazwach file1.xml
oraz file2.xml, w pliku R.java pojawi si nastpujce wpisy (listing 3.6):
Listing 3.6. Kilka staych dla rnych plikw ukadu graficznego
public static final class layout {
...jakie inne pliki
public static final int file1=0x7f030000;
public static final int file2=0x7f030001;
}

Zdefiniowane w tych plikach widoki, na przykad TextView (listing 3.5), dostpne s w kodzie
Java poprzez wygenerowane w pliku R.java identyfikatory ich zasobw:
TextView tv = (TextView)this.findViewById(R.id.text1);
tv.setText("Wpisz tu jaki tekst");

W tym przykadzie widok TextView lokalizowany jest za pomoc metody findViewById klasy
Activity. Staa R.id.text1 nawizuje do identyfikatora zdefiniowanego dla widoku TextView.
Identyfikator ten w pliku ukadu graficznego wyglda nastpujco:
<TextView android:id="@+id/text1"
...
</TextView>

Warto atrybutu id wskazuje na to, e staa text1 zostanie uyta do jednoznacznego rozpoznawania tego widoku wrd innych widokw obsugiwanych przez t aktywno. Znak
+ w wyraeniu @+id/text1 oznacza, e identyfikator text1 zostanie utworzony, w przypadku
gdy jeszcze nie istnieje. Skadnia identyfikatora zasobu jest bardziej skomplikowana. Zajmiemy
si ni w nastpnym punkcie.

Skadnia odniesienia do zasobu


Bez wzgldu na rodzaj (dotychczas omwilimy zasoby typu string oraz layout) wszystkie zasoby s identyfikowane (s tworzone do nich odniesienia) poprzez atrybut id kodu Java.
Skadnia stosowana do wizania tego atrybutu z zasobem pliku XML jest okrelana mianem
skadni odniesienia do zasobu. Formalna struktura skadni wspomnianego wyej identyfikatora @+id/text1 wyglda nastpujco:
@[package:]type/name

96

Android 3. Tworzenie aplikacji

Skadnik type odnosi si do jednej z przestrzeni nazw okrelonych dla zasobw, dostpnych
w pliku R.java. Wrd nich znajduj si takie jak:
R.drawable,
R.id,
R.layout,
R.string,
R.attr,
R.plural,
R.array.
Odpowiadajcymi im typami w skadni odniesienia do zasobw XML s odpowiednio:
drawable,
id,
layout,
string,
attr,
plurals,
string-array.
Element name w skadni @[package:]type/name to nazwa nadawana zasobowi (na przykad
text1 na listingu 3.5); jest ona reprezentowana rwnie jako staa int w pliku R.java.
Jeeli nie zostanie zdefiniowany aden pakiet w skadni @[package:]type/name, to para
zostanie przetworzona na podstawie lokalnych zasobw oraz lokalnego pakietu
R.java aplikacji.

type/name

Jeeli wpiszemy android:type/name, identyfikator odniesienia zostanie utworzony przy uyciu


pakietu android, a dokadnie pliku android.R.java. W celu zlokalizowania odpowiedniego pliku
R.java przetwarzajcego odniesienie moemy w miejscu skadnika package uy nazwy dowolnego pakietu Java. Znajc te informacje, przyjrzyjmy si przykadom. W trakcie przegldania listingu 3.7 zwrmy uwag, e znajdujcy si po lewej stronie czon identyfikatora (android:id)
nie jest czci skadni. Stanowi on jedynie sposb przydzielania identyfikatora do kontrolki
takiej jak TextView.
Listing 3.7. Analiza skadni odniesie do zasobw
<TextView android:id="text">

// Bd kompilacji, gdy identyfikator nie przyjmie nieprzetworzonych cigw znakw


// tekstowych.
<TextView android:id="@text">

// Niewaciwa skadnia. W czonie @text brakuje nazwy typu.


// Skadnia powinna wyglda nastpujco: @id/text/ @+id/text lub @string/string1.
// Zostanie wywietlony bd No Resource type specified (nie okrelono typu zasobu).
<TextView android:id="@id/text">

// Bd: nie zosta odnaleziony zasb odpowiadajcy identyfikatorowi text,


// chyba e przedtem text zosta zdefiniowany jako identyfikator.

Rozdzia 3 Korzystanie z zasobw

97

<TextView android:id="@android:id/text">

// Bd: zasb nie jest publiczny.


// Wskazuje na to, e nie ma takiego identyfikatora w obiekcie android.R.id.
// Oczywicie byoby to poprawne, gdyby to plik R.java pakietu android definiowa
// identyfikator z t nazw.
<TextView android:id="@+id/text">

//Sukces: zostaje utworzony identyfikator o nazwie text w lokalnym pakiecie R.java.

W skadni @+id/text symbol + posiada specjalne znaczenie. System zostaje w ten sposb poinformowany, e identyfikator text moe jeszcze nie istnie w takim przypadku naley go
utworzy oraz nada nazw text.

Definiowanie wasnych identyfikatorw zasobw


do pniejszego uytku
Dwoma gwnymi mechanizmami przydzielania atrybutu id s wygenerowanie nowego identyfikatora lub wykorzystanie ju utworzonego przez pakiet Android. Moliwe jest jednak
rwnie wasnorczne okrelanie identyfikatorw i pniejsze ich wykorzystanie we wasnych
pakietach.
Omwiony uprzednio wiersz <TextView android:id="@+id/text"> wskazuje na to, e atrybut
id nazwany text bdzie uywany, jeeli jest ju utworzony. Jeeli ten atrybut nie istnieje,
zostanie utworzony. Zatem kiedy atrybut id, taki jak wspomniany text, ktry ju istnieje
w pliku R.java, moe zosta ponownie wykorzystany?
Moliwe, e kto chciaby wstawi tak sta jak R.id.text do pliku R.java, lecz plik ten nie jest
modyfikowalny. Nawet gdyby istniaa taka moliwo, jest on odtwarzany za kadym razem,
gdy co zostaje zmienione, dodane lub usunite w podkatalogu /res/*.
Rozwizaniem jest uycie znacznika zasobw item do zdefiniowania atrybutu id bez doczania
konkretnego zasobu. Na listingu 3.8 zosta pokazany przykad:
Listing 3.8. Predefiniowanie identyfikatora
<resources>
<item type="id" name="text"/>
</resources>

Element type dotyczy rodzaju zasobu w tym przypadku atrybutu id. Kiedy ten atrybut
znajduje si na miejscu, powinna dziaa definicja widoku, widoczna na listingu 3.9:
Listing 3.9. Wykorzystywanie predefiniowanego identyfikatora
<TextView android:id="@id/text">
...
</TextView>

98

Android 3. Tworzenie aplikacji

Skompilowane oraz nieskompilowane zasoby Androida


Android obsuguje zasoby gwnie za pomoc dwch kategorii plikw: plikw XML oraz plikw
nieskompresowanych (na przykad obrazy, pliki audio i wideo). Pokazalimy wczeniej, e nawet
w przypadku plikw XML zasoby s czasami definiowane jako wartoci wewntrz tych plikw
(na przykad cigi znakw), a czasami cay plik XML moe by zasobem (przytoczy mona
przykad zasobu typu layout).
Pliki XML mona podzieli na kolejne dwie kategorie: cz z nich zostaje skompilowana do
formatu binarnego, inne natomiast zostaj skopiowane na urzdzenie w niezmienionej postaci.
Przykady, z ktrymi mielimy do czynienia pliki XML zawierajce zasoby typu string oraz
pliki XML zawierajce zasoby typu layout zostaj skompilowane do formatu binarnego
przed doczeniem do pakietu instalacyjnego. Pliki te posiadaj predefiniowane formaty,
zgodnie z ktrymi wzy s przeksztacane w identyfikatory.
Mona take wskaza, e niektre pliki XML nie bd posiaday cile ustalonego formatu
struktury; nie bd one interpretowane ani przeksztacane w identyfikatory zasobw. Moe
jednak cigle istnie potrzeba skompilowania ich do formatu binarnego, co jest rwnoznaczne
z uzyskaniem komfortu lokalizacji. W celu uzyskania tego efektu mona umieci te pliki
w podkatalogu /res/xml/, co spowoduje ich skompilowanie do formatu binarnego. W takim
przypadku do odczytywania wzw XML naley uywa czytnikw XML Androida.
Jeeli jednak pliki (w tym pliki XML) zostan umieszczone w katalogu /res/raw/, nie zostan
przeksztacone do formatu binarnego. Konieczne s w tym przypadku jawne interfejsy API oparte
na technologii przesyania strumieniowego w celu obsuenia odczytu tych plikw. Do tej
kategorii nale pliki audio i wideo.
Warto zauway, e dziki temu, i katalog raw jest podkatalogiem katalogu /res/*, nawet
te nieskompilowane pliki audio i wideo mog korzysta z zalet lokalizacji w taki sam sposb
jak inne rodzaje zasobw.

Jak zostao wspomniane w tabeli 2.1 (rozdzia 2.), pliki zasobw s przechowywane w rnych
podkatalogach, w zalenoci od ich typw. Poniej wypisalimy kilka istotnych podkatalogw
wza res wraz z rodzajami przechowywanych w nich zasobw:
anim skompilowane pliki animacji;
drawable mapy bitowe;
layout definicje widoku bd interfejsu UI;
values tablice, kolory, wymiary, cigi znakw oraz style;
xml skompilowane wasne pliki XML;
raw nieskompilowane nieskompresowane pliki.
Kompilator zasobw w narzdziu AAPT kompiluje wszystkie zasoby poza znajdujcymi si
w katalogu raw i umieszcza je w kocowym pliku .apk. Plik ten zawiera kod i zasoby aplikacji.
Jest powizany z plikiem .jar rodowiska Java (skrt apk rozwija si jako Android Package,
czyli pakiet systemu Android). To wanie plik .apk jest instalowany w urzdzeniu.

Rozdzia 3 Korzystanie z zasobw

99

Chocia analizator skadni zasobw XML umoliwia nazwanie zasobu na przykad


hello-string, pojawi si bd kompilacji pliku R.java. Mona tego unikn, zmieniajc
nazw zasobu na hello_string (mylnik zostaje zastpiony podkrelnikiem).

Rodzaje gwnych zasobw w Androidzie


Po przedstawieniu podstawowych informacji dotyczcych zasobw zajmiemy si wyliczeniem
czci pozostaych zasobw obsugiwanych przez Androida, ich reprezentacji w jzyku XML
oraz sposobem ich wykorzystywania w kodzie Java (mona uywa tego podrozdziau jako
skrconej instrukcji obsugi podczas tworzenia plikw zasobw dla poszczeglnych rodzajw
zasobw). Zacznijmy od szybkiego przejrzenia rodzajw zasobw oraz ich funkcji (tabela 3.1).
Tabela 3.1. Rodzaje zasobw
Typ zasobu

Lokalizacja

Opis

Kolory

/res/values/any-file

Reprezentuje identyfikatory kolorw, wskazujce na kody


kolorw. Identyfikatory tych zasobw s umieszczone
w pliku R.java jako R.color.*. Wzem XML w pliku
jest /resources/color.

Cigi znakw

/res/values/any-file

Reprezentuje zasoby typu string. Dziki tym zasobom


istnieje moliwo korzystania, poza prostymi cigami
znakowymi, z cigw znakw sformatowanych
w rodowisku Java oraz ze znacznikw nieprzetworzonego
jzyka HTML. Identyfikatory tych zasobw s umieszczone
w pliku R.java jako R.string.*. Wzem XML w pliku
jest /resources/string.

Tablice cigw
znakw

/res/values/any-file

Reprezentuje zasoby skadajce si z tablic cigw


znakw. Identyfikatory tych zasobw s zdefiniowane
w pliku R.java jako R.array.*. Wze XML w pliku wyglda
nastpujco: /resources/string-array.

Wielokrotnoci

/res/values/any-file

Reprezentuje zbir kilku cigw znakw, z ktrych


kady jest odpowiedni dla wartoci odpowiadajcej
liczbie na przykad jakich elementw. Chodzi o to,
e w poszczeglnych jzykach sposb zapisania zdania
zaley od liczby elementw, o ktrych mowa w tym
zdaniu zdania mwice o jednym, o kilku, o wielu
albo o ani jednym elemencie brzmi rnie. Identyfikator
zasobu jest widoczny w pliku R.java jako R.plural.*. Wze
w pliku to /resources/plurals.

Wymiary

/res/values/any-file

Reprezentuje wymiary lub rozmiary rnych elementw


oraz widokw w Androidzie. Obsuguje piksele, cale,
milimetry, piksele niezalene od gstoci oraz piksele
niezalene od skali. Identyfikatory tych zasobw s
umieszczone w pliku R.java jako R.dimen.*. Wzem
XML w pliku jest /resources/dimen.

100 Android 3. Tworzenie aplikacji


Tabela 3.1. Rodzaje zasobw cig dalszy
Typ zasobu

Lokalizacja

Opis

Obrazy

/res/drawable/
multiple-files

Reprezentuje zasoby obrazw. Obsugiwanymi typami


s pliki .jpg, .gif, .png itd. Kady obraz znajduje si
w oddzielnym pliku i otrzymuje wasny identyfikator
oparty na nazwie tego pliku. Identyfikatory tych zasobw
s umieszczone w pliku R.java jako R.drawable.*.
Obsugiwane s take tak zwane obrazy rozcigalne,
w ktrych cz obrazu ulega rozcigniciu, podczas
gdy pozostae jego fragmenty nie ulegaj zmianie.
Taki rozcigalny obraz jest znany rwnie jako plik
9-patch (.9.png).

Kolorowe
obiekty
rysowane

/res/values/any-file

Reprezentuje prostokty kolorw, ktre mog by


uywane jako to widokw lub inne zwyke elementy
rysowane (ang. drawable), takie jak mapy bitowe.
Mona stosowa ten rodzaj zasobu, zamiast wybiera
na to jednokolorow map bitow. W jzyku Java
odpowiednikiem jest utworzenie kolorowego prostokta
i skonfigurowanie go jako ta widoku. Suy do tego
znacznik wartoci <drawable>. Identyfikatory tych
zasobw s umieszczone w pliku R.java jako R.drawable.*.

take
/res/drawable/
multiple-files

Wzem w pliku XML jest /resources/drawable. Android


obsuguje take zaokrglone prostokty oraz prostokty
o wypenieniu w formie gradientu kolorw poprzez pliki
XML umieszczone w podkatalogu /res/drawable.
Pliki takie posiadaj gwny znacznik XML <shape>.
Te zasoby rwnie s zamieszczone w pliku R.java
jako R.drawable.*. Nazwa kadego pliku w tym przypadku
jest tumaczona na unikatowy identyfikator.
Wasne pliki
XML

/res/xml/*.xml

Android dopuszcza wasne pliki XML jako zasoby. Pliki


te s przetwarzane przez kompilator AAPT. Identyfikatory
tych zasobw s dostpne w pliku R.java jako R.xml.*.

Wasne,
nieskompresow
ane zasoby

/res/raw/*.*

Android dopuszcza w tym katalogu posiadanie wasnych,


nieskompilowanych plikw binarnych lub tekstowych.
Kady plik otrzymuje specyficzny identyfikator zasobu.
Te pliki zasobw s eksponowane w pliku R.java jako
R.raw.*.

Wasne,
nieskompresow
ane pliki
dodatkowe

/assets/*.*/*.*

Android dopuszcza posiadanie wasnych plikw


we wasnych podkatalogach, mieszczcych si
w podkatalogu /assets. Nie s to faktyczne zasoby, lecz
nieskompresowane pliki. W przeciwiestwie do
pozostaych podkatalogw wza /res istnieje moliwo
tworzenia tu wasnego drzewa katalogowego. Pliki
te nie generuj identyfikatorw zasobw. Trzeba poda
relatywn nazw cieki, poczwszy od podkatalogu
/assets bez uwzgldniania jego nazwy w ciece.

Rozdzia 3 Korzystanie z zasobw

101

Na nastpnych stronach kady z wymienionych w powyszej tabeli zasobw zostanie dokadniej


omwiony oraz zaprezentowany we fragmentach kodu XML oraz Java.
Jeli przyjrze si sposobowi tworzenia identyfikatorw zasobw, to wyglda na to
chocia nigdzie oficjalnie tego nie napisano e s generowane na podstawie nazwy
pliku, pod warunkiem e te pliki XML znajduj si w ktrymkolwiek miejscu podkatalogu
res/values. Jeeli tam si znajduj, sprawdzana jest jedynie zawarto pliku, by dowiedzie
si, czy nadaje si do utworzenia identyfikatorw.

Tablice cigw znakw


Istnieje moliwo zdefiniowania cigw znakw w postaci tablicy jako zasb w kadym pliku
umieszczonym w podkatalogu /res/values. Wykorzystywany jest w tym celu wze XML nazwany string-array. Jest to wze potomny, ktrego rodzicem jest resources, podobnie jak
ma to miejsce w przypadku wza string. Listing 3.10 prezentuje przykad definiowania tablicy
w pliku zasobw.
Listing 3.10. Definiowanie tablicy cigw znakw
<resources .>
Inne zasoby
<string-array name="test_array">
<item>raz</item>
<item>dwa</item>
<item>trzy</item>
</string-array>
Inne zasoby
</resources>

Po zdefiniowaniu takiego zasobu tablicy cigw znakw moemy pobra t tablic w kodzie
Java, co zostao pokazane na listingu 3.11.
Listing 3.11. Odczytywanie tablicy cigw znakw w kodzie Java
//Uzyskuje dostp do obiektu zasobw z poziomu aktywnoci
Resources res = your-activity.getResources();
String strings[] = res.getStringArray(R.array.test_array);

//Wywietla cigi znakw


for (String s: strings)
{
Log.d("example", s);
}

Wielokrotnoci
Zasb plurals skada si ze zbioru cigw znakw. Te cigi znakw stanowi rnorodne
sposoby numerycznego okrelenia liczby jakich elementw, na przykad jajek w gniedzie.
Rozwamy poniszy przykad:
Jest 1 jajko.

102 Android 3. Tworzenie aplikacji


S 2 jajka.
Jest 0 jajek.
Jest 100 jajek.

Zwrmy uwag, e zdania s identyczne dla liczb 0 i 100, jednak wygldaj inaczej w przypadku liczb 1 i 2. Taka odmienno zapisu zda moe zosta odwzorowana za pomoc zasobu
plurals. Na listingu 3.12 widzimy, w jaki sposb mona wewntrz pliku zasobu zaprezentowa
te trzy odmiany zdania na podstawie liczby elementw.
Listing 3.12. Definiowanie wielokrotnoci w pliku zasobw
<resources>
<plurals name="eggs_in_a_nest_text">
<item quantity="one">Jest 1 jajko.</item>
<item quantity="few1">S %d jajka.</item>
<item quantity="other">Jest %d jajek.</item>
</plurals>
</resources>

Zauwamy, w jaki sposb te trzy odmiany zostay zdefiniowane jako pi elementw jednej
wielokrotnoci. Teraz moemy wykorzysta pokazany na listingu 3.13 kod Java do wywietlenia cigu znakw odnoszcego si do liczby jakich elementw, o ktrych mowa w zdaniu. Pierwszym parametrem metody getQuantityString() jest identyfikator zasobu wielokrotnoci. Za pomoc drugiego parametru wybieramy potrzebny cig znakw. Jeeli
warto liczby elementw wynosi 1, 2, 3 lub 4, nie modyfikujemy cigu znakw w aden
sposb. Jeeli ta warto bdzie inna, naley wprowadzi trzeci parametr, ktrego warto
bdzie zastpowaa zmienn %d. Za kadym razem, gdy bdziemy chcieli formatowa cigi
znakw w zasobie wielokrotnoci, wymagane bd przynajmniej te trzy parametry.
Listing 3.13. Wywietlanie cigw znakw zawartych w zasobie wielokrotnoci
Resources
String s1
String s2
String s3
String s4

res = your-activity.getResources();
= res.getQuantityString(R.plurals.eggs_in_a_nest_text,
= res.getQuantityString(R.plurals.eggs_in_a_nest_text,
= res.getQuantityString(R.plurals.eggs_in_a_nest_text,
= res.getQuantityString(R.plurals.eggs_in_a_nest_text,

0,0);
1,1);
2,2);
10,10);

Dziki temu fragmentowi kodu podanie dowolnej wartoci jako liczby elementw spowoduje wywietlenie odpowiedniego cigu znakw, stanowicego zdanie we waciwej formie
gramatycznej.
Czy istniej jednak jakie inne zastosowania atrybutu quantity wystpujcego w wle item?
eby zrozumie zastosowanie tych zasobw, zalecamy przejrzenie kodw rdowych plikw
Resources.java i PluralRules.java, ktre s dostpne w kodzie systemu Android. Wrd zamieszczonych na kocu rozdziau odnonikw mona znale odniesienia do wycigw z tych
plikw rdowych.

Warto few odnosi si do gramatyki jzyka polskiego, gdzie oddzieln odmian uzyskuj zdania
zawierajce cyfry 2, 3, 4 oraz wszelkie liczby, ktre kocz si cyframi 2, 3, 4 (za wyjtkiem cyfr 12,
13, 14) przyp. tum.

Rozdzia 3 Korzystanie z zasobw

103

Podsumowujc, w jzyku angielskim mamy do czynienia tylko z wartociami jeden i wiele,


podczas gdy w jzyku polskim (i czeskim) dochodzi jeszcze warto kilka (reprezentujca
przedzia liczb 2 4).

Dodatkowe informacje na temat zasobw typu string


Na pocztku tego rozdziau omwilimy skrtowo zasoby cigw znakw. Przyjrzymy si im
teraz dokadniej, przeanalizujemy take cigi znakw jzyka HTML oraz metody podstawiania
zmiennych w zasobach typu string.
Wikszo struktur interfejsu uytkownika umoliwia korzystanie z zasobw typu
string. Jednak w przeciwiestwie do innych szkieletw interfejsu UI, Android pozwala na
szybkie powizanie identyfikatorw z zasobami cigu znakw poprzez plik R.java. Zatem
stosowanie cigw znakw w formie zasobw uatwia prac.

Rozpoczniemy od definiowania w pliku zasobw XML zwykych cigw znakw, cytowanych


cigw znakw, cigw znakw formatowanych znacznikami HTML oraz podstawialnych cigw znakw (listing 3.14).
Listing 3.14. Skadnia jzyka XML stosowana do definiowania zasobw typu string
<resources>
<string name="simple_string">Prosty cig znakw</string>
<string name="quoted_string">"cytowany cig znakw xyz"</string>
<string name="double_quoted_string">\"cudzysw\"</string>
<string name="java_format_string">
Witaj %2$s formatowanie java. %1$s ponownie
</string>
<string name="tagged_string">
Witaj <b><i>ukony Androidzie</i></b>, jeste pogrubiony.
</string>
</resources>

Plik XML zasobw typu


pliku nie ma znaczenia.

string

musi zosta umieszczony w podkatalogu /res/values. Nazwa

Naley zauway, e cytowane cigi znakw musz zosta wstawione pomidzy znaki cytowania
lub zacytowane w alternatywny sposb. Definicje typu string pozwalaj take na stosowanie
standardowych sekwencji formatowania w jzyku Java.
Android dopuszcza rwnie stosowanie wewntrz wza XML <string> takich elementw, jak
<b> czy <i>, oraz innych prostych znacznikw formatowania tekstu w jzyku HTML. Mona
utworzy taki zoony cig znakw do sformatowania tekstu przed jego wstawieniem do
widoku tekstu.
Kada z tych metod zostaa zaprezentowana na listingu 3.15.
Listing 3.15. Stosowanie zasobw typu string w kodzie Java
//Odczytuje prosty cig znakw i wstawia go do widoku tekstu
String simpleString = activity.getString(R.string.simple_string);
textView.setText(simpleString);

104 Android 3. Tworzenie aplikacji


//Odczytuje cytowany cig znakw i wstawia go do widoku tekstu
String quotedString = activity.getString(R.string.quoted_string);
textView.setText(quotedString);

//Odczytuje cig znakw w cudzysowie i wstawia go do widoku tekstu


String doubleQuotedString = activity.getString(R.string.double_quoted_string);
textView.setText(doubleQuotedString);

//Odczytuje cig znakw sformatowany w jzyku Java


String javaFormatString = activity.getString(R.string.java_format_string);

//Konwertuje sformatowany cig znakw poprzez przeniesienie argumentw


String substitutedString = String.format(javaFormatString, "Hello" , "Android");

//Umieszcza dane wyjciowe w widoku tekstu


textView.setText(substitutedString);

// Odczytuje z zasobu cig znakw sformatowany w jzyku HTML i umieszcza go w widoku


// tekstu
String htmlTaggedString = activity.getString(R.string.tagged_string);

// Konwertuje go do postaci cigu tekstowego nadajcego si do umieszczenia w widoku


// tekstu
// Klasa android.text.Html umoliwia rysowanie cigw znakw w kodzie html
// Jest to klasa cile zdefiniowana przez Android i nie obsuguje wszystkich znacznikw
// html
Spanned textSpan = android.text.Html.fromHtml(htmlTaggedString);

// Umieszcza tekst w widoku tekstu


textView.setText(textSpan);

Po zdefiniowaniu cigw znakw jako zasobu mona umieci go bezporednio w takim widoku, jak TextView, w definicji ukadu graficznego XML tego widoku. Na listingu 3.16 zosta pokazany przykad, w ktrym cig znakw sformatowany za pomoc znacznikw HTML jest
skonfigurowany jako zawarto tekstowa widoku TextView.
Listing 3.16. Stosowanie zasobw typu string w jzyku XML
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAlign="center"
android:text="@string/tagged_string"/>

Widok TextView natychmiast rozpoznaje formatowanie HTML i odpowiednio je przetwarza,


co jest bardzo przydatne, gdy w ten sposb mona szybko formatowa w widokach atrakcyjnie
wygldajcy tekst jako cz ukadu graficznego.

Zasoby typu Color


Podobnie jak w przypadku zasobw typu string, mona stosowa identyfikatory odniesienia
rwnie do wskazywania kolorw w sposb poredni. Dziki temu istnieje moliwo umieszczania kolorw i tworzenia kompozycji graficznych. Po zdefiniowaniu kolorw oraz przypisaniu im identyfikatorw w plikach zasobw staj si one dostpne w kodzie Java poprzez te
identyfikatory. Analogicznie jak w przypadku identyfikatorw zasobw typu string, ktre s
dostpne w przestrzeni nazw <pakiet>.R.string, identyfikatory kolorw znajduj si w przestrzeni nazw <pakiet>.R.color.

Rozdzia 3 Korzystanie z zasobw

105

Android definiuje rwnie podstawowy zestaw kolorw we wasnych plikach zasobw. Ich
identyfikatory dostpne s w przestrzeni nazw android.R.color. Lista staych kolorw dostpnych w przestrzeni nazw android.R.color zostaa umieszczona pod nastpujcym adresem (w jzyku angielskim):
http://developer.android.com/reference/android/R.color.html
Listing 3.17 prezentuje przykady okrelania koloru w pliku zasobw XML.
Listing 3.17. Skadnia jzyka XML do definiowania zasobw typu Color
<resources>
<color name="red">#f00</color>
<color name="blue">#0000ff</color>
<color name="green">#f0f0</color>
<color name="main_back_ground_color">#ffffff00</color>
</resources>

Wpisy przedstawione na listingu 3.17 musz si znajdowa w pliku umieszczonym w podkatalogu /res/values. Nazwa pliku moe by dowolna. Android odczyta wszystkie pliki, a nastpnie je
przetworzy oraz odszuka oddzielne wzy, takie jak <resources> oraz <color>, w celu okrelenia
identyfikatorw.
Na listingu 3.18 zosta pokazany sposb zastosowania zasobu typu color w kodzie Java.
Listing 3.18. Zasoby typu color w kodzie Java
int mainBackGroundColor
= activity.getResources.getColor(R.color.main_back_ground_color);

Z kolei na listingu 3.19 zaprezentowano przykad wykorzystania zasobu typu color w definicji
widoku.
Listing 3.19. Zastosowanie kolorw w definicji widoku
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="@color/ red"
android:text="Przykadowy tekst napisany czerwon czcionk."/>

Zasoby typu dimension


Przykadami wymiarw wykorzystywanych podczas tworzenia ukadu graficznego w jzyku
XML lub Java s piksele, cale oraz punkty. Mona stosowa zasoby wymiarw do tworzenia
stylw oraz rozmieszczania elementw interfejsu uytkownika bez koniecznoci ingerencji
w kod rdowy aplikacji.
Na listingu 3.20 przedstawiono sposb korzystania z zasobw typu
XML.

dimension

w jzyku

106 Android 3. Tworzenie aplikacji


Listing 3.20. Skadnia jzyka XML suca do definiowania zasobw typu dimension
<resources>
<dimen name="mysize_in_pixels">1px</dimen>
<dimen name="mysize_in_dp">5dp</dimen>
<dimen name="medium_size">100sp</dimen>
</resources>

Wymiary mona definiowa w nastpujcych jednostkach:


px piksele,
in cale,
mm milimetry,
pt punkty,
dp piksele niezalene od gstoci na podstawie wartoci 160 dpi (liczba pikseli
na cal) ekranu (wymiary zostaj dopasowane do upakowania pikseli na ekranie),
sp piksele niezalene od skali (wymiary umoliwiajce uytkownikowi
powikszanie/pomniejszanie; szczeglnie przydatne w przypadku czcionek).
W celu uzyskania wymiaru w jzyku Java naley uzyska dostp do wystpienia obiektu klasy
Resources. Osigamy to poprzez wywoanie funkcji getResources w obiekcie activity (listing
3.21). Po otrzymaniu obiektu klasy Resources mona go uy do wyszukania zasobu typu
dimension, korzystajc z identyfikatora tego zasobu (ponownie listing 3.21).
Listing 3.21. Stosowanie zasobw typu dimension w kodzie Java
float dimen = activity.getResources().getDimension(R.dimen.mysize_in_pixels);

W metodzie tej stosowana jest pena nazwa Dimension, podczas gdy w przestrzeni nazw
pliku R.java uywana jest skrcona forma dimen.

Podobnie jak w jzyku Java, wobec odniesienia do zasobu w rodowisku XML stosuje si nazw
dimen zamiast penej nazwy dimension (listing 3.22).
Listing 3.22. Uywanie zasobw typu dimension w kodzie XML
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_size"/>

Zasoby typu image


Android tworzy identyfikatory zasobw dla plikw obrazw umieszczonych w podkatalogu
/res/drawable. Obsugiwanymi rozszerzeniami plikw s .gif, .jpg oraz .png. Identyfikator kadego umieszczonego w tym podkatalogu pliku obrazu jest tworzony na podstawie jego nazwy. Jeeli na przykad plik nosi nazw sample_image.jpg, identyfikatorem tego zasobu bdzie
R.drawable.sample_image.

Rozdzia 3 Korzystanie z zasobw

107

Jeeli dwa pliki bd miay takie same nazwy, system wywietli komunikat o bdzie. Poza tym
podkatalogi umieszczone w wle /res/drawable bd ignorowane. aden plik umieszczony
w takim podkatalogu nie bdzie odczytywany.

W definicjach ukadu graficznego pisanych w kodzie XML mona tworzy odniesienia do


obrazw znajdujcych si w podkatalogu /res/drawable, jak zostao to zaprezentowane na listingu 3.23.
Listing 3.23. Stosowanie zasobw obrazw w jzyku XML
<Button
android:id="@+id/button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Dzwo"
android:background="@drawable/sample_image"
/>

Mona take uzyska dostp do obrazu programowo za pomoc kodu Java i okreli go
jako obiekt interfejsu uytkownika, na przykad jako przycisk (listing 3.24).
Listing 3.24. Uywanie zasobw typu image w rodowisku Java
// Wywouje funkcj getDrawable, eby pobraa obraz
BitmapDrawable d = activity.getResources().getDrawable(R.drawable.sample_image);

// Mona teraz uy obiektu rysowanego do ustawienia ta


button.setBackgroundDrawable(d);

// lub mona wyznaczy to bezporednio poprzez identyfikator zasobu


button.setBackgroundResource(R.drawable.sample_image);

Przedstawione powyej techniki ustawiania ta odwouj si wstecz a do nadrzdnej


klasy View. W wyniku tego wikszo elementw interfejsu uytkownika bdzie korzystaa
z tego ta.

Android obsuguje rwnie specjalny format obrazu, zwany obrazem rozcigalnym. Jest to
rodzaj obrazu .png, w ktrym mona definiowa fragmenty obrazu jako statyczne lub rozcigalne.
Do okrelania tych rejonw suy narzdzie Draw9-patch, znajdujce si w pakiecie Android
SDK (wicej informacji na jego temat w jzyku angielskim mona znale na stronie
http://developer.android.com/guide/developing/tools/draw9patch.html).
Po przygotowaniu obrazu .png moe by on uywany tak samo jak kady inny typ obrazu. Ten
szczeglny typ przydaje si jako to dla przyciskw, ktre ulegaj rozcigniciu w celu dopasowania do tekstu.

108 Android 3. Tworzenie aplikacji

Zasoby typu color-drawable


Rysunek w Androidzie jest jednym z rodzajw rysowanych zasobw. Drugim obsugiwanym typem zasobu tego rodzaju jest tak zwany kolorowy obiekt rysowany (ang. color-drawable);
w istocie jest to prostokt wypeniony kolorem.
W dokumentacji Androida znajduje si stwierdzenie, e istnieje moliwo rysowania
prostoktw o zaokrglonych naronikach. Nam si jednak nie udao tego dokona.
Zamiast tego przedstawilimy poniej alternatywne rozwizanie tego problemu.
Dokumentacja sugeruje take, e tworzon klas Java jest PaintDrawable, jednak w wyniku
dziaania kodu otrzymujemy nazw klasy ColorDrawable.

W celu zdefiniowania takiego prostokta wypenionego kolorem naley okreli element


rodowiska XML poprzez nazw wza drawable w dowolnym pliku XML znajdujcym si
w podkatalogu /res/values. Na listingu 3.25 wymieniono kilka przykadw.
Listing 3.25. Skadnia jzyka XML suca do definiowania zasobw typu color-drawable
<resources>
<drawable name="red_rectangle">#f00</drawable>
<drawable name="blue_rectangle">#0000ff</drawable>
<drawable name="green_rectangle">#f0f0</drawable>
</resources>

Listingi 3.26 oraz 3.27 przedstawiaj kolejno zastosowanie zasobu typu color-drawable w jzyku
Java oraz jzyku XML.
Listing 3.26. Zastosowanie zasobw typu color-drawable w kodzie Java
// Wczytuje obiekt rysowany
ColorDrawable redDrawable =
(ColorDrawable)
activity.getResources().getDrawable(R.drawable.red_rectangle);

// Ustawia ten obiekt jako to widoku tekstu


textView.setBackgroundDrawable(redDrawable);

Listing 3.27. Korzystanie z zasobw typu color-drawable w kodzie XML


<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAlign="center"
android:background="@drawable/red_rectangle"/>

W celu zaokrglenia rogw obiektu Drawable mona zastosowa pominity w dokumentacji znacznik <shape>. Musi by on jednak umieszczony w oddzielnym pliku, w katalogu
/res/drawable. Sposb uycia znacznika <shape> umieszczonego w pliku /res/drawable/my_
rounded_rectangle.xml zosta zaprezentowany na listingu 3.28.

Rozdzia 3 Korzystanie z zasobw

109

Listing 3.28. Definiowanie prostokta o zaokrglonych rogach


<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#f0600000"/>
<stroke android:width="3dp" color="#ffff8080"/>
<corners android:radius="13dp" />
<padding android:left="10dp" android:top="10dp"
android:right="10dp" android:bottom="10dp" />
</shape>

Mona nastpnie wykorzysta ten zasb jako to, tak jak we wczeniejszym przykadzie z widokiem tekstu, co zostao pokazane na listingu 3.29:
Listing 3.29. Wykorzystanie obiektu Drawable w kodzie Java
// Wczytuje obiekt rysowany
GradientDrawable roundedRectangle =
(GradientDrawable)
activity.getResources().getDrawable(R.drawable.my_rounded_rectangle);

// Ustawia ten obiekt jako to widoku tekstu


textView.setBackgroundDrawable(roundedRectangle);

Nie trzeba obsadza bazowego zwracanego obiektu Drawable obiektem klasy


GradientDrawable; zrobilimy to jednak, eby pokaza przemian znacznika <shape>
w ten wanie element. Jest to istotna informacja, poniewa mona sprawdzi
w dokumentacji interfejsu API Java, jakie znaczniki jzyka XML ta klasa definiuje.
Na koniec warto wspomnie, e obraz mapy bitowej, umieszczony w podkatalogu drawable,
jest przetwarzany przez klas BitmapDrawable. Warto rysowanego zasobu, jak choby
prostokta widocznego na listingu 3.29, jest przetwarzana przez klas ColorDrawable.
Plik XML zawierajcy znacznik <shape> jest przetwarzany przez klas GradientDrawable.

Praca na wasnych plikach zasobw XML


Oprcz dotychczas omawianych zasobw strukturalnych, w Androidzie dopuszczalne jest rwnie korzystanie z wasnych plikw XML jako z zasobw. Dziki temu takie pliki zyskuj pewne
zalety waciwe zasobom. Moliwe jest uzyskanie szybkiego odniesienia do tych plikw w postaci
wygenerowanych identyfikatorw zasobw. Poza tym pojawia si moliwo okrelenia lokalizacji tych plikw. Mona je rwnie skutecznie kompilowa i przechowywa w urzdzeniu.
W taki sposb odczytywane pliki s umieszczone w podkatalogu /res/xml. Na listingu 3.30 zosta
zaprezentowany przykad pliku /res/xml/test.xml:
Listing 3.30. Przykadowy plik XML
<rootelem1>
<subelem1>
Kod aplikacji Witaj, wiecie! z podelementu xml
</subelem1>
</rootelem1>

110 Android 3. Tworzenie aplikacji


Tak jak w przypadku pozostaych plikw zasobw XML Androida, narzdzie AAPT skompiluje
ten plik, zanim umieci go w pakiecie aplikacji. Do sprawdzenia skadni zawartego w nim kodu
potrzebne bdzie wprowadzenie instancji XmlPullParser. Mona wykorzysta instancj implementacji XmlPullParser, stosujc poniszy fragment kodu (przykad z listingu 3.31 dotyczy
kontekstu activity):
Listing 3.31. Odczytywanie pliku XML
Resources res = activity.getResources();
XmlResourceParser xpp = res.getXml(R.xml.test);

Zwracany obiekt XmlResourceParser jest instancj obiektu klasy XmlPullParser dziki


niemu zostaje zaimplementowana rwnie funkcja java.util.AttributeSet. Listing 3.32 przedstawia wikszy wycinek kodu odczytujcego plik test.xml.
Listing 3.32. Zastosowanie wystpienia XmlPullParser
private String getEventsFromAnXMLFile(Activity activity)
throws XmlPullParserException, IOException
{
StringBuffer sb = new StringBuffer();
Resources res = activity.getResources();
XmlResourceParser xpp = res.getXml(R.xml.test);
xpp.next();
int eventType = xpp.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT)
{
if(eventType == XmlPullParser.START_DOCUMENT)
{
sb.append("******Start document");
}
else if(eventType == XmlPullParser.START_TAG)
{
sb.append("\nStart tag "+xpp.getName());
}
else if(eventType == XmlPullParser.END_TAG)
{
sb.append("\nEnd tag "+xpp.getName());
}
else if(eventType == XmlPullParser.TEXT)
{
sb.append("\nText "+xpp.getText());
}
eventType = xpp.next();
}//eof-while
sb.append("\n******End document");
return sb.toString();
}//eof-function

Na listingu 3.32 mona zobaczy, w jaki sposb uzyska obiekt XmlPullParser i wykorzysta go
do nawigacji wrd elementw dokumentu XML oraz w jaki sposb zastosowa dodatkowe
metody tego obiektu do otrzymania szczegowych informacji na temat tych elementw.

Rozdzia 3 Korzystanie z zasobw

111

Jeeli powyszy kod ma zadziaa, naley utworzy wspomniany wczeniej plik XML i wywoa funkcj getEventsFromAnXMLFile z dowolnego elementu menu lub za pomoc kliknicia przyciskiem myszy. Otrzymamy cig znakw, ktry mona nastpnie skopiowa do
dziennika za pomoc metody Log.d.

Praca na nieskompresowanych zasobach


Poza wasnymi plikami XML mona rwnie uywa nieskompresowanych typw plikw.
Zasoby te s umieszczone w podkatalogu /res/raw i stanowi pliki audio, wideo lub tekstowe,
ktre wymagaj zlokalizowania lub utworzenia odniesienia w formie identyfikatorw zasobw.
W przeciwiestwie do zasobw znajdujcych si w podkatalogu /res/xml, pliki te nie s kompilowane, lecz s przenoszone do pakietu aplikacji w niezmienionej postaci, jednak kady z tych
plikw bdzie posiada wygenerowany identyfikator w pliku R.java. W przypadku umieszczenia
pliku tekstowego w podkatalogu /res/raw/test.txt kod potrzebny do jego odczytania bdzie
wyglda tak jak na listingu 3.33.
Listing 3.33. Odczytywanie nieskompresowanego zasobu
String getStringFromRawFile(Activity activity)
throws IOException
{
Resources r = activity.getResources();
InputStream is = r.openRawResource(R.raw.test);
String myText = convertStreamToString(is);
is.close();
return myText;
}
String convertStreamToString(InputStream is)
throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i = is.read();
while (i != -1)
{
baos.write(i);
i = is.read();
}
return baos.toString();
}

Pliki posiadajce tak sam nazw powoduj wygenerowanie bdu kompilacji we


wtyczce ADT. Tak samo jest w przypadku wszystkich identyfikatorw zasobw tworzonych
na podstawie nazw plikw.

Praca z dodatkowymi plikami


Istnieje jeszcze jeden katalog, w ktrym mona przechowywa pliki doczane do pakietu
aplikacji /assets. Znajduje si on na tym samym poziomie co katalog /res, co oznacza, e nie
jest jednym z podkatalogw tego wza. Pliki znajdujce si w tym katalogu nie posiadaj

112 Android 3. Tworzenie aplikacji


identyfikatorw wygenerowanych przez plik R.java. Ich odczytanie wymaga podania cieki
dostpu. Jest to relatywna cieka, rozpoczynajca si od katalogu /assets. Aby uzyska dostp
do tych plikw, korzysta si z klasy AssetManager, co zostao przedstawione na listingu 3.34:
Listing 3.34. Odczytywanie dodatkowego pliku
// Uwaga: w kodzie nie zostay pokazane wyjtki
String getStringFromAssetFile(Activity activity)
{
AssetManager am = activity.getAssets();
InputStream is = am.open("test.txt");
String s = convertStreamToString(is);
is.close();
return s;
}

Przegld struktury katalogw mieszczcych zasoby


Jako podsumowanie na listingu 3.35 umieszczamy ogln struktur katalogow zasobw:
Listing 3.35. Katalogi zasobw
/res/values/strings.xml
/colors.xml
/dimens.xml
/attrs.xml
/styles.xml
/drawable/*.png
/*.jpg
/*.gif
/*.9.png
/anim/*.xml
/layout/*.xml
/raw/*.*
/xml/*.xml
/assets/*.*/*.*

Jedynie katalog /assets moe posiada wasne drzewo podkatalogw, gdy nie jest
podkatalogiem wza /res. aden inny podkatalog nie moe mie plikw na niszym
poziomie struktury. Jest to spowodowane sposobem, w jaki plik R.java generuje
identyfikatory dla plikw.

Zasoby a zmiany konfiguracji


Zasoby okazuj si bardzo pomocne w dostosowywaniu waciwoci aplikacji w zalenoci
od lokalizacji uytkownika. Mona na przykad zaoy, e w zalenoci od jzyka, ktrym
posuguje si uytkownik (okrelanego jako region wiata), bdzie si zmienia zawarto
cigu znakw. Zasoby systemu Android rozwijaj t ide dla wszystkich moliwych opcji
konfiguracyjnych urzdzenia, nie tylko tych dotyczcych jzyka. Innym przykadem zmiany

Rozdzia 3 Korzystanie z zasobw

113

konfiguracji jest reakcja aplikacji na obrcenie urzdzenia z pozycji pionowej do poziomej.


Przypomnijmy, e orientacja pionowa jest znana jako tryb portretowy, a orientacja pozioma
nosi nazw trybu krajobrazowego.
System Android pozwala na wybr rnych zestaww ukadw graficznych zwizanych z identyfikatorem tego samego zasobu, w zalenoci od orientacji wywietlacza. Mona tego dokona za pomoc oddzielnych katalogw dla kadej konfiguracji. Przykadowe katalogi zostay
zaprezentowane na listingu 3.36.
Listing 3.36. Katalogi alternatywnych zasobw
\res\layout\main_layout.xml
\res\layout-port\main_layout.xml
\res\layout-land\main_layout.xml

Nawet jeli utworzymy trzy oddzielne pliki ukadw graficznych i kady umiecimy w osobnym katalogu, wszystkie wygeneruj tylko jeden wsplny identyfikator ukadu graficznego
w pliku R.java. Identyfikator ten bdzie wyglda nastpujco:
R.layout.main_layout

Jeeli jednak wczytamy ukad graficzny odpowiadajcy temu identyfikatorowi, uzyskamy ukad
optymalnie odpowiadajcy uoeniu urzdzenia.
W powyszym przykadzie rozszerzenia port i land katalogu nosz nazw kwalifikatorw
konfiguracji. Kwalifikatory te s niezalene od wielkoci liter oraz s oddzielone mylnikiem
(-) od nazwy katalogu zasobu. Zasoby definiowane w takich katalogach zawierajcych kwalifikatory konfiguracji s nazywane alternatywnymi zasobami. Z kolei obiekty znajdujce si w katalogu zasobw pozbawionym kwalifikatorw okrelane s jako zasoby domylne.
Poniej zamieszczono spis dostpnych kwalifikatorw konfiguracji.
mccAAA AAA jest kodem MCC (ang. Mobile Country Code kod kraju w telefonii
mobilnej),
mncAAA AAA jest kodem operatora/sieci,
pl-rPL jzyk i region,
small, normal, large, xlarge rozmiar ekranu,
long, notlong typ ekranu,
port, land tryb portretowy lub krajobrazowy,
car, desk rodzaj dokowania,
night, notnight noc lub dzie,
ldpi, mdpi, hdpi, xhdpi, nodpi gsto ekranu,
notouch, stylus, finger reakcja ekranu na dotyk,
keysexposed, keyssoft, keyshidden rodzaj klawiatury,
nokeys, qwerty, 12key liczba przyciskw,
navexposed, navhidden przyciski nawigacji odsonite lub ukryte,
nonav, dpad, trackball, wheel rodzaj urzdzenia sterujcego,
v3, v4, v7 poziom interfejsu API.

114 Android 3. Tworzenie aplikacji


Za pomoc tych kwalifikatorw moemy utworzy rnorakie katalogi zasobw, ktrych
kilka przykadw moemy zobaczy na listingu 3.37.
Listing 3.37. Dodatkowe katalogi zawierajce alternatywne zasoby
\res\layout-mcc312-mnc222-en-rUS
\res\layout-ldpi
\res\layout-hdpi
\res\layout-car

Moemy sprawdzi aktualny region jzykowy poprzez uruchomienie aplikacji Custom Locale, dostpnej w emulatorze. Oto cieka dostpu: Ekran startowy/lista aplikacji/Custom
Locale.
System Android, korzystajc z danego identyfikatora, stosuje algorytm sucy do wybrania
waciwego zasobu. Aby lepiej zrozumie reguy rzdzce tym procesem, Czytelnik moe
przejrze odpowiednie adresy URL, zamieszczone w podrozdziale Odnoniki, postaramy
si jednak ju teraz wyjani niektre z nich.
Podstawowa zasada polega na tym, e kwalifikatory wystpujce na listingu 3.37 s przetwarzane w kolejnoci ich wystpowania. Spjrzmy na katalogi zamieszczone na listingu 3.38.
Listing 3.38. Rne odmiany plikw ukadw graficznych
\res\layout\main_layout.xml
\res\layout-port\main_layout.xml
\res\layout-en\main_layout.xml

Na listingu 3.38 wida, e plik main_layout.xml jest dostpny w dwch dodatkowych wersjach:
dla jzyka oraz dla orientacji wywietlacza. Sprawdmy teraz, ktr wersj ukadu graficznego
wybierze system, gdy urzdzenie znajduje si w trybie portretowym. Nawet jeeli ustawimy
urzdzenie w orientacji pionowej, Android wybierze ukad graficzny z katalogu layout-en,
poniewa wrd kwalifikatorw konfiguracji wersja dotyczca jzyka ma wyszy priorytet od
wersji zwizanej z uoeniem ekranu. W podrozdziale Odnoniki umiecilimy cza do
zasobw SDK, gdzie mona znale pen list kwalifikatorw konfiguracji oraz kolejno ich
przetwarzania.
Przyjrzyjmy si dokadniej reguom pierwszestwa, przeprowadzajc eksperymenty na kilku
zasobach cigw znakw. Naley zwrci uwag, e cigi znakw opieraj si na pojedynczych
identyfikatorach, podczas gdy zasoby ukadw graficznych s zalene od plikw. W celu przetestowania kolejnoci przetwarzania kwalifikatorw konfiguracji wobec cigw znakw stworzymy
pi identyfikatorw zasobw, ktre mog wystpowa w nastpujcych konfiguracjach: default,
en, en_us, port oraz en_port. Te identyfikatory to:
teststring_all identyfikator ten znajdzie si we wszystkich odmianach katalogu
values, wcznie z domyln.
testport_port ten identyfikator bdzie obecny w konfiguracji domylnej
oraz w odmianie port.
t1_enport zostanie umieszczony w konfiguracji domylnej oraz w odmianach
en i port.

Rozdzia 3 Korzystanie z zasobw

t1_1_en_port

znajdziemy go wycznie w konfiguracji domylnej oraz odmianie

en-port.
t2

115

ten identyfikator bdzie dostpny wycznie w konfiguracji domylnej.

Na listingu 3.39 przedstawiono wszystkie odmiany katalogu values.


Listing 3.39. Odmiany cigw znakw zalene od konfiguracji
// values/strings.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">teststring w katalogu gwnym</string>
<string name="testport_port">testport-port</string>
<string name="t1_enport">t1 w katalogu gwnym</string>
<string name="t1_1_en_port">t1_1 w katalogu gwnym</string>
<string name="t2">t2 w katalogu gwnym</string>
</resources>

// values-en/strings_en.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">teststring-en</string>
<string name="t1_enport">t1_en</string>
<string name="t1_1_en_port">t1_1_en</string>
</resources>

// values-en-rUS/strings_en_us.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">test-en-us</string>
</resources>

// values-port/strings_port.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">test-en-us-port</string>
<string name="testport_port">testport-port</string>
<string name="t1_enport">t1_port</string>
<string name="t1_1_en_port">t1_1_port</string>
</resources>

// values-en-port/strings_en_port.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">test-en-port</string>
<string name="t1_1_en_port">t1_1_en_port</string>
</resources>

Na listingu 3.40 widzimy plik R.java wygenerowany dla tych zasobw.


Listing 3.40. Plik R.java obsugujcy rne odmiany konfiguracji cigw znakw
public static final class string {
public static final int teststring_all=0x7f050000;
public static final int testport_port=0x7f050004;
public static final int t1_enport=0x7f050001;
public static final int t1_1_en_port=0x7f050002;
public static final int t2=0x7f050003;
}

116 Android 3. Tworzenie aplikacji


Od razu wida, e chocia zdefiniowalimy mnstwo cigw znakw, system wygenerowa zaledwie pi identyfikatorw zasobw. Jeeli wczytamy teraz wartoci tych identyfikatorw,
wynikiem bdzie nastpujce zachowanie (testowalimy je w konfiguracji en_US oraz w trybie
portretowym):
teststring_all ten identyfikator znajduje si we wszystkich piciu odmianach
katalogu values. Z tego powodu zostaje wybrana konfiguracja values-en-rUS.
Zgodnie z reguami pierwszestwa zdefiniowany jzyk posiada priorytet ponad
odmianami: domyln, en, port oraz en-port.
testport_port ten identyfikator znajdziemy wycznie w konfiguracji domylnej
oraz odmianie port. Poniewa nie znajduje si w adnym katalogu posiadajcym
kwalifikator en, wychodzi na to, e kwalifikator -port posiada pierwszestwo przed
konfiguracj domyln i zostanie wybrana warto z tej odmiany. Gdyby ten identyfikator
zosta umieszczony w ktrym z katalogw zawierajcych kwalifikator en, to wanie
stamtd pochodziby odpowiedni cig znakw.
t1_enport ten identyfikator jest obecny w trzech konfiguracjach: domylnej, -en
i port. Poniewa w tym samym czasie mamy do czynienia z odmianami en i port,
zostanie wybrana warto z tej pierwszej.
t1_1_en_port ten identyfikator znajdziemy w czterech odmianach: domylnej,
-port, -en oraz en-port. Poniewa jest dostpny w konfiguracji en-port, zostanie
wybrana warto wanie std, a pozostae odmiany zostan zignorowane.
t2 ten identyfikator jest dostpny jedynie w konfiguracji domylnej, wic z niej
zostanie pobrana warto cigu znakw.
Zestaw Android SDK uwzgldnia bardziej zoony algorytm wybierania konfiguracji, warto si
wic z nim zaznajomi. Jednak powyszy przykad powinien da oglne pojcie na jego temat.
Podstaw jest poznanie regu pierwszestwa jednych odmian konfiguracji nad innymi. W nastpnym podrozdziale znajduje si odpowiedni odnonik do informacji o pakiecie SDK.

Odnoniki
W trakcie poznawania tajnikw zasobw Androida mog si przyda ponisze odnoniki;
opisalimy, co mona znale po ich klikniciu.
http://developer.android.com/guide/topics/resources/index.html ten adres URL
stanowi map po dokumentacji dotyczcej zasobw.
http://developer.android.com/guide/topics/resources/available-resources.html
znajdziemy tu opisane rnorodne rodzaje zasobw.
http://developer.android.com/reference/android/content/res/Resources.html
umieszczono tutaj opis rnorodnych metod sucych do odczytywania zasobw.
http://developer.android.com/reference/android/R.html opis zasobw zdefiniowanych
w rdzeniu systemu Android.
http://www.androidbook.com/item/3542 nasze badania dotyczce zasobw
wielokrotnoci, tablic cigw znakw oraz zasobw alternatywnych, jak rwnie
odniesienia do innych materiaw.
ftp://ftp.helion.pl/przyklady/and3ta.zip z tego adresu moemy pobra projekt
rodowiska Eclipse, w ktrym zostao ukazanych wiele koncepcji zawartych w tym
rozdziale. Waciwy plik znajdziesz w katalogu o nazwie ProAndroid3_R03_Zasoby.

Rozdzia 3 Korzystanie z zasobw

117

Podsumowanie
Podsumujmy ten rozdzia poprzez wyliczenie opisanych tematw. Czytelnik mg pozna rodzaje zasobw obsugiwanych przez Androida oraz metody ich tworzenia w plikach XML.
Mona si byo dowiedzie, w jaki sposb s tworzone identyfikatory zasobw oraz jak je
umieci w kodzie Java. Czytelnicy przekonali si take, e tworzenie identyfikatorw zasobw
jest wygodn metod, uatwiajc zarzdzanie zasobami w Androidzie. Poza tym mona byo
zrozumie, w jaki sposb naley pracowa z zasobami nieskompresowanymi oraz dodatkowymi
plikami. Poruszylimy rwnie do oglnie zagadnienie alternatywnych zasobw, zasobw
wielokrotnoci oraz tablic cigw znakw.
Majc tak wiedz, w nastpnym rozdziale mona zaj si dostawcami treci.

118 Android 3. Tworzenie aplikacji

R OZDZIA

4
Dostawcy treci

Koncepcja dostawcw treci w Androidzie oznacza abstrakcyjn warstw uatwiajc usugom dostp do danych. Dziki dostawcom treci dostp do rde
danych jest podobny do takiego jak w przypadku architektury REST. Dobrym
przykadem s strony WWW.
Witryna internetowa przekazuje do przegldarki informacje na temat danych
dostpnych pod okrelonym adresem URL. Podobnie dostawca treci zapewnia
opis danych przekazywanych obsugiwanej aktywnoci. W tym znaczeniu dostawca treci suy jako osona danych. Przykadem rda danych, ktre mona
umieci w dostawcy treci, jest baza danych SQLite.
Skrt REST oznacza REpresentational State Transfer, czyli reprezentacyjny
transfer stanu. Jest to skomplikowana nazwa dla bardzo prostej koncepcji,
z ktr wszyscy (na przykad uytkownicy sieci WWW) s dobrze zaznajomieni.
Kiedy kto wpisuje adres URL w przegldarce i otrzymuje w odpowiedzi
usug sieciow (wywietlenie strony), wykonuje operacj zapytania
wobec tej usugi. Zapytanie to jest oparte wanie na architekturze REST.
Innym przykadem jest wypenienie formularza na stronie WWW.
Przesanie tego formularza do serwera moe spowodowa zmian stanu tego
serwera albo zaktualizowanie jego zawartoci. Operacje te rwnie s
oparte na architekturze REST. Zazwyczaj koncepcji tej jest przeciwstawiane
pojcie usug sieciowych SOAP (ang. Simple Object Access Protocol
protok wywoywania zdalnego dostpu do obiektw). Wicej informacji
na temat architektury REST mona znale na stronie Wikipedii
http://en.wikipedia.org/wiki/Representational_State_Transfer.

Aby odczyta dane zawarte w dostawcy treci lub je w nim zapisa, naley skorzysta
z zestawu identyfikatorw URI, rwnie zgodnych z zaoeniami architektury REST.
eby na przykad odczyta zbir tytuw ksiek znajdujcych si w dostawcy treci,
ktry stanowi opakowanie bazy danych o ksikach, potrzebny byby nastpujcy
identyfikator URI:
content://com.android.book.BookProvider/books

120 Android 3. Tworzenie aplikacji


Aby odczyta dane wybranej ksiki (ksik numer 23), identyfikator URI musiaby wyglda nastpujco:
content://com.android.book.BookProvider/books/23
W tym rozdziale pokaemy, w jaki sposb identyfikatory URI s powizane z podstawowymi
mechanizmami dostpu do bazy danych. Kada aplikacja zainstalowana w urzdzeniu moe korzysta z tych URI, eby uzyska dostp do danych i je modyfikowa. W zwizku z tym dostawcy treci peni istotn rol w procesie wspdzielenia danych pomidzy aplikacjami.
Jednak cilej mwic, obowizki dostawcw treci dotycz w wikszym stopniu mechanizmw
opakowania danych ni zapewniania do nich dostpu. Aby uzyska dostp do rde danych,
potrzebny bdzie rzeczywisty mechanizm dostpu do danych, na przykad baza SQLite lub dostp sieciowy. Zatem abstrakcyjny obiekt dostawcy treci potrzebny jest jedynie w przypadku
wspuytkowania danych na zewntrz lub pomidzy aplikacjami. Przy wewntrznym dostpie
do danych aplikacja moe korzysta z dowolnego, odpowiedniego mechanizmu przechowywania
i dostpu, takiego jak:
Preferencje. Zestawy par klucz warto mogcych przechowywa preferencje danych.
Pliki. Wewntrzne pliki aplikacji, ktre mog by przechowywane w wymiennym
magazynie danych.
SQLite. Baza danych SQLite, do ktrej dostp uzyskuje wycznie pakiet generujcy
dan baz.
Sie. Mechanizm umoliwiajcy odczytywanie lub przechowywanie danych w internecie.
Pomimo duej liczby mechanizmw udostpniania danych obsugiwanych przez system
Android niniejszy rozdzia dotyczy bazy SQLite oraz abstrakcyjnych dostawcw treci,
poniewa stanowi one podstaw technologii wspdzielenia danych. Wystpuje ona
o wiele powszechniej w strukturze Androida ni w innych szkieletach interfejsw
UI. Mechanizm sieciowy zostanie omwiony w rozdziale 11., a mechanizm preferencji
w rozdziale 9.

Analiza wbudowanych dostawcw Androida


Android wyposaono w wiele rodzajw wbudowanych dostawcw treci. Dokumentacja tych
dostawcw jest dostpna w pakiecie android.provider zestawu SDK, a ich list mona znale
pod adresem http://developer.android.com/reference/android/provider/package-summary.html.
Poniej zostaa wymieniona cz z dostawcw treci omwionych na powyszej stronie WWW:
Browser (Przegldarka)
CallLog (Dziennik pocze)
Contacts (Kontakty)
People (Osoby)
Phones (Telefony)
Photos (Zdjcia)
Groups (Grupy)
MediaStore (Dane multimedialne)
Audio (Audio)

Rozdzia 4 Dostawcy treci

121

Albums (Albumy)
Artists (Wykonawcy)
Genres (Gatunki)
Playlists (Listy odtwarzania)
Images (Obrazy)
Thumbnails (Miniatury)
Video (Wideo)
Settings (Ustawienia)
W zalenoci od uywanej wersji Androida lista dostawcw moe si skada z innych
elementw. Zadaniem powyszej listy jest zaprezentowanie dostpnych elementw,
jednak nie naley jej uznawa za bezwzgldny punkt odniesienia.

Bazy danych s elementami najwyszego poziomu, natomiast elementami na niszych poziomach s tabele. Zatem pozycje Browser, CallLog, Contacts, MediaStore oraz Settings s oddzielnymi bazami danych SQLite, zdefiniowanymi jako dostawcy. Bazy te zazwyczaj posiadaj
rozszerzenie .db i s dostpne jedynie z poziomu pakietu implementacyjnego. Kada prba uzyskania dostpu spoza tego pakietu musi nastpi poprzez interfejs dostawcy treci.

Analiza baz danych na emulatorze oraz dostpnych urzdzeniach


Wielu dostawcw treci w Androidzie wykorzystuje bazy danych SQLite (http://www.sqlite.org/),
zatem do badania tych baz mona uy narzdzi dostpnych zarwno w Androidzie, jak i w rodowisku SQLite. Cz tych narzdzi umieszczono w folderze \katalog-instalacyjny-android-sdk\
tools, inne mona znale w katalogu \android-sdk-install-directory\platform-tools.
Jednym z narzdzi dostpnych w urzdzeniu jest zdalna powoka, umoliwiajca uruchomienie
programu bazy SQLite w wierszu polece dla wybranej bazy danych. W dalszej czci podpunktu omwimy metod korzystania z tej aplikacji w celu przeanalizowania wbudowanych baz
danych Androida.
W rozdziale 2. mona znale informacje dotyczce pooenia katalogu narzdzi oraz
przywoywania wiersza polece w rnych systemach operacyjnych. W tym oraz
w wikszoci pozostaych rozdziaw podajemy przykady gwnie dla systemu Windows.
W dalszej czci podrozdziau bdziemy czsto korzysta z narzdzi wiersza polece.
Rozdzia 2. zawiera rwnie instrukcje, w jaki sposb skonfigurowa ciek do katalogu
z narzdziami w rnych systemach operacyjnych, wic Czytelnik nie musi si ni
przejmowa, trzeba jedynie zna nazw pliku wykonywalnego lub wsadowego.

System Android posiada aplikacj wiersza polece znan jako Android Debug Bridge (adb), ktr
mona znale w katalogu jako plik:
platform-tools\adb.exe
Jest to specjalne narzdzie, z ktrym wikszo pozostaych aplikacji musi si poczy, zanim
uzyska dostp do urzdzenia. eby jednak zadziaao, trzeba najpierw uruchomi emulator lub
podczy urzdzenie. Za pomoc poniszego polecenia mona sprawdzi, czy jest uruchomiony
jaki emulator lub urzdzenie:
adb devices

122 Android 3. Tworzenie aplikacji


Jeli emulator nie dziaa, mona go uruchomi za pomoc poniszego polecenia:
emulator.exe @avdname

Argument @avdname jest nazw urzdzenia AVD (w rozdziale 2. napisalimy o koniecznoci


posiadania wirtualnego urzdzenia AVD oraz omwilimy sposb jego utworzenia). Do wywietlenia listy istniejcych urzdze wirtualnych suy polecenie:
android list avd

Na ekranie pojawi si lista dostpnych urzdze AVD. Jeeli w rodowisku Eclipse zaprojektowano i uruchomiono przynajmniej jedn aplikacj, musiao rwnie zosta utworzone przynajmniej jedno urzdzenie AVD. Powysze polecenie spowoduje wywietlenie nazwy przynajmniej tego urzdzenia.
Poniej zaprezentowano dane wywietlane po wpisaniu powyszego polecenia (niektre mog
ulec zmianie w zalenoci od cieki do katalogu tools na przykad i:\android oraz od
wersji Androida):
I:\android\tools>android list avd
Available Android Virtual Devices:
Name: avd
Path: I:\android\tools\..\avds\avd3
Target: Google APIs (Google Inc.)
Based on Android 1.5 (API level 3)
Skin: HVGA
Sdcard: 32M
--------Name: titanium
Path: C:\Documents and Settings\Satya\.android\avd\titanium.avd
Target: Android 1.5 (API level 3)
Skin: HVGA

Jak ju wspomniano, urzdzenia AVD zostay szczegowo omwione w rozdziale 2.


Mona rwnie uruchomi emulator za pomoc wtyczki rodowiska Eclipse. Dzieje si to automatycznie po uruchomieniu programu lub podczas sprawdzania bdw. Po uruchomieniu
emulatora mona jeszcze raz wywoa list podczonych urzdze za pomoc polecenia:
adb devices

Powinny zosta wywietlone informacje podobne do nastpujcej:


List of devices attached
emulator-5554 device

List dostpnych opcji i polece mona wywoa za pomoc poniszego polecenia:


adb help

Pod widocznym poniej adresem zostaa zamieszczona lista wielu opcji rozruchowych narzdzia adb:
http://developer.android.com/guide/developing/tools/adb.html

Rozdzia 4 Dostawcy treci

123

Istnieje moliwo uruchomienia za pomoc aplikacji adb okna powoki w podczonym


urzdzeniu:
adb shell

Jest to powoka ash systemu Unix, pozbawiona jest jednak kilku polece. Dostpne
jest polecenie ls, brakuje natomiast instrukcji find, grep oraz awk.

Dostpny zestaw polece powoki zostaje wywietlony po wpisaniu nastpujcego polecenia


w oknie zachty powoki:
#ls /system/bin

Symbol # jest znakiem zachty powoki. W celu zachowania zwizoci bdziemy go pomija
w nastpnych przykadach. Po wpisaniu powyszego polecenia pojawi si lista polece przedstawionych w tabeli 4.1. (Naley mie na uwadze fakt, e tabela ta jest zaprezentowana wycznie
w celach demonstracyjnych i nie zostay w niej wymienione wszystkie polecenia. W zalenoci
od wersji zestawu Android SDK na licie mog wystpowa rne elementy).
eby zobaczy katalogi i pliki podstawowego poziomu, wystarczy wpisa:
ls -l

Lista baz danych znajduje si w katalogu:


ls /data/data

Tu si znajduje lista wszystkich pakietw zainstalowanych w urzdzeniu. Przyjrzyjmy si na


przykad zawartoci pakietu com.android.providers.contacts:
ls /data/data/com.android.providers.contacts/databases

Zostanie wywietlony plik contacts.db, bdcy baz danych SQLite (plik ten oraz cieka do
niego zale od rodzaju urzdzenia oraz wersji systemu).
Powinnimy wspomnie, e w Androidzie mona tworzy bazy danych podczas pierwszej
prby uzyskania do nich dostpu. Oznacza to, e powyszy plik moe nie by widoczny,
w przypadku gdy aplikacja korzystajca z kontaktw nie zostaa jeszcze uruchomiona.

Gdyby w powoce ash byo dostpne polecenie find, istniaaby moliwo wyszukiwania
wszystkich plikw *.db. Nie mona tego wykona w prosty sposb za pomoc samego polecenia
ls. Najprostszym sposobem jest wpisanie:
ls -R /data/data/*/databases

Dziki temu poleceniu dowiadujemy si, e Android zawiera nastpujce bazy danych (jak
zwykle liczba i rodzaje elementw listy mog si rni w zalenoci od edycji zestawu Android SDK):
alarms.db
contacts.db
downloads.db
internal.db
settings.db
mmssms.db
telephony.db

124 Android 3. Tworzenie aplikacji


Tabela 4.1. Zestaw dostpnych polece powoki
dumpcrash

sh

date

am

hciattach

dd

dumpstate

sdptool

cmp

input

logcat

cat

itr

servicemanager

dmesg

monkey

dbus-daemon

df

pm

debug_tool

getevent

svc

flash_image

getprop

ssltest

installd

hd

debuggerd

dvz

id

dhcpcd

hostapd

ifconfig

hostapd_cli

htclogkernel

insmod

fillup

mountd

ioctl

linker

qemud

kill

logwrapper

radiooptions

ln

telnetd

toolbox

log

iftop

hcid

lsmod

mkdosfs

route

ls

mount

setprop

mkdir

mv

sleep

dumpsys

notify

setconsole

service

netstat

smd

playmp3

printenv

stop

sdutil

reboot

top

rild

ps

start

dalvikvm

renice

umount

dexopt

rm

vmstat

surfaceflinger

rmdir

wipe

app_process

rmmod

watchprops

mediaserver
system_server

sendevent

sync

schedtop

netcfg

ping

chmod

Istnieje moliwo otwarcia bazy danych za pomoc aplikacji sqlite3 w powoce adb, jeli
wpisze si nastpujcy wiersz:
sqlite3 /data/data/com.android.providers.contacts/databases/contacts.db

Rozdzia 4 Dostawcy treci

125

Zamknicie aplikacji nastpuje po wpisaniu polecenia:


sqlite>.exit

Naley zwrci uwag, e znakiem zachty aplikacji adb jest #, natomiast w przypadku sqlite3
jest to sqlite>. Informacje dotyczce rnych polece aplikacji sqlite3 dostpnych wewntrz
powoki adb mona znale pod adresem http://www.sqlite.org/sqlite.html, jednak omwimy tu
kilka waniejszych polece, dziki czemu odwiedziny tej witryny nie bd konieczne. Lista tabel
zostanie wywietlona po wpisaniu:
sqlite> .tables

Polecenie to jest skrtow wersj kodu:


SELECT name FROM sqlite_master
WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%'
UNION ALL
SELECT name FROM sqlite_temp_master
WHERE type IN ('table','view')
ORDER BY 1

Jak mona si domyli, element sqlite_master jest gwn tabel, zarzdzajc pozostaymi
tabelami i widokami bazy danych. Poniszy wiersz wywouje instrukcj create dla tabeli
people, znajdujcej si w pliku contacts.db:
.schema people

Jest to jeden ze sposobw uzyskania nazw kolumn tabeli SQLite. Zostan rwnie wywietlone
typy danych zawartych w kolumnach. Podczas pracy z dostawcami usug typy danych bd
peniy wan funkcj, gdy od nich zale metody dostpu do bazy danych.
Jednak analizowanie wynikw instrukcji create jedynie w celu poznania nazw kolumn oraz
typw danych w nich zawartych jest dosy mudnym zajciem. Na szczcie istnieje inny sposb:
mona wyizolowa plik contacts.db z pakietu, a nastpnie obejrze tabel za pomoc dowolnego
interfejsu GUI (ang. Graphical User Interface graficzny interfejs uytkownika) obsugujcego
baz danych SQLite w wersji 3. Dziki poniszemu wierszowi, wpisanemu w oknie polece systemu operacyjnego, moliwe jest uzyskanie pliku contacts.db:
adb pull /data/data/com.android.providers.contacts/databases/contacts.db
c:/somelocaldir/contacts.db

Podczas zbierania materiaw do ksiki korzystalimy z darmowej aplikacji Sqliteman


(http://sqliteman.com/), cakiem dobrze sprawujcego si interfejsu graficznego dla baz danych
SQLite. Kilkakrotnie przerwa dziaanie, poza tym jednak okaza si uytecznym narzdziem do
przegldania baz danych systemu Android.

Krtki elementarz baz danych SQLite


Przedstawione poniej przykadowe instrukcje SQLite mog pomc w nauce sprawnego posugiwania si bazami danych SQLite:
// Pokazuje nagwki kolumn w oknie narzdzia
sqlite>.headers on

// Zaznacza wszystkie krotki tabeli


select * from table1;

126 Android 3. Tworzenie aplikacji


// Zlicza krotki tabeli
select count(*) from table1;

// Zaznacza okrelon kategori kolumn


select col1, col2 from table1;

// Zaznacza rne wartoci w kolumnie


select distinct col1 from table1;

// Zlicza ilo unikatowych wartoci


select count(col1) from (select distinct col1 from table1);

// Grupuje elementy w okrelonej kolejnoci


select count(*), col1 from table1 group by col1;

// Regularne wewntrzne czenie (ang. inner join)


select * from table1 t1, table2 t2
where t1.col1 = t2.col1;

// Lewe zewntrzne czenie (ang. outer join)


// Pobiera wszystko z t1, nawet jeeli nie ma krotek w t2
select * from table t1 left outer join table2 t2
on t1.col1 = t2.col1
where ....

Architektura dostawcw treci


Czytelnik ju teraz wie, w jaki sposb przeglda zawarto dostawcw treci za pomoc narzdzi
dostpnych w Androidzie oraz odpowiednich interfejsw GUI. Obecnie zajmiemy si analiz
niektrych elementw struktury dostawcw treci oraz ich powizaniami z innymi rodzajami
abstrakcyjnych obiektw umoliwiajcych dostp do danych.
Technologia dostawcw treci ma swoje odpowiedniki w nastpujcych mechanizmach:
stronach WWW,
architekturze REST,
usugach sieciowych,
procedurach skadowanych.
Kady dostawca treci zostaje zarejestrowany w urzdzeniu jako strona WWW poprzez cig
znakw (podobny do nazwy domeny, tu jednak nazywany upowanieniem). Taki niepowtarzalny
cig znakw jest podstaw dla zestawu identyfikatorw URI, udostpnianych przez kadego
dostawc treci. W podobny sposb strona WWW wraz z domen posiada adresy URL, odsyajce do jej dokumentw lub, oglnie, treci.
Rejestracja upowanienia przebiega w pliku AndroidManifest.xml. Poniej zaprezentowano dwa
przykady rejestrowania dostawcw treci w tym pliku:
<provider android:name="SomeProvider"
android:authorities="com.your-company.SomeProvider" />
<provider android:name="NotePadProvider"
android:authorities="com.google.provider.NotePad"
/>

Rozdzia 4 Dostawcy treci

127

Upowanienie peni funkcj nazwy domeny dla danego dostawcy treci. Na podstawie powyszych przykadw rejestracji upowanie dostawcy treci bd honorowa adresy URL
rozpoczynajce si od prefiksu upowanienia:
content://com.your-company.SomeProvider/
content://com.google.provider.NotePad/

Wida wic, e dostawcy treci, na przykad strony WWW, posiadaj podstawow nazw
domeny, zachowujc si jak pocztek adresu URL.
Naley zwrci uwag, e dostawcy treci w Androidzie nie musz posiada penej,
zoonej nazwy upowanienia. Obecnie jest ona zalecana wycznie dla dostawcw
treci wydawanych przez niezalenych producentw. Dlatego wanie czasami niektrzy
dostawcy treci opisywani s jednym sowem, na przykad contacts, w przeciwiestwie
do com.google.android.contacts (w przypadku dostawcy treci od niezalenego
producenta).

Dostawcy treci zapewniaj take adresy URL oparte na architekturze REST, suce do odczytywania lub modyfikowania danych. Na bazie powyszej rejestracji identyfikator URI sucy
do rozpoznawania katalogu lub zbioru notatek bazy danych NotePadProvider bdzie wyglda
nastpujco:
content://com.google.provider.NotePad/Notes

Identyfikator URI dotyczy okrelonej notatki (mianowiciecontent://com.google.provider.NotePad/


Notes/#), gdzie # oznacza atrybut id tej notatki. Poniej wypisano inne przykady identyfikatorw URI akceptowanych przez dostawcw treci:
content://media/internal/images
content://media/external/images
content://contacts/people/
content://contacts/people/23

Zwrmy uwag, e multimedia tych dostawcw (content://media) oraz ich kontakty


(content://contacts) nie posiadaj penej, zoonej struktury. Wynika to z faktu, e dostawcy
ci zostali dostarczeni przez niezalenych producentw i s kontrolowani przez Androida.
Dostawcy treci wykazuj rwnie waciwoci usug sieciowych. Poprzez swoje identyfikatory
URI dostawca treci przedstawia wewntrzne dane w formie usugi. Jednak inaczej ni w przypadku wywoa usug sieciowych opartych na protokole SOAP na kocu adresu URL, wewntrzne dane dostawcy treci nie s typowymi danymi. Bardziej przypominaj zestaw wynikowy
dostpny w interfejsie JDBC. Take tutaj podobiestwa dotycz wycznie koncepcji. Nie chcemy,
aby Czytelnik odnis wraenie, e te dane s tosame z obiektem ResultSet interfejsu JDBC.
Aby wywoanie przebiego pomylnie, wymagana jest znajomo struktury zwracanych wierszy
i kolumn. W punkcie Struktura typw MIME w Androidzie zostan omwione wbudowane
mechanizmy, pozwalajce na okrelenie typu MIME (ang. Multipurpose Internet Mail Extensions uniwersalne rozszerzenia poczty internetowej) danych reprezentowanych przez identyfikatory URI.
Poza podobiestwem do stron WWW, architektury REST oraz usug sieciowych identyfikatory
URI dostawcw treci wykazuj rwnie powinowactwo z nazwami procedur skadowanych
w bazach danych. Procedury te pozwalaj na oparty na usugach dostp do relacyjnych,
podstawowych danych. Identyfikatory URI s podobne do procedur skadowanych, gdy

128 Android 3. Tworzenie aplikacji


ich wywoanie powoduje przekazanie kursora. Jednak obydwa mechanizmy rni si tym, e
w przypadku dostawcy treci dane wejciowe wywoywanej usugi s zazwyczaj zagniedone
bezporednio w identyfikatorze URI.
Powysze porwnanie technologii ma na celu ukazanie szerszego zakresu funkcjonalnoci
dostawcw treci.

Struktura identyfikatorw URI dostawcw treci


Porwnalimy dostawc treci do strony WWW, poniewa reaguje on na przychodzce
identyfikatory URI. Aby zatem odczyta dane umieszczone w dostawcy treci, wystarczy
wywoa jego identyfikator URI. Jednak w przypadku dostawcw treci dane s odczytywane
w formie zbioru krotek i kolumn, reprezentowanych przez obiekt cursor. W takim kontekcie
przeanalizujemy struktur identyfikatora URI, eby si dowiedzie, w jaki sposb odczytywa dane.
Identyfikatory URI w Androidzie przypominaj nieco identyfikatory URI protokou HTTP,
rni si tylko pocztkowym czonem content oraz ogln struktur:
content://*/*/*

lub
content://authority-name/path-segment1/path-segment2/itd...

Poniej zosta ukazany przykadowy identyfikator URI, odnoszcy si do notatki nr 23


w bazie notatek:
content://com.google.provider.NotePad/notes/23

Po czonie content: umieszczono niepowtarzalny identyfikator upowanienia, ktry jest uywany do zlokalizowania dostawcy w rejestrze dostawcw. W powyszym przykadzie czonem stanowicym upowanienie jest com.google.provider.NotePad.
Czon /notes/23 jest sekcj okrelajc ciek, inn dla kadego dostawcy. Czony notes oraz 23
nazywane s segmentami cieki. Zadaniem dostawcy jest okrelenie oraz zinterpretowanie
sekcji oraz segmentw cieki w danym identyfikatorze URI.
Proces projektowania dostawcy treci polega przewanie na deklarowaniu staych w klasie Java
lub interfejsie Java, umieszczonych w tej samej implementacji pakietu, w ktrej znajduje si
dostawca. Co wicej, pierwszy czon sekcji cieki moe wskazywa na zbir obiektw. Na
przykad czon /notes wskazuje zbir lub katalog notatek, a segment /23 precyzuje interesujc
nas notatk.
Po otrzymaniu identyfikatora URI zadaniem dostawcy treci jest odczytanie wierszy wskazywanych przez ten identyfikator. Zadaniem dostawcy jest rwnie zmiana zawartoci takiego identyfikatora za pomoc ktrej z metod zmiany stanu: wstawiania, aktualizowania lub usuwania.

Struktura typw MIME w Androidzie


Tak samo jak w przypadku strony WWW przekazujcej danemu adresowi URL typ MIME
(dziki czemu przegldarka uruchamia waciw aplikacj do przegldania zawartoci strony),
dostawca treci przekazuje typ MIME okrelonemu identyfikatorowi URI. Zapewnia to elastyczno przegldania danych. Znajc typ danych, mona przypisa kilka rnych programw
do jego obsugi. Na przykad umieszczony na dysku plik tekstowy mona otworzy w kilku

Rozdzia 4 Dostawcy treci

129

rodzajach edytorw tekstu. W zalenoci od systemu operacyjnego moe pojawi si nawet


opcja wyboru ktrego z edytorw.
Typy MIME dziaaj w Androidzie podobnie jak w przypadku protokou HTTP. Dostawca
otrzymuje danie typu MIME obsugiwanego identyfikatora URI, a nastpnie przekazuje skadajcy si z dwch czci cig znakw, ktry suy do identyfikowania tego typu MIME zgodnie
ze standardow konwencj sieciow. Standardy typw MIME mona znale pod adresem:
http://tools.ietf.org/html/rfc2046
Wedug specyfikacji typu MIME skada si on z dwch czci: typu oraz podtypu. Poniej
zaprezentowano przykady znanych par typu MIME:
text/html
text/css
text/xml
text/vnd.curl
application/pdf
application/rtf
application/vnd.ms-excel

Pen list zarejestrowanych typw i podtypw mona przejrze w witrynie organizacji IANA
(ang. Internet Assigned Numbers Authority Urzd Przydzielania Numerw Internetowych):
http://www.iana.org/assignments/media-types/
Podstawowymi zarejestrowanymi typami treci s:
application
audio
example
image
message
model
multipart
text
video

Kady gwny typ posiada podtypy. Jeeli jednak producent wykorzystuje zastrzeony format
danych, nazwa podtypu rozpoczyna si od vnd. Na przykad arkusze kalkulacyjne Microsoft
Excel s identyfikowane jako podtyp o nazwie vnd.ms-excel, podczas gdy standard pdf nie
jest zastrzeony, dlatego jego identyfikator nie posiada adnego przedrostka okrelajcego
producenta.
Pewne podtypy posiadaj przedrostek x- w nazwie; s to podtypy niestandardowe, ktrych nie
trzeba rejestrowa. S one uznawane za prywatne wartoci, ktre s dwustronnie definiowane
pomidzy dwoma wsppracujcymi agentami. Poniej znajduje si kilka przykadw:
application/x-tar
audio/x-aiff
video/x-msvideo

Android posuguje si konwencj podobn do definiowania typw MIME. Prefiks vnd wskazuje
na to, e typy i podtypy s niestandardowymi formami okrelonymi przez producenta. W celu
zapewnienia niepowtarzalnoci Android idzie dalej w kierunku rozgraniczenia wieloczciowych typw i podtypw przypominajcych specyfikacj domenow. Co wicej, typ MIME
w Androidzie przybiera dwie formy dla kadej treci: jedn dla okrelonego rekordu, a drug dla
wielu rekordw.

130 Android 3. Tworzenie aplikacji


Dla pojedynczego rekordu typ MIME wyglda nastpujco:
vnd.android.cursor.item/vnd.yourcompanyname.contenttype

Dla krotek lub zbioru rekordw wyglda on tak:


vnd.android.cursor.dir/vnd.yourcompanyname.contenttype

Dwa przykady:
// Pojedyncza notatka
vnd.android.cursor.item/vnd.google.note

// Zbir lub katalog notatek


vnd.android.cursor.dir/vnd.google.note

Nasuwa si wniosek, e Android rozpoznaje natywnie katalog elementw oraz


pojedyncze elementy. Elastyczno programistyczna jest ograniczona do podtypw.
Na przykad takie elementy jak opcje listy zale od tego, co zostanie przekazane
w kursorze jako jeden z gwnych typw MIME.

Typy MIME s powszechnie stosowane w Androidzie, zwaszcza w intencjach, w przypadku


ktrych system wanie dziki typom danych MIME moe wybra waciw aktywno i j
przywoa. Typy MIME s niezmiennie wydzielane z ich identyfikatorw URI dziki dostawcom
treci. Naley pamita o trzech kwestiach podczas pracy z tymi typami:
Zarwno typ, jak i podtyp musz w sposb jednoznaczny reprezentowa wskazywany
element. Jak stwierdzilimy, typ jest cile ustalony w postaci katalogu elementw
lub pojedynczego elementu. W kontekcie systemu Android nie s one zbyt otwartymi
obiektami.
Jeeli typ i podtyp nie s standardowe, naley poprzedzi je prefiksem vnd (zazwyczaj
dzieje si to w przypadku okrelonych rekordw).
Przewanie przydziela si im przestrze nazw zgodnie z potrzeb.
Podsumowujc, gwny typ MIME zbioru elementw zwrconych przez obiekt cursor powinien zawsze wyglda tak jak vnd.android.cursor.dir, natomiast analogiczny przypadek dla
pojedynczego elementu powinien przybra posta vnd.android.cursor.item. Wiksze pole
do manewru istnieje w przypadku podtypu, na przykad vnd.google.note; po przedrostku
vnd. mona wpisa dowoln nazw.

Odczytywanie danych za pomoc identyfikatorw URI


Wiadomo teraz, e do odczytu danych zawartych w dostawcy treci naley wykorzysta identyfikatory URI tego dostawcy. Poniewa identyfikatory te s niepowtarzalne dla kadego dostawcy,
bardzo wane jest ich udokumentowanie oraz udostpnienie ich listy programistom do wgldu,
a nastpnie do ich wywoywania. Zaimplementowana w Androidzie technologia dostawcw
dokonuje tego poprzez definiowanie staych, reprezentujcych te cigi znakw URI.
Przyjrzyjmy si trzem identyfikatorom URI, zdefiniowanym przez klasy pomocnicze w rodowisku Android SDK:
MediaStore.Images.Media.INTERNAL_CONTENT_URI
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
Contacts.People.CONTENT_URI

Rozdzia 4 Dostawcy treci

131

Ich odpowiednikami w postaci tekstowych cigw znakw s:


content://media/internal/images
content://media/external/images
content://contacts/people/

Dostawca MediaStore definiuje dwa identyfikatory URI, a dostawca Contacts okrela jeden
identyfikator. Mona zauway, e te stae s definiowane za pomoc drzewa hierarchii.
Przykadowy identyfikator URI dla kontaktw jest zdefiniowany jako Contacts.People.
CONTENT_URI. Spowodowane jest to faktem, e bazy danych kontaktw mog korzysta z wielu
tablic do reprezentowania jednostek w dostawcy Contacts. Kategoria People jest jedn z tablic
lub zbiorw. Kada podstawowa jednostka bazy danych moe posiada wasny identyfikator
URI, wszystkie s jednak zakorzenione za pomoc podstawowej nazwy upowanienia (w przypadku dostawcy kontaktw jest to contacts://contacts).
W odniesieniu Contacts.People.CONTENT_URI element Contacts jest pakietem Java,
a skadnik People stanowi interfejs wewntrz tego pakietu. Zwrmy rwnie uwag
na fakt, i identyfikatory Contacts oraz Contacts.people stay si przestarzae w wersji 2.0
Androida, a ich nowe odpowiedniki zostay omwione w rozdziale 27. Jednak identyfikatory
te cigle s przydatne, zwaszcza do omawiania koncepcji dostawcw treci.

Biorc pod uwag takie identyfikatory URI, kod sucy do odczytania jednego wiersza dostawcy
treci zawierajcego imiona ludzi wyglda nastpujco:
Uri peopleBaseUri = Contacts.People.CONTENT_URI;
Uri myPersonUri = Uri.withAppendedId(Contacts.People.CONTENT_URI, 23);

// Kwerenda dla tego rekordu


// managedQuery jest metod w klasie Activity
Cursor cur = managedQuery(myPersonUri, null, null, null);

Zwrmy uwag, e identyfikator Contacts.People.CONTENT_URI jest zdefiniowany jako staa


w klasie People. W tym przykadzie do gwnego identyfikatora URI zostaje dodany identyfikator okrelonej osoby i nastpuje wywoanie metody managedQuery.
Jako cz kwerendy w identyfikatorze URI mona wprowadzi kolejno sortowania, zaznaczanie okrelonych kolumn oraz klauzul WHERE. W powyszym przykadzie te parametry maj
warto null.
Dostawca treci powinien wywietli list obsugiwanych przez niego kolumn poprzez
zaimplementowanie zestawu interfejsw lub wywietlenie nazw tych kolumn w postaci
staych. Jednak klasa lub interfejs, ktre definiuj kolumny w formie staych, powinny
take w jasny sposb okrela typ kolumny za pomoc odpowiednio dobranej konwencji
nazewnictwa, komentarzy lub dokumentacji, poniewa nie istnieje formalny sposb
okrelania typu kolumny za pomoc staych.

Na podstawie wczeniejszych przykadw na listingu 4.1 przedstawiono metod wywoania


kursora, zawierajcego okrelon list kolumn z tabeli People umieszczonej wewntrz dostawcy
treci contacts.

132 Android 3. Tworzenie aplikacji


Listing 4.1. Wywoanie obiektu Cursor z dostawcy treci
// Tabela okrelajca zwracane kolumny
string[] projection = new string[] {
People._ID,
People.NAME,
People.NUMBER,
};

// Odczytuje baz identyfikatorw URI tablicy People w dostawcy treci Contacts


// np. content://contacts/people/
Uri mContactsUri = Contacts.People.CONTENT_URI;

// Najlepsza metoda odczytywania kwerendy; zwraca zarzdzan kwerend


Cursor managedCursor = managedQuery( mContactsUri,
projection, //Ktra kolumna bdzie zwrcona
null,

// klauzula WHERE

Contacts.People.NAME + " ASC");

// klauzula sortowania

Zwrmy uwag, e obiekt projection jest jedynie tablic cigw znakw, reprezentujc
nazwy kolumn. Zatem bez znajomoci nazw tych kolumn trudno bdzie go utworzy. Ich nazwy
mona znale w tej samej klasie, ktra dostarcza identyfikator URI, w tym przypadku w klasie
People. Zobaczmy, jakie s w niej zdefiniowane nazwy pozostaych kolumn:
CUSTOM_RINGTONE
DISPLAY_NAME
LAST_TIME_CONTACTED
NAME
NOTES
PHOTO_VERSION
SEND_TO_VOICE_MAIL
STARRED
TIMES_CONTACTED

Informacje na temat tych kolumn mona znale w dokumentacji pakietu SDK dotyczcej
klasy android.provider.Contacts.PeopleColumns. Dokumentacja ta jest dostpna pod adresem:
http://developer.android.com/reference/android/provider/Contacts.PeopleColumns.html
Jak ju wczeniej zasugerowalimy, baza danych typu contacts zawiera wiele tabel, z ktrych
kada jest reprezentowana przez klas lub interfejs, co umoliwia opisanie tych kolumn oraz ich
typw. Przyjrzyjmy si pakietowi android.providers.Contacts, ktrego kod rdowy mona
przejrze na stronie:
http://developer.android.com/reference/android/provider/Contacts.html
Pakiet zawiera nastpujce zagniedone klasy lub interfejsy:
ContactMethods
Extensions
Groups
Organizations
People

Rozdzia 4 Dostawcy treci

133

Phones
Photos
Settings

Kada z tych klas reprezentuje nazw tabeli w bazie danych contacts.db, natomiast wszystkie
tabele maj opisa struktur swoich wasnych identyfikatorw URI. W dodatku dla kadej klasy
jest zdefiniowany odpowiedni interfejs Columns, umoliwiajcy identyfikacj nazw kolumn, na
przykad PeopleColumns.
Spjrzmy jeszcze na otrzymany obiekt Cursor moe nie zawiera adnych rekordw. Nazwy, typ oraz kolejno kolumn s okrelone dla kadego dostawcy. Jednak kady zwrcony
wiersz posiada domyln kolumn _id, stanowic niepowtarzalny identyfikator tego wiersza.

Korzystanie z kursora systemu Android


Oto kilka faktw na temat kursora systemu Android:
Kursor jest zbiorem krotek.
Naley skorzysta z metody moveToFirst() przed odczytem jakichkolwiek danych,
poniewa kursor jest ustawiony przed pierwsz krotk.
Niezbdna jest znajomo nazw kolumn.
Niezbdna jest znajomo typw kolumn.
Wszystkie metody pola dostpu opieraj si na numerze kolumny, zatem naley
najpierw przeksztaci nazw kolumny na jej numer.
Obiekt Cursor jest kursorem swobodnym (moe porusza si do przodu, do tyu
oraz przeskakiwa).
Dziki powyszej waciwoci istnieje moliwo wykorzystania go do zliczania krotek.
Istnieje wiele metod, dziki ktrym mona sterowa kursorem w Androidzie. Na listingu 4.2
pokazalimy, w jaki sposb mona sprawdzi, czy kursor jest pusty, oraz w jaki sposb go
przemieszcza krotka po krotce, w przypadku gdy nie jest pusty.
Listing 4.2. Sterowanie kursorem za pomoc ptli while
if (cur.moveToFirst() == false)
{

// brak wierszy, pusty kursor


return;
}

// Kursor wskazuje pierwszy wiersz


// Uzyskajmy dostp do kilku kolumn
int nameColumnIndex = cur.getColumnIndex(People.NAME);
String name = cur.getString(nameColumnIndex);

// Niech kursor sprawdza wiersze po kolei


while(cur.moveToNext())
{

// Kursor pomylnie przeniesiony


// pola dostpu
}

134 Android 3. Tworzenie aplikacji


Na pocztku listingu 4.2 zaoylimy, e kursor jest ulokowany przed pierwsz krotk. eby
umieci kursor na pozycji pierwszego wiersza, stosujemy metod moveToFirst() wobec obiektu
cursor. Jeeli kursor jest pusty, otrzymujemy warto false. Korzystamy wic wielokrotnie
z metody moveToNext(), eby przesuwa kursor.
Aby si dowiedzie, gdzie w danej chwili znajduje si kursor, mona skorzysta z poniszych
metod:
isBeforeFirst()
isAfterLast()
isClosed()

Mona je rwnie wykorzystywa w ptli for (listing 4.3) zamiast w przedstawionej na listingu
4.2 ptli while.
Listing 4.3. Sterowanie kursorem za pomoc ptli for
//Najpierw uzyskujemy indeksy spoza ptli
int nameColumn = cur.getColumnIndex(People.NAME);
int phoneColumn = cur.getColumnIndex(People.NUMBER);

//Ruch kursora jest teraz zaleny od indeksw kolumn


for(cur.moveToFirst();!cur.isAfterLast();cur.moveToNext())
{
String name = cur.getString(nameColumn);
String phoneNumber = cur.getString(phoneColumn);
}

Kolejno wystpowania indeksw w kolumnach zdaje si by przyjta nieco arbitralnie. Z tego


powodu zalecamy pozyskiwanie w jawny sposb indeksw z kursora w celu uniknicia
niespodzianek. eby okreli liczb krotek objtych kursorem, stosuje si dostpn w Androidzie
metod getCount().

Praca z klauzul WHERE


Istniej dwa sposoby wprowadzenia klauzuli WHERE w dostawcach treci:
za pomoc identyfikatora URI,
za pomoc kombinacji klauzuli string oraz zestawu wymiennych argumentw,
stanowicych tablice cigw znakw.
Obydwie wymienione metody przedstawimy w formie przykadowych kodw.
Wprowadzanie klauzuli WHERE za pomoc identyfikatora URI
Wyobramy sobie, e chcemy odczyta notatk o identyfikatorze 23 z bazy notatek Google. Do
uzyskania kursora zawierajcego jeden wiersz odpowiadajcy wierszowi nr 23 tablicy notatek
mona napisa kod zaprezentowany na listingu 4.4.
Listing 4.4. Wprowadzanie klauzul WHERE jzyka SQL za pomoc identyfikatora URI
Activity someActivity;

//...inicjalizacja aktywnoci someActivity


String noteUri = "content://com.google.provider.NotePad/notes/23";

Rozdzia 4 Dostawcy treci

135

Cursor managedCursor = someActivity.managedQuery( noteUri,


projection, // Ktre kolumny zostan przekazane
null,

// Klauzula WHERE
// Klauzula sortowania

null);

Pozostawilimy warto null w argumencie klauzuli WHERE, bdcej czci metody managedQuery,
poniewa w tym przypadku zaoylimy, e dostawca notatek jest w stanie sam okreli warto
obiektu id podanej notatki. Warto ta jest umieszczona w identyfikatorze URI. Uylimy
identyfikatora URI jako pojemnika do wprowadzenia klauzuli WHERE. Staje si to zrozumiae,
gdy si zwrci uwag, w jaki sposb dostawca treci implementuje zwizan z nim metod kwerendy. Poniej umiecilimy fragment kodu takiej metody:
// Uzyskuje identyfikator notatki z przychodzcego identyfikatora uri, wygldajcego jak
//content://.../notes/23
int noteId = uri.getPathSegments().get(1);

// da od konstruktora kwerend utworzenia zapytania


// Okrela nazw tabeli
queryBuilder.setTables(NOTES_TABLE_NAME);

// Wykorzystuje warto noteID do wstawienia klauzuli WHERE


queryBuilder.appendWhere(Notes._ID + "=" + noteId);

Zauwamy, w jaki sposb obiekt id notatki jest uzyskiwany z identyfikatora URI. Klasa Uri, reprezentujca nadchodzcy element uri, posiada metod pozwalajc na wydobycie fragmentw
identyfikatora nastpujcych po gwnej czci content://com.google.provider.NotePad.
Fragmenty te nosz nazw segmentw cieki; s to cigi znakw oddzielonych znakiem /
na przykad /seg1/seg3/seg4/ indeksowane na podstawie pozycji. Dla naszego identyfikatora URI pierwszym segmentem moe by 23. Segment ten naley doda do klauzuli WHERE,
okrelonej w klasie QueryBuilder. Ostatecznie rwnowana instrukcja wyboru wygldaaby
nastpujco:
select * from notes where _id = 23

Klasy Uri oraz UriMatcher uywane s do rozpoznawania identyfikatorw URI oraz


wydobywania z nich parametrw (klasa UriMatcher zostanie omwiona w podpunkcie
Stosowanie klasy UriMatcher do rozpoznawania identyfikatorw URI). W pakiecie
android.database.sqlite znajduje si pomocnicza klasa SQLiteQueryBuilder,
pozwalajca na konstruowanie kwerend SQL wykonywanych przez klas SQLiteDatabase
w wystpieniu bazy danych SQLite.

Stosowanie jawnych klauzul WHERE


Po zaprezentowaniu metody, w ktrej identyfikator URI suy do wprowadzania klauzuli WHERE,
nadszed czas na pokazanie drugiego sposobu, umoliwiajcego wysyanie listy jawnych
kolumn oraz odpowiadajcych im wartoci w formie tej klauzuli. Przyjrzyjmy si ponownie
metodzie managedQuery z klasy Activity, przedstawionej na listingu 4.4. Oto jej sygnatura:
public final Cursor managedQuery(Uri uri,
String[] projection,
String selection,

136 Android 3. Tworzenie aplikacji


String[] selectionArgs,
String sortOrder)

Zwrmy uwag na argument selection, ktrego zadeklarowanym typem jest String. Ten
cig znakw peni funkcj filtra (w istocie klauzuli WHERE) okrelajcego wiersze, ktre zostan
otrzymane i sformatowane do postaci klauzuli WHERE jzyka SQL (sama klauzula WHERE zostaje
pominita). Parametr null zwrci wszystkie krotki ze wskazanego identyfikatora URI. W cigu
znakw wyboru mona wstawia znaki zapytania, ktre bd zastpowane przez wartoci argumentu selectionArgs w kolejnoci ich pojawiania si. Typem wartoci bdzie String.
Poniewa istniej dwie metody okrelania klauzuli WHERE, mog pojawi si problemy ze stwierdzeniem, w jaki sposb dostawca treci wykorzysta te klauzule oraz ktra z nich uzyskuje pierwszestwo, jeeli s wykorzystywane obydwa mechanizmy.
Na przykad mona wysa kwerend dotyczc notatki numer 23 na obydwa sposoby:
// metoda URI
managedQuery("content://com.google.provider.NotePad/notes/23"
,null
,null
,null
,null);

lub
// jawna klauzula WHERE
managedQuery("content://com.google.provider.NotePad/notes"
,null
,"_id=?"
,new String[] {23}
,null);

Zgodnie z konwencj metod URI stosuje si zawsze tam, gdzie istnieje taka moliwo, natomiast jawne definiowanie klauzuli WHERE uywane jest w specjalnych przypadkach.

Wstawianie rekordw
Do tej pory zajmowalimy si odczytywaniem danych z dostawcw treci za pomoc identyfikatorw URI. Teraz omwimy techniki wstawiania, aktualizowania oraz usuwania danych.
W trakcie objaniania zagadnienia dostawcw treci szeroko wykorzystujemy przykady
zaczerpnite z prototypowej aplikacji Notepad, ktr firma Google zamiecia jako cz
samouczka. Nie jest jednak wymagana doskonaa znajomo tego programu. Nawet osoby,
ktre go nie znaj, powinny bez wikszego problemu zrozumie przytaczane tu przykady.
W dalszej czci rozdziau zaprezentujemy peny kod przykadowego dostawcy treci.

Klasa android.content.ContentValues w Androidzie suy do przechowywania wstawianej


wartoci pojedynczego rekordu. Klasa ContentValues stanowi katalog par klucz warto, na
przykad nazwa kolumny i jej wartoci. Rekordy s wstawiane najpierw poprzez wprowadzenie
rekordu do klasy ContentValues, a nastpnie klasa android.content.ContentResolver wstawia
ten rekord za pomoc identyfikatora URI.

Rozdzia 4 Dostawcy treci

137

Klasa ContentResolver jest niezbdna, poniewa na tym etapie nie wymaga si


wstawiania rekordu do bazy danych. Zamiast tego wstawia si rekord do dostawcy
okrelanego przez identyfikator URI. Klasa ContentResolver rozpoznaje waciwego
dostawc treci i przekazuje mu obiekt umieszczony w klasie ContentValues.

Poniej zaprezentowano przykad umieszczenia pojedynczego wiersza z notatkami w klasie


ContentValues:
ContentValues values = new ContentValues();
values.put("title", "Nowa notatka");
values.put("note","To jest nowa notatka");

// obiekt values jest teraz przygotowany do wstawienia

Odniesienie do klasy ContentResolver otrzymuje si poprzez zapytanie klasy Activity:


ContentResolver contentResolver = activity.getContentResolver();

Teraz wystarczy wskaza identyfikator URI klasie ContentResolver, eby wstawi wiersz. Identyfikatory te s zdefiniowane w klasie odpowiadajcej tabeli Notes. Na przykadzie aplikacji Notepad identyfikatorem jest:
Notepad.Notes.CONTENT_URI

Moemy teraz wykorzysta posiadany identyfikator URI oraz klas ContentValues i utworzy
danie wstawienia wiersza:
Uri uri = contentResolver.insert(Notepad.Notes.CONTENT_URI, values);

Otrzymujemy identyfikator URI wskazujcy na nowo wstawiony rekord. Identyfikator ten bdzie
posiada nastpujc struktur:
Notepad.Notes.CONTENT_URI/new_id

Dodawanie pliku do dostawcy treci


Czasami moe zaistnie potrzeba przechowania pliku w bazie danych. Standardowym sposobem jest zapisanie takiego pliku na dysku, a nastpnie aktualizacja rekordu bazy danych, ktry
ma wskazywa nazw tego pliku.
Android korzysta z tego protokou i go automatyzuje poprzez definiowanie specyficznej procedury zachowywania oraz odczytywania tych plikw. Stosuje si konwencj, wedle ktrej nazwa
z odniesieniem pliku jest zapisywana w rekordzie znajdujcym si w specjalnie zarezerwowanej
do tego celu kolumnie _data.
Po wstawieniu rekordu do tabeli Android odsya dajcemu programowi identyfikator URI.
Po zapisaniu rekordu za pomoc tego mechanizmu naley zapisa plik w tej lokacji. Aby mona
byo to zrobi, Android umoliwia klasie ContentResolver uzyskanie wartoci parametru uri
z rekordu bazy danych i zwrcenie zapisywalnego strumienia danych wynikowych. Nastpnie Android umieszcza wewntrzny plik oraz magazynuje odniesienie do jego nazwy w polu
kolumny _data.
Mona rozwin przykad z aplikacj Notepad o zapisanie obrazu z okrelonej notatki. Naleaoby w takim przypadku stworzy dodatkow kolumn nazwan _data oraz zastosowa po
raz pierwszy instrukcj insert, eby otrzyma identyfikator URI. Poniej zademonstrowano
opisany fragment protokou:

138 Android 3. Tworzenie aplikacji


ContentValues values = new ContentValues();
values.put("title", "Nowa notatka");
values.put("note","To jest nowa notatka");

// Korzysta z rozpoznawania treci, eby wstawi rekord


ContentResolver contentResolver = activity.getContentResolver();
Uri newUri = contentResolver.insert(Notepad.Notes.CONTENT_URI, values);

Po uzyskaniu identyfikatora URI rekordu poniszy kod zostanie uyty do zadania od klasy
ContentResolver odniesienia do wyjciowego strumienia danych:
...

// Program rozpoznajcy tre bezporednio uzyskuje dostp do wyjciowego


// strumienia
// Klasa ContentResolver ukrywa dostp do pola _data, w ktrym jest przechowywane
// odniesienie do prawdziwego pliku.
OutputStream outStream = activity.getContentResolver().openOutputStream(newUri);
someSourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
outStream.close();

Nastpnie wyjciowy strumie zostaje wykorzystany przez kod do zapisu danych.

Aktualizowanie oraz usuwanie


Dotychczas zajmowalimy si kwerendami oraz instrukcj wstawiania danych. Instrukcje
aktualizowania oraz usuwania danych nie s skomplikowane. Wykonanie operacji aktualizacji jest
podobne do wykonywania operacji wstawienia, czyli zmienione wartoci kolumn przechodz
przez klas ContentValues. Sygnatura metody update w obiekcie ContentResolver wyglda
nastpujco:
int numberOfRowsUpdated =
activity.getContentResolver().update(
Uri uri,
ContentValues values,
String whereClause,
String[] selectionArgs )

Argument whereClause ogranicza aktualizacj do waciwych krotek. Sygnatura metody delete


wyglda analogicznie:
int numberOfRowsDeleted =
activity.getContentResolver().delete(
Uri uri,
String whereClause,
String[] selectionArgs )

Oczywicie w metodzie delete argument ContentValues staje si zbdny, gdy nie ma potrzeby
okrelania kolumn podczas usuwania rekordu.
Niemal wszystkie wywoania z klas managedQuery oraz ContentResolver s ostatecznie
kierowane do klasy provider. Wiedza, w jaki sposb dostawca implementuje kad z tych
metod, pozwala wysnu wnioski na temat techniki wykorzystywania ich przez klienta.
W nastpnej sekcji omwimy od podstaw implementacj przykadowego dostawcy treci,
nazwanego BookProvider.

Rozdzia 4 Dostawcy treci

139

Implementowanie dostawcw treci


Przedyskutowalimy metody interakcji z dostawc treci pod ktem pracy na danych, jednak
jeszcze nie pokazalimy, jak napisa nowego dostawc. W celu utworzenia dostawcy treci naley
rozszerzy klas android.content.ContentProvider i zaimplementowa nastpujce gwne
metody:
query
insert
update
delete
getType

Przed implementacj tych metod naley skonfigurowa kilka rzeczy. Omwimy implementacj
dostawcy treci w formie szczegowego opisu kadego z nastpujcych etapw:
1. Zaplanuj baz danych, identyfikatory URI, nazwy kolumn i tak dalej. Utwrz klas
metadanych, w ktrej bd zdefiniowane stae dla wszystkich elementw metadanych.
2. Rozszerz abstrakcyjn klas ContentProvider.
3. Zaimplementuj metody: query, insert, update, delete oraz getType.
4. Zarejestruj dostawc w pliku manifecie.

Planowanie bazy danych


Aby przedstawi to zagadnienie, pokaemy, jak zbudowa baz danych zawierajc kolekcj
ksiek. Zawiera ona tylko jedn tabel books, a jej kolumny nosz nazwy name, isbn oraz
author. Nazwy tych kolumn stanowi metadane. Zostan one zdefiniowane w klasie Java.
Klasa ta, nazwana BookProviderMetaData, jest zaprezentowana na listingu 4.5. Najistotniejsze
elementy tej klasy zostay zaznaczone pogrubieniem.
Listing 4.5. Definiowanie metadanych bazy danych: klasa BookProviderMetaData
public class BookProviderMetaData
{
public static final String AUTHORITY = "com.androidbook.provider.BookProvider";
public static final String DATABASE_NAME = "book.db";
public static final int DATABASE_VERSION = 1;
public static final String BOOKS_TABLE_NAME = "books";
private BookProviderMetaData() {}

// wewntrzna klasa opisujca obiekt BookTable


public static final class BookTableMetaData implements BaseColumns
{
private BookTableMetaData() {}
public static final String TABLE_NAME = "books";

// definicje identyfikatora URI oraz typu MIME


public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/books");
public static final String CONTENT_TYPE =

140 Android 3. Tworzenie aplikacji


"vnd.android.cursor.dir/vnd.androidbook.book";
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.androidbook.book";
public static final String DEFAULT_SORT_ORDER = "modified DESC";

// Tu rozpoczynaj si dodatkowe kolumny


// typ string
public static final String BOOK_NAME = "name";

// typ string
public static final String BOOK_ISBN = "isbn";

// typ string
public static final String BOOK_AUTHOR = "author";

// Liczba cakowita z metody System.currentTimeMillis()


public static final String CREATED_DATE = "created";

// Liczba cakowita z metody System.currentTimeMillis()


public static final String MODIFIED_DATE = "modified";
}
}

Klasa BookProviderMetaData rozpoczyna dziaanie od zdefiniowania swojego upowanienia


przybiera ono posta com.androidbook.provider.BookProvider. Posuy nam ono do zarejestrowania dostawcy w pliku manifecie Androida. Cig znakw upowanienia stanowi pocztkow cz identyfikatora URI tego dostawcy.
Nastpnie klasa ta definiuje jedn tabel (books) jako wewntrzn klas BookTableMetaData.
Klasa BookTableMetaData tworzy identyfikator URI, potrzebny do rozpoznawania kolekcji
ksiek. Biorc pod uwag wspomniane w poprzednim akapicie upowanienie, identyfikator
URI dla kolekcji ksiek bdzie wyglda nastpujco:
content://com.androidbook.provider.BookProvider/books

Identyfikator ten jest wskazywany przez sta:


BookProviderMetaData.BookTableMetaData.CONTENT_URI

Klasa BookProviderMetaData przechodzi do definiowania typw MIME zbioru ksiek oraz


pojedynczych egzemplarzy. W implementacji dostawcy te stae posu do przekazywania
typw MIME przychodzcym identyfikatorom URI.
Nastpnie zostaje zdefiniowany zestaw kolumn: name, isbn, author, created (czas utworzenia)
oraz modified (data ostatniej aktualizacji).
Typy danych w kolumnach powinny by okrelane za pomoc komentarzy w kodzie.

Klasa metadanych BookTableMetaData dziedziczy rwnie elementy klasy BaseColumns, ktra


dostarcza standardowe pole _id reprezentujce identyfikator wiersza. Majc ju przygotowane
metadane, jestemy gotowi stawi czoa implementacji dostawcy.

Rozdzia 4 Dostawcy treci

141

Rozszerzanie klasy ContentProvider


Implementacja naszego przykadu dostawcy treci BookProvider niesie ze sob konieczno
rozszerzenia klasy ContentProvider oraz przesonienia metody onCreate() w celu utworzenia
bazy danych, a nastpnie wprowadzenia metod query, insert, update, delete i getType.
W tym podpunkcie omwimy proces konfigurowania oraz tworzenia bazy danych, natomiast
nieco dalej przedstawimy kolejno metody query, insert, update, delete i getType. Na listingu
4.6 zosta zaprezentowany peny kod rdowy tej klasy. Najwaniejsze sekcje zostay zaznaczone
pogrubieniem.
Metoda query da okrelonego przez ni zestawu kolumn. Przypomina to klauzul select,
ktra da nazw kolumn wraz z ich odpowiednikami as (czasami zwanymi synonimami).
Android wykorzystuje obiekt map, ktry wywouje map projekcji, reprezentujc nazwy tych
kolumn oraz ich synonimy. Musimy skonfigurowa tak map, eby wprowadzi j w pniejszej implementacji metody query. W kodzie implementacji dostawcy (listing 4.6) odpowiedzialny za to fragment zosta umieszczony na pocztku jako cz konfiguracji mapy projektu.
Wikszo implementowanych przez nas metod pobiera identyfikatory URI w postaci danych
wejciowych. Chocia wszystkie identyfikatory obsugiwane przez tego dostawc posiadaj
taki sam pocztek adresu, jego kocwka bdzie w kadym identyfikatorze inna tak samo
jak w przypadku strony WWW. Kady identyfikator musi rni si kocowymi czonami
w celu okrelania rnych danych lub dokumentw. Zilustrujemy to przykadem:
Uri1: content://com.androidbook.provider.BookProvider/books
Uri2: content://com.androidbook.provider.BookProvider/books/12

Zauwamy, w jaki sposb dostawca treci BookProvider rozrnia poszczeglne identyfikatory.


Zaprezentowalimy prosty przypadek. Gdyby nasz dostawca przechowywa nie tylko ksiki,
zostayby utworzone identyfikatory rwnie dla tych obiektw.
Implementacja dostawcy potrzebuje mechanizmu umoliwiajcego odrnianie poszczeglnych identyfikatorw URI; w tym celu zostaa wprowadzona klasa UriMatcher. Musimy wic
skonfigurowa ten obiekt wraz ze wszystkimi wariacjami identyfikatora URI. Kod ten zosta
umieszczony na listingu 4.6 zaraz po fragmencie definiujcym map. Klasa UriMatcher zostanie
dokadniej omwiona w podpunkcie Stosowanie klasy UriMatcher do rozpoznawania identyfikatorw URI.
Kod z listingu 4.6 powoduje nastpnie przesonicie metody onCreate(), co uatwia utworzenie
bazy danych. Kod rdowy implementuje nastpnie metody insert(), query(), update(),
getType() oraz delete(). Cay kod zosta zaprezentowany na jednym listingu, jednak jego poszczeglne aspekty zostan omwione w oddzielnych podpunktach.
Listing 4.6. Implementacja dostawcy treci BookProvider
public class BookProvider extends ContentProvider
{

// Docza pomocniczy znacznik. Nie ma znaczenia dla dostawcw.


private static final String TAG = "BookProvider";

// Konfiguruje map projekcji.


// Mapy projekcji s podobne do aliasu kolumny as w jzyku sql,
// za pomoc ktrej mona zmienia nazwy kolumn.
private static HashMap<String, String> sBooksProjectionMap;

142 Android 3. Tworzenie aplikacji


static
{
sBooksProjectionMap = new HashMap<String, String>();
sBooksProjectionMap.put(BookTableMetaData._ID, BookTableMetaData._ID);

// kolumny name, isbn, author


sBooksProjectionMap.put(BookTableMetaData.BOOK_NAME
, BookTableMetaData.BOOK_NAME);
sBooksProjectionMap.put(BookTableMetaData.BOOK_ISBN
, BookTableMetaData.BOOK_ISBN);
sBooksProjectionMap.put(BookTableMetaData.BOOK_AUTHOR
, BookTableMetaData.BOOK_AUTHOR);

// kolumny created, modified


sBooksProjectionMap.put(BookTableMetaData.CREATED_DATE
, BookTableMetaData.CREATED_DATE);
sBooksProjectionMap.put(BookTableMetaData.MODIFIED_DATE
, BookTableMetaData.MODIFIED_DATE);
}

// Konfiguruje identyfikatory URI.


// Mechanizm umoliwiajcy identyfikowanie wzorcw wszystkich przychodzcych
// identyfikatorw URI.
private static final UriMatcher sUriMatcher;
private static final int INCOMING_BOOK_COLLECTION_URI_INDICATOR = 1;
private static final int INCOMING_SINGLE_BOOK_URI_INDICATOR = 2;
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(BookProviderMetaData.AUTHORITY
, "books"
, INCOMING_BOOK_COLLECTION_URI_INDICATOR);
sUriMatcher.addURI(BookProviderMetaData.AUTHORITY
, "books/#",
INCOMING_SINGLE_BOOK_URI_INDICATOR);
}

/**
* Konfiguracja/tworzenie bazy danych.
* Klasa ta pomaga otwiera, tworzy i aktualizowa plik bazy danych.
*/
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, BookProviderMetaData.DATABASE_NAME, null
, BookProviderMetaData.DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.d(TAG,"wywolana wewnetrzna metoda oncreate");
db.execSQL("CREATE TABLE " + BookTableMetaData.TABLE_NAME + " ("
+ BookProviderMetaData._ID + " INTEGER PRIMARY KEY,"
+ BookTableMetaData.BOOK_NAME + " TEXT,"
+ BookTableMetaData.BOOK_ISBN + " TEXT,"
+ BookTableMetaData.BOOK_AUTHOR + " TEXT,"

Rozdzia 4 Dostawcy treci

143

+ BookTableMetaData.CREATED_DATE + " INTEGER,"


+ BookTableMetaData.MODIFIED_DATE + " INTEGER"
+ ");");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(TAG,"wywolana wewnetrzna metoda onupgrade");
Log.w(TAG, "Aktualizacja bazy danych z wersji " + oldVersion + " do wersji "
+ newVersion + ", w wyniku ktorej wszystkie stare dane zostana usuniete");
db.execSQL("DROP TABLE IF EXISTS " + BookTableMetaData.TABLE_NAME);
onCreate(db);
}
}
private DatabaseHelper mOpenHelper;

// Zajmuje si kwesti wywoywania zwrotnego metody OnCreate.


@Override
public boolean onCreate() {
Log.d(TAG,"wywolana glowna metoda onCreate");
mOpenHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection
, String[] selectionArgs, String sortOrder)
{
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
switch (sUriMatcher.match(uri))
{
case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
qb.setTables(BookTableMetaData.TABLE_NAME);
qb.setProjectionMap(sBooksProjectionMap);
break;
case INCOMING_SINGLE_BOOK_URI_INDICATOR:
qb.setTables(BookTableMetaData.TABLE_NAME);
qb.setProjectionMap(sBooksProjectionMap);
qb.appendWhere(BookTableMetaData._ID + "="
+ uri.getPathSegments().get(1));
break;
default:
throw new IllegalArgumentException("Nieznany ident. URI" + uri);
}

// Jeeli kolejno sortowania nie jest okrelona, naley skorzysta


// z domylnej wartoci.
String orderBy;
if (TextUtils.isEmpty(sortOrder)) {
orderBy = BookTableMetaData.DEFAULT_SORT_ORDER;
} else {

144 Android 3. Tworzenie aplikacji


orderBy = sortOrder;
}

// Otwiera baz danych i uruchamia kwerend.


SQLiteDatabase db =
mOpenHelper.getReadableDatabase();
Cursor c = qb.query(db, projection, selection,
selectionArgs, null, null, orderBy);

// Przykadowy sposb zliczania.


int i = c.getCount();

// Informuje kursor, ktry identyfikator URI ma by obserwowany


// na wypadek zmiany rda danych.
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
return BookTableMetaData.CONTENT_TYPE;
case INCOMING_SINGLE_BOOK_URI_INDICATOR:
return BookTableMetaData.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Nieznany ident. URI " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {

// Sprawdza dany identyfikator Uri.


if (sUriMatcher.match(uri) != INCOMING_BOOK_COLLECTION_URI_INDICATOR) {
throw new IllegalArgumentException("Nieznany ident. URI " + uri);
}
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
Long now = Long.valueOf(System.currentTimeMillis());

// Naley si upewni, e wszystkie s skonfigurowane.


if (values.containsKey(BookTableMetaData.CREATED_DATE) == false) {
values.put(BookTableMetaData.CREATED_DATE, now);
}
if (values.containsKey(BookTableMetaData.MODIFIED_DATE) == false) {
values.put(BookTableMetaData.MODIFIED_DATE, now);
}

Rozdzia 4 Dostawcy treci

145

if (values.containsKey(BookTableMetaData.BOOK_NAME) == false) {
throw new SQLException(
" Nieudana prba wstawienia wiersza z powodu braku nazwy ksiki " + uri);
}
if (values.containsKey(BookTableMetaData.BOOK_ISBN) == false) {
values.put(BookTableMetaData.BOOK_ISBN, "Nieznany numer ISBN");
}
if (values.containsKey(BookTableMetaData.BOOK_AUTHOR) == false) {
values.put(BookTableMetaData.BOOK_ISBN, "Nieznany autor");
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(BookTableMetaData.TABLE_NAME
, BookTableMetaData.BOOK_NAME, values);
if (rowId > 0) {
Uri insertedBookUri = ContentUris.withAppendedId(
BookTableMetaData.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(insertedBookUri, null);
return insertedBookUri;
}
throw new SQLException("Nieudana prba umieszczenia wiersza w " + uri);
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
switch (sUriMatcher.match(uri)) {
case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
count = db.delete(BookTableMetaData.TABLE_NAME, where, whereArgs);
break;
case INCOMING_SINGLE_BOOK_URI_INDICATOR:
String rowId = uri.getPathSegments().get(1);
count = db.delete(BookTableMetaData.TABLE_NAME
, BookTableMetaData._ID + "=" + rowId
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : "")
, whereArgs);
break;
default:
throw new IllegalArgumentException("Nieznany ident. URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs)
{
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
switch (sUriMatcher.match(uri)) {
case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
count = db.update(BookTableMetaData.TABLE_NAME,
values, where, whereArgs);

146 Android 3. Tworzenie aplikacji


break;
case INCOMING_SINGLE_BOOK_URI_INDICATOR:
String rowId = uri.getPathSegments().get(1);
count = db.update(BookTableMetaData.TABLE_NAME
, values
, BookTableMetaData._ID + "=" + rowId
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : "")
, whereArgs);
break;
default:
throw new IllegalArgumentException("Nieznany ident. URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}

Wypenianie kontraktw typw MIME


Dostawca treci BookProvider musi posiada take zaimplementowan metod getType(),
przekazujc typ MIME danego identyfikatora URI. Podobnie jak w przypadku innych metod
dostawcy treci jest ona przeciona w odniesieniu do nadchodzcych identyfikatorw URI. W ten
sposb jej zadaniem jest rozrnianie typw identyfikatorw URI: czy dany typ okrela kolekcj
ksiek, czy te tylko jeden egzemplarz.
Jak wczeniej wspomnielimy, zastosujemy klas UriMatcher do okrelenia typu identyfikatora URI. Klasa BookTableMetaData posiada zdefiniowane stae, przekazywane w zalenoci od
identyfikatora URI. Listing 4.6 przedstawia implementacj tej metody.

Implementowanie metody query


W dostawcy treci metoda query przekazuje zbir wierszy, w zalenoci od przychodzcych
identyfikatorw URI oraz klauzuli WHERE.
Rwnie ta metoda wykorzystuje klas UriMatcher do rozpoznawania typu identyfikatora URI.
Jeeli typ identyfikatora URI odpowiada pojedynczemu elementowi, metoda ta otrzymuje
kod reprezentujcy ksik w nastpujcy sposb:
1. Wydobywa segmenty cieki za pomoc metody getPathSegments().
2. Numeruje segmenty identyfikatora URI w celu znalezienia pierwszej czci cieki,
ktra jest identyfikatorem ksiki.
Metoda query wykorzystuje nastpnie utworzone na pocztku listingu 4.6 mapy do identyfikowania otrzymywanych kolumn. Ostatecznie kursor jest przekazywany programowi dajcemu. W trakcie tego procesu metoda query wykorzystuje klas SQLiteQueryBuilder do
sformuowania oraz wykonania kwerendy (listing 4.6).

Rozdzia 4 Dostawcy treci

147

Implementowanie metody insert


Zadaniem metody insert w dostawcy treci jest wstawianie rekordu do bazy danych oraz przekazywanie identyfikatora URI wskazujcego ten rekord.
Take w tym przypadku metoda UriMatcher suy do identyfikacji adresw URI. Kod sprawdza
najpierw, czy identyfikator ten wskazuje waciwy typ danych. Jeli nie, zostaje wywietlony
wyjtek (listing 4.6).
Nastpnie zostaj zweryfikowane obowizkowe oraz opcjonalne parametry kolumn. Jeeli nie
zostay zdefiniowane wartoci dla niektrych kolumn, mog zosta wstawione domylne.
W dalszej kolejnoci zostaje zastosowana klasa SQLiteDataBase do wstawienia rekordu oraz
utworzenia jego identyfikatora. Na kocu zostaje skonstruowany adres URI na podstawie
zwrconego identyfikatora z bazy danych.

Implementowanie metody update


W dostawcy treci metoda update aktualizuje rekord (lub rekordy) na podstawie przekazanych
wartoci kolumny, jak i klauzuli WHERE. Przekazuje ona liczb zaktualizowanych krotek.
Podobnie jak w pozostaych metodach, take i tutaj klasa UriMatcher suy do rozpoznawania
identyfikatorw URI. Jeeli identyfikator ten wskazuje zbir danych, nastpuje wykonanie klauzuli WHERE, dziki czemu zostanie zaktualizowana jak najwiksza liczba rekordw. Jeli identyfikator URI okrela pojedynczy egzemplarz, otrzymujemy identyfikator danej ksiki, zdefiniowany jako dodatkowa klauzula WHERE. Na kocu kod wywietla liczb uaktualnionych
rekordw (listing 4.6). W rozdziale 21. zostan gruntownie przeanalizowane konsekwencje
uytej tu metody notifyChange. Naley zwrci uwag, w jaki sposb metoda ta pozwala
ogosi wiatu, e dane obecne pod okrelonym adresem URI zostay zaktualizowane. Teoretycznie mona zastosowa tak sam technik przy metodzie insert, stwierdzajc, e katalog
/books zosta zmieniony po wstawieniu rekordu.

Implementowanie metody delete


Metoda delete w dostawcy treci suy do usuwania rekordu (lub rekordw) na podstawie
przekazywanej klauzuli WHERE. Nastpnie otrzymujemy liczb usunitych wierszy.
Tak samo jak w przypadku metod omwionych w poprzednich podpunktach, do okrelania
typu identyfikatora URI suy metoda UriMatcher. Jeeli identyfikator ten wskazuje zbir
danych, wykorzystywana jest klauzula WHERE, dziki czemu usuwana jest jak najwiksza
liczba rekordw. Jeeli warto tej klauzuli bdzie wynosi null, zostan usunite wszystkie
rekordy. Jeli identyfikator URI okrela pojedynczy egzemplarz, otrzymujemy identyfikator
danej ksiki, zdefiniowany jako dodatkowa klauzula WHERE. Na kocu kod wywietla liczb
usunitych rekordw (listing 4.6).

Stosowanie klasy UriMatcher do rozpoznawania identyfikatorw URI


Wspomnielimy kilkakrotnie o klasie UriMatcher, a zatem przyjrzyjmy si jej dokadniej. Niemal wszystkie metody w dostawcy treci s przecione w odniesieniu do rodzajw identyfikatorw URI. Na przykad do odczytania pojedynczego egzemplarza ksiki i caej listy ksiek jest
uywana ta sama metoda query(). Metoda ta musi zna rodzaj wywoywanego identyfikatora
URI. Klasa UriMatcher jest pomocna w identyfikacji typw adresw URI.

148 Android 3. Tworzenie aplikacji


Dziaa to nastpujco: instancja klasy UriMatcher zostaje powiadomiona o oczekiwanych wzorcach typw identyfikatora URI. Kady wzorzec otrzyma rwnie niepowtarzalny numer. Po zarejestrowaniu tych wzorcw klasa UriMatcher bdzie sprawdzaa, czy przychodzce identyfikatory im odpowiadaj.
Jak ju wczeniej stwierdzilimy, nasz dostawca treci BookProvider zawiera dwa wzorce identyfikatorw URI: jeden dla kolekcji ksiek, a drugi dla pojedynczych egzemplarzy. Kod przedstawiony na listingu 4.7 rejestruje obydwa wzorce za pomoc klasy UriMatcher. Zostaje przypisana warto 1 dla kolekcji ksiek i warto rwna 2 dla pojedynczego egzemplarza (same
wzorce identyfikatorw URI zostaj zdefiniowane w metadanych tabeli books).
Listing 4.7. Rejestrowanie wzorcw identyfikatorw URI za pomoc klasy UriMatcher
private static final UriMatcher sUriMatcher;

// Definiuje identyfikatory dla kadego typu adresu uri


private static final int INCOMING_BOOK_COLLECTION_URI_INDICATOR = 1;
private static final int INCOMING_SINGLE_BOOK_URI_INDICATOR = 2;
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

// Rejestruje wzorzec dla kolekcji ksiek


sUriMatcher.addURI(BookProviderMetaData.AUTHORITY
, "books"
, INCOMING_BOOK_COLLECTION_URI_INDICATOR);

// Rejestruje wzorzec dla jednej ksiki


sUriMatcher.addURI(BookProviderMetaData.AUTHORITY
, "books/#",
INCOMING_SINGLE_BOOK_URI_INDICATOR);
}

Po utworzeniu rejestracji mona zobaczy, jak rol odgrywa klasa UriMatcher w implementacji
metody query:
switch (sUriMatcher.match(uri)) {
case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
...
case INCOMING_SINGLE_BOOK_URI_INDICATOR:
...
default:
throw new IllegalArgumentException("Nieznany ident. URI " + uri);
}

Zwrmy uwag na sposb przekazywania przez metod match tej samej liczby, ktra zostaa
wczeniej zarejestrowana. Konstruktor klasy UriMatcher wykorzystuje liczb cakowit wzgldem
gwnego identyfikatora URI. UriMatcher przekazuje t warto, w przypadku gdy w adresie
URL nie ma ani segmentw cieki, ani upowanie. Warto NO_MATCH jest zwracana rwnie
w przypadku niepasujcych wzorcw identyfikatorw. Mona utworzy klas UriMatcher
nieposiadajc wzorcw dopasowania; w takim przypadku Android inicjalizuje wewntrznie warto NO_MATCH w klasie UriMatcher. Kod z listingu 4.7 mona zatem napisa rwnie
w nastpujcy sposb:
static {
sUriMatcher = new UriMatcher();
sUriMatcher.addURI(BookProviderMetaData.AUTHORITY

Rozdzia 4 Dostawcy treci

149

, "books"
, INCOMING_BOOK_COLLECTION_URI_INDICATOR);
sUriMatcher.addURI(BookProviderMetaData.AUTHORITY
, "books/#",
INCOMING_SINGLE_BOOK_URI_INDICATOR);
}

Korzystanie z map projekcji


Dostawca treci peni rol porednika pomidzy abstrakcyjnym a rzeczywistym zestawem
kolumn w bazie danych, mimo to zestawy te mog si rni. Podczas tworzenia kwerend
potrzebna jest mapa czca okrelane przez klienta kolumny klauzuli WHERE z rzeczywistymi bazodanowymi kolumnami. Takie mapy projekcji s konfigurowane za pomoc klasy
SQLiteQueryBuilder.
Poniej przytaczamy informacje zawarte w dokumentacji Android SDK, traktujce o metodzie mapowania public void setProjectionMap(Map columnMap) dostpnej w klasie
QueryBuilder:
Klasa ta ustanawia map projekcji dla kwerendy. Mapa projekcji wie nazwy kolumn
przekazywane kwerendzie przez program dajcy z bazodanowymi nazwami kolumn.
Jest to funkcja przydatna podczas zmieniania nazw kolumn, jak i suca do usuwania
niejasnoci w nazwach kolumn podczas wykonywania operacji czenia. Mona na
przykad poczy nazw name z people.name. Jeeli mapa projekcji jest konfigurowana,
musi zawiera nazwy wszystkich kolumn, ktre mog by wywoywane przez uytkownika,
nawet jeli klucz i warto s takie same.
Nasz dostawca treci BookProvider konfiguruje map projekcji w nastpujcy sposb:
sBooksProjectionMap = new HashMap<String, String>();
sBooksProjectionMap.put(BookTableMetaData._ID, BookTableMetaData._ID);

// kolumny name, isbn, author


sBooksProjectionMap.put(BookTableMetaData.BOOK_NAME
, BookTableMetaData.BOOK_NAME);
sBooksProjectionMap.put(BookTableMetaData.BOOK_ISBN
, BookTableMetaData.BOOK_ISBN);
sBooksProjectionMap.put(BookTableMetaData.BOOK_AUTHOR
, BookTableMetaData.BOOK_AUTHOR);

// kolumny created, modified


sBooksProjectionMap.put(BookTableMetaData.CREATED_DATE
, BookTableMetaData.CREATED_DATE);
sBooksProjectionMap.put(BookTableMetaData.MODIFIED_DATE
, BookTableMetaData.MODIFIED_DATE);

Teraz konstruktor kwerendy stosuje zmienn sBooksProjectionMap w nastpujcy sposb:


queryBuilder.setTables(BookTableMetaData_TABLE_NAME);
queryBuilder.setProjectionMap(sNotesProjectionMap);

Rejestrowanie dostawcy
Ostatnim etapem jest rejestracja dostawcy treci w pliku AndroidManifest.xml za pomoc
struktury ukazanej na listingu 4.8:

150 Android 3. Tworzenie aplikacji


Listing 4.8. Rejestrowanie dostawcy
<provider android:name=".BookProvider"
android:authorities="com.androidbook.provider.BookProvider "/>

Testowanie dostawcy BookProvider


Skoro ju otrzymalimy dostawc treci BookProvider, zaprezentujemy przykadowy kod,
pozwalajcy na przetestowanie koncepcji dostawcy treci. Przyjrzymy si procesom dodawania
oraz usuwania informacji o ksice, zliczania liczby ksiek oraz wywietlania wszystkich ksiek.
Naley pamita, e s to tylko wycinki z przykadowego projektu i jako takie nie dadz si
skompilowa, gdy brakuje tu kilku dodatkowych plikw, niezbdnych do dziaania projektu.
Sdzimy jednak, e te przykadowe fragmenty kodu mog si okaza cenne podczas demonstrowania uprzednio omawianych koncepcji.
Na kocu rozdziau zamiecilimy odnonik do przykadowego projektu, ktry mona pobra
oraz przetestowa w rodowisku Eclipse.

Dodawanie ksiki
Kod z listingu 4.9 powoduje wstawienie nowej ksiki do bazy danych.
Listing 4.9. Testowanie funkcji dodawania za pomoc dostawcy treci
public void addBook(Context context)
{
String tag = "Testowanie dostawcy BookProvider";
Log.d(tag,"Dodawanie ksiazki");
ContentValues cv = new ContentValues();
cv.put(BookProviderMetaData.BookTableMetaData.BOOK_NAME, "book1");
cv.put(BookProviderMetaData.BookTableMetaData.BOOK_ISBN, "isbn-1");
cv.put(BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR, "author-1");
ContentResolver cr = context.getContentResolver();
Uri uri = BookProviderMetaData.BookTableMetaData.CONTENT_URI;
Log.d(tag,"identyfikator uri wstawianej ksiazki:" + uri);
Uri insertedUri = cr.insert(uri, cv);
Log.d(tag,"identyfikator Uri wstawiania:" + insertedUri);
}

Usuwanie ksiki
Kod widoczny na listingu 4.10 powoduje usunicie ostatniego rekordu z ksikowej bazy danych.
Z kolei listing 4.11 ukazuje przykad dziaania metody getCount() widocznej w poniszym
kodzie.

Rozdzia 4 Dostawcy treci

151

Listing 4.10. Testowanie funkcji usuwania za pomoc dostawcy treci


public void removeBook(Context context)
{
String tag = "Testowanie dostawcy BookProvider";
int i = getCount(context); //Spjrzmy na funkcj getCount z listingu 4.11
ContentResolver cr = context.getContentResolver();
Uri uri = BookProviderMetaData.BookTableMetaData.CONTENT_URI;
Uri delUri = Uri.withAppendedPath(uri, Integer.toString(i));
Log.d(tag, "Identyfikator Uri usuwania:" + delUri);
cr.delete(delUri, null, null);
Log.d(tag, "Nowa zliczona wartosc:" + getCount(context));
}

Zauwamy, e mamy tu do czynienia z krtkim przykadem, pokazujcym mechanizm usuwania za pomoc identyfikatora URI. Algorytm uzyskiwania ostatniego identyfikatora URI nie
zawsze musi by stuprocentowo skuteczny. Powinien jednak poprawnie dziaa w przypadku
dodawania piciu rekordw, a nastpnie usuwania ich od koca, jednego po drugim. W typowej aplikacji dylibymy do wywietlenia rekordw w formie listy oraz poproszenia uytkownika o zaznaczenie rekordu przeznaczonego do usunicia. W ten sposb uzyskalibymy
dokadny identyfikator tego rekordu.

Zliczanie ksiek
Kod z listingu 4.11 pobiera kursor i zlicza zawarte w nim rekordy.
Listing 4.11. Zliczanie rekordw w tabeli
private int getCount(Context context)
{
Uri uri = BookProviderMetaData.BookTableMetaData.CONTENT_URI;
Activity a = (Activity)context;
Cursor c = a.managedQuery(uri,
null, //projekcja
null,

//cig znakw wyboru


//argumenty tablicy zawierajcej cigi znakw wyboru
null); //kolejno sortowania
null,

int numberOfRecords = c.getCount();


c.close();
return numberOfRecords;
}

Wywietlanie listy ksiek


Za pomoc kodu z listingu 4.12 pobierane s wszystkie rekordy z bazy ksiek.
Listing 4.12. Wywietlanie listy ksiek
public void showBooks(Context context)
{
String tag = "Testowanie dostawcy BookProvider";
Uri uri = BookProviderMetaData.BookTableMetaData.CONTENT_URI;

152 Android 3. Tworzenie aplikacji


Activity a = (Activity)context;
Cursor c = a.managedQuery(uri,
null, //projekcja
null,

//cig znakw wyboru


//argumenty tablicy cigw znakw wyboru
null); //kolejno sortowania
null,

int iname = c.getColumnIndex(


BookProviderMetaData.BookTableMetaData.BOOK_NAME);
int iisbn = c.getColumnIndex(
BookProviderMetaData.BookTableMetaData.BOOK_ISBN);
int iauthor = c.getColumnIndex(
BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR);

//Raportowanie indeksw
Log.d(tag,"name,isbn,author:" + iname + iisbn + iauthor);

//przechodzi po wierszach w oparciu o indeksy


for(c.moveToFirst();!c.isAfterLast();c.moveToNext())
{

//Zbiera wartoci
String
String
String
String

id = c.getString(1);
name = c.getString(iname);
isbn = c.getString(iisbn);
author = c.getString(iauthor);

//Raportuje wiersz lub zapisuje go do dziennika


StringBuffer cbuf = new StringBuffer(id);
cbuf.append(",").append(name);
cbuf.append(",").append(isbn);
cbuf.append(",").append(author);
Log.d(tag, cbuf.toString());
}

//Raportuje liczb przeczytanych wierszy


int numberOfRecords = c.getCount(context);
Log.d(tag,"Ilosc rekordow:" + numberOfRecords);

//Zamyka kursor
//W idealnym przypadku powinno to zosta wykonane w
//bloku finally
c.close();
}

Odnoniki
Poniej przedstawiamy kilka dodatkowych zasobw, ktre mog pomc w zdobywaniu
wiedzy na tematy przedstawione w tym rozdziale:
http://developer.android.com/guide/topics/providers/content-providers.html
znajdziemy tu dokumentacj dotyczc dostawcw treci.

Rozdzia 4 Dostawcy treci

153

http://developer.android.com/reference/android/content/ContentProvider.html
zamieszczono tutaj opis interfejsu API klasy ContentProvider, z ktrego
moemy si dowiedzie o kontraktach tej klasy.
http://developer.android.com/reference/android/content/UriMatcher.html ten adres
URL prowadzi do strony zawierajcej uyteczne informacje o klasie UriMatcher.
http://developer.android.com/reference/android/database/Cursor.html dziki
informacjom tu zawartym nauczymy si odczytywa informacje z dostawcy treci
lub bezporednio z bazy danych.
http://www.sqlite.org/sqlite.html strona domowa silnika SQLite, na ktrej znajdziemy
wiele informacji na jego temat oraz narzdzi pozwalajcych na prac z bazami
danych SQLite.
ftp://ftp.helion.pl/przyklady/and3ta.zip z tego adresu moemy pobra testowy
projekt stworzony specjalnie z myl o niniejszym rozdziale. Waciwy plik znajdziesz
w katalogu o nazwie ProAndroid3_R04_DostawcyTreci.

Podsumowanie
W niniejszym rozdziale opisalimy najwaniejsze kwestie dotyczce identyfikatorw URI, typw
MIME oraz dostawcw treci. Nauczylimy si wykorzystywa baz danych SQLite do tworzenia
dostawcw zwizanych z identyfikatorami URI. Kiedy dane zostan w taki sposb odsonite,
kada aplikacja systemu Android moe je wykorzysta.
Taka zdolno uzyskiwania dostpu do danych oraz ich aktualizowania za pomoc identyfikatorw URI, bez wzgldu na granice procesw, wpisuje si znakomicie w technologi rodowiska
zorientowanego na usugi oraz przetwarzanie rozproszone, co opisalimy w rozdziale 1.
W kolejnym rozdziale omwimy pojcie intencji, ktre poprzez identyfikatory URI danych oraz
identyfikatory URI typu MIME s zwizane z dostawcami treci (jak rwnie z innymi skadnikami Androida). Wiedza zdobyta w tym rozdziale bardzo si przyda do zrozumienia zagadnienia intencji identyfikatory URI danych odgrywaj tu kluczow rol.

154 Android 3. Tworzenie aplikacji

R OZDZIA

5
Intencje

W Androidzie wprowadzono pojcie intencji. Intencje su do przywoywania


skadnikw, do ktrych zaliczamy w Androidzie aktywnoci (skadniki interfejsu
uytkownika), usugi (kod przetwarzany w tle), odbiorcw komunikatw (kod
generujcy odpowiedzi na nadawane wiadomoci) oraz dostawcw treci (kod,
ktry wychwytuje dane).

Podstawowe informacje na temat intencji


Chocia pod pojciem intencji najczciej rozumiemy mechanizm umoliwiajcy
przywoywanie skadnikw, z pojciem tym wie si kilka koncepcji. Intencje
wykorzystuje si do wywoania zewntrznych aplikacji z poziomu innej aplikacji.
Su do wywoywania wewntrznych lub zewntrznych skadnikw danej aplikacji.
Mona dziki nim generowa zdarzenia, na ktre mog odpowiada inne elementy
o podobnym modelu publikowania i subskrybowania. Dziki intencjom mona
rwnie generowa alarmy.
Czym jest intencja? Najkrtsza odpowied jest taka: intencj nazywamy
akcj oraz powizane z ni dane.

Na najprostszym poziomie intencja jest dziaaniem, ktre Android moe przeprowadzi lub przywoa. Przywoywane dziaanie zaley od tego, co jest dla niego
zarejestrowane. Wyobramy sobie nastpujc aktywno:
public class BasicViewActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.some-view);
}
}//klasa eof

156 Android 3. Tworzenie aplikacji


Ukad graficzny some-view musi wskazywa odpowiedni plik w katalogu /res/layout. Android
pozwala nastpnie na zarejestrowanie tej aktywnoci w pliku manifecie, dziki czemu bdzie
wywoywana przez inne aplikacje. Kod rejestracji zosta zaprezentowany poniej:
<activity android:name="BasicViewActivity"
android:label="Testy podstawowego widoku">
<intent-filter>
<action android:name="com.androidbook.intent.action.ShowBasicView"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

W powyszym procesie rejestracji bierze udzia nie tylko aktywno, lecz rwnie dziaanie, dziki
ktremu mona t aktywno wywoa. Projektant aktywnoci przewanie wybiera nazw dla
dziaania i definiuje je jako cz filtra intencji dla tej aktywnoci. W dalszej czci rozdziau
pojawi si wicej informacji na temat filtrw intencji.
Po zdefiniowaniu aktywnoci oraz jej zarejestrowaniu wobec dziaania mona uy intencji do
wywoania klasy BasicViewActivity:
public static invokeMyApplication(Activity parentActivity)
{
String actionName= " com.androidbook.intent.action.ShowBasicView ";
Intent intent = new Intent(actionName);
parentActivity.startActivity(intent);
}

Oglna konwencja nazewnictwa dziaania wyglda nastpujco:


<nazwa-pakietu>intent.action.NAZWA_DZIAANIA.

Po przywoaniu aktywnoci BasicViewActivity posiada ona zdolno do rozpoznania wywoujcej j intencji. Poniej przedstawiono kod tej aktywnoci zmodyfikowany w taki sposb,
aby zostaa wczytana intencja wywoujca:
public class BasicViewActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.some_view);
Intent intent = this.getIntent();
if (intent == null)
{
Log.d("test tag", "Ta aktywnosc jest wywolywana bez uzycia intencji");
}
}
}//klasa eof

Intencje dostpne w Androidzie


Moemy przetestowa intencje w jednej z aplikacji doczonych do Androida. Na stronie
http://developer.android.com/guide/appendix/g-app-intents.html zostaa zamieszczona lista
niektrych dostpnych aplikacji oraz wywoujcych je intencji.

Rozdzia 5 Intencje

157

Naley jednak pamita, e ta lista moe ulega zmianie w zalenoci od wersji


systemu Android.

Wrd dostpnych aplikacji mog si znajdowa nastpujce:


aplikacja przegldarki stron WWW;
aplikacja umoliwiajca poczenie telefoniczne;
aplikacja wywietlajca klawiatur wpisywania numeru, umoliwiajca rczne
wpisanie numeru telefonicznego i wykonanie poczenia za porednictwem interfejsu
uytkownika;
aplikacja przedstawiajca map wiata oraz wskazane wsprzdne dugoci
i szerokoci geograficznej;
aplikacja zawierajca szczegowe mapy i wywietlajca zdjcia ulic z serwisu Google.
Na listingu 5.1 zaprezentowano kod pozwalajcy na przywoanie powyszych aplikacji za pomoc ich opublikowanych intencji.
Listing 5.1. Korzystanie z aplikacji wbudowanych w Androida
public class IntentsUtils
{
public static void invokeWebBrowser(Activity activity)
{
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.google.com"));
activity.startActivity(intent);
}
public static void invokeWebSearch(Activity activity)
{
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.setData(Uri.parse("http://www.google.com"));
activity.startActivity(intent);
}
public static void dial(Activity activity)
{
Intent intent = new Intent(Intent.ACTION_DIAL);
activity.startActivity(intent);
}
public static void call(Activity activity)
{
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:555-555-5555"));
activity.startActivity(intent);
}
public static void showMapAtLatLong(Activity activity)
{
Intent intent = new Intent(Intent.ACTION_VIEW);
//geo:lat,long?z=zoomlevel&q=question-string
intent.setData(Uri.parse("geo:0,0?z=4&q=business+near+city"));
activity.startActivity(intent);
}

158 Android 3. Tworzenie aplikacji


public static void tryOneOfThese(Activity activity)
{
IntentsUtils.invokeWebBrowser(activity);
}
}

Taki kod jest przydatny w przypadku prostej aktywnoci, ktra zawiera element menu przywoujcy metod tryOneOfThese(activity). Utworzenie prostego menu jest banalne (listing 5.2).
Listing 5.2. rodowisko testowe suce do zbudowania prostego menu
public class MainActivity extends Activity
{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText("Witaj, Androidzie. Przywitaj si");
setContentView(tv);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
int base=Menu.FIRST; // warto wynosi 1
MenuItem item1 = menu.add(base,base,base,"Test");
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == 1) {
IntentUtils.tryOneOfThese(this);
}
else {
return super.onOptionsItemSelected(item);
}
return true;
}
}

W rozdziale 2. zostay zawarte informacje dotyczce utworzenia z tych plikw projektu


Android, skompilowania go i uruchomienia. Na pocztku rozdziau 7. Praca z menu,
pokazalimy natomiast kod pozwalajcy na utworzenie menu. Mona rwnie pobra
przykadowy kod, napisany specjalnie z myl o tym rozdziale, do ktrego odnonik
znajduje si na kocu tego rozdziau. Podstawowa aktywno zawarta w tym kodzie
moe si nieznacznie rni od opisywanej w ksice, jednak sama koncepcja nie ulega
zmianie. W tym przykadowym kodzie wczytujemy rwnie menu z pliku XML.

Rozdzia 5 Intencje

159

Przegld struktury intencji


Kolejnym wietnym sposobem zrozumienia koncepcji intencji jest poznanie skadnikw przez
ni przechowywanych. Intencja zawiera dziaanie, dane (reprezentowane przez identyfikator
URI), map typu klucz warto dla dodatkowych danych oraz jawn nazw klasy (zwan
nazw skadnika). Omwimy kolejno kady z wymienionych elementw.
Jeeli intencja zawiera w sobie nazw skadnika, jest ona okrelana jako intencja jawna.
Jeli natomiast nie przechowuje nazwy skadnika, ale jest zalena od innych skadnikw,
na przykad dziaania lub danych, nazywana jest intencj niejawn. W trakcie czytania
rozdziau zauwaymy, e pomidzy tymi dwoma typami istniej drobne rnice.

Intencje a identyfikatory danych URI


Do tej pory zajmowalimy si najprostszymi intencjami, w ktrych wymagana jest jedynie
nazwa dziaania. Aktywno ACTION_DIAL ukazana na listingu 5.1 jest wanie jedn z nich:
eby wywoa klawiatur do wpisywania numeru, wystarczya wycznie nazwa dziaania:
public static void dial(Activity activity)
{
Intent intent = new Intent(Intent.ACTION_DIAL);
activity.startActivity(intent);
}

Inaczej ma si sprawa z intencj ACTION_CALL, suc do wykonania poczenia z wybranym


numerem. Intencja ta wymaga dodatkowego parametru Data (ponownie odnosimy si do listingu 5.1). Wskazuje on identyfikator URI, ktry z kolei przekierowuje do numeru telefonu:
public static void call(Activity activity)
{
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:555-555-5555"));
activity.startActivity(intent);
}

W tej intencji czci danych zwizan z dziaaniem jest cig znakw lub staa typu string,
ktre przewanie zawieraj przedrostek z nazw pakietu Java.
Tej czci danych w intencji nie stanowi rzeczywiste dane, tylko wskanik do tych danych,
ktrym jest cig znakw bdcy identyfikatorem URI. Identyfikator URI intencji moe zawiera
argumenty, ktre mona uznawa za dane, na przykad adres URL.
Format tego identyfikatora moe by inny dla kadej aktywnoci wywoywanej przez to dziaanie.
W tym przypadku dziaanie CALL decyduje o tym, jakiego identyfikatora URI naley si spodziewa. Z tego identyfikatora uzyskiwany jest numer telefonu.
Wywoywana aktywno moe rwnie wykorzysta identyfikator URI jako wskanik
do rda danych, wydoby te dane ze rda i wykorzysta je. Przydaje si to w przypadku
plikw multimedialnych, na przykad muzyki, wideo i obrazw.

160 Android 3. Tworzenie aplikacji

Dziaania oglne
Dziaania Intent.ACTION_CALL oraz Intent.ACTION_DIAL mog doprowadzi do bdnego
wniosku, e istnieje wzajemnie jednoznaczny zwizek pomidzy dziaaniem oraz wynikiem tego
dziaania. eby udowodni, e jest inaczej, rozpatrzmy kontrprzykad fragmentu IntentUtils
umieszczonego na listingu 5.1:
public static void invokeWebBrowser(Activity activity)
{
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.google.com"));
activity.startActivity(intent);
}

Zwrmy uwag, e dziaanie jest okrelone jedynie jako ACTION_VIEW. Skd wiadomo, ktr
aktywno naley przywoa na podstawie takiej oglnej nazwy dziaania? W takich przypadkach Android bada nie tylko nazw oglnego dziaania, ale take charakter identyfikatora URI.
Analizuje struktur identyfikatora URI, w tym przykadzie http, a nastpnie sprawdza kad
zarejestrowan aktywno pod ktem rozpoznawania tej struktury. Spord aktywnoci, ktre
mog odpowiedzie na ten identyfikator, system wyszukuje tak, ktra moe odpowiedzie na
intencj VIEW, i wanie ona zostaje wywoana. eby ten mechanizm mg dziaa, aktywno
przegldarki powinna mie zarejestrowan intencj VIEW wobec schematu danych http. W pliku
manifecie taka deklaracja intencji moe wyglda nastpujco:
<activity...>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http"/>
<data android:scheme="https"/>
</intent-filter>
</activity>

Wicej informacji na temat opcji danych mona zdoby, przegldajc definicj XML elementu data filtru intencji na stronie http://developer.android.com/guide/topics/manifest/
data-element.html. Elementy lub atrybuty wza data s nastpujce:
host
mimeType
path
pathPattern
pathPrefix
port
scheme

Atrybut mimeType jest powszechnie uywany. Na przykad przedstawiony poniej filtr intencji dla
aktywnoci wywietlajcej list notatek wskazuje typ MIME jako katalog tych notatek:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
</intent-filter>

Deklaracj tego filtru intencji mona odczyta jako: Przywoaj t aktywno, aby przejrze list
notatek.

Rozdzia 5 Intencje

161

Z drugiej strony ekran wywietlajcy jedn notatk posiada filtr intencji zadeklarowany
za pomoc typu MIME wskazujcego pojedynczy element:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
</intent-filter>

Deklaracj tego filtru intencji mona odczyta jako: Przywoaj t aktywno, aby obejrze
pojedyncz notatk.

Korzystanie z dodatkowych informacji


Poza gwnymi atrybutami dziaa oraz danych intencja posiada rwnie atrybuty dodatkowe
(ang. extra). Atrybut dodatkowy zapewnia opcjonalne informacje skadnikowi odbierajcemu
intencj. Dane dodatkowe przybieraj posta par klucz warto: nazwa klucza zazwyczaj rozpoczyna si od nazwy pakietu, a warto powinna by dowolnym, podstawowym typem danych
lub dowolnym obiektem byleby tylko zaimplementowano interfejs android.os.Parcelable.
Takie dodatkowe informacje s reprezentowane przez klas Androida android.os.Bundle.
Poniej zaprezentowano dwie metody w klasie
atrybutw obiektu klasy Bundle:

Intent,

zapewniajce dostp do dodatkowych

// Pobranie klasy Bundle z intencji


Bundle extraBundle = intent.getExtras();

// Umieszczenie klasy Bundle w intencji


Bundle anotherBundle = new Bundle();

// Wypenienie klasy Bundle parami klucz warto


...

// Ustanowienie klasy Bundle w intencji


intent.putExtras(anotherBundle);

Funkcja getExtras jest zrozumiaa: przekazuje obiekt klasy Bundle zawarty w intencji. putExtras
sprawdza, czy intencja zawiera aktualnie obiekt Bundle. Jeeli tak jest w istocie, funkcja ta przenosi dodatkowe klucze i wartoci z nowego obiektu klasy Bundle do ju istniejcego. W przeciwnym razie funkcja putExtras utworzy taki obiekt i skopiuje pary klucz warto z tego
obiektu do istniejcego ju wystpienia klasy Bundle.
Funkcja putExtras tworzy repliki przychodzcego obiektu Bundle, a nie odniesienia
do nich. Zatem podczas pniejszej modyfikacji przychodzcej klasy Bundle nie
trzeba zmienia pakietu znajdujcego si w intencji.

Istnieje wiele metod dodawania podstawowych typw do obiektu klasy Bundle. Poniej zaprezentowano kilka metod dodajcych proste typy danych do dodatkowych danych:
putExtra(String
putExtra(String
putExtra(String
putExtra(String

name,
name,
name,
name,

boolean value);
int value);
double value);
String value);

162 Android 3. Tworzenie aplikacji


A tu s nieco trudniejsze dodatkowe dane:
// Obsuga prostej tablicy
putExtra(String name, int[]values);
putExtra(String name, float[]values);

// Obiekty serializowalne
putExtra(String name, Serializable value);

// Obsuga parcelowania
putExtra(String name, Parcelable value);

// Dodaje kolejny obiekt klasy Bundle do danego klucza


// Obiekty klasy Bundle w obiektach klasy Bundle
putExtra(String name, Bundle value);

// Dodaje obiekty Bundle z innej intencji


// Kopie obiektw klasy Bundle
putExtra(String name, Intent anotherIntent);

// Obsuga jawnej listy tablicowej


putIntegerArrayListExtra(String name, ArrayList arrayList);
putParcelableArrayListExtra(String name, ArrayList arrayList);
putStringArrayListExtra(String name, ArrayList arrayList);

Po stronie odbiorcy rwnowane metody pobierajce (typu get) rozpoczynaj dziaanie od odczytywania dodatkowych danych na podstawie kluczowych nazw.
Klasa Intent definiuje dodatkowe, kluczowe cigi znakw, zwizane z konkretnymi dziaaniami.
Pod adresem http://developer.android.com/reference/android/content/Intent.html#EXTRA_
ALARM_COUNT mona si zapozna z du liczb tych zawierajcych dodatkowe informacje
staych.
Przyjrzyjmy si dostpnym pod powyszym adresem URL przykadom dodatkowych danych,
zwizanych z wysyaniem wiadomoci e-mail:
EXTRA_EMAIL. Klucz ten suy do przechowywania grupy adresw e-mail. Jego warto
to android.intent.extra.EMAIL. Powinien wskazywa tablic cigw znakowych,
zawierajc wpisane adresy e-mail.
EXTRA_SUBJECT. Dziki temu kluczowi moliwe jest przechowywanie nazwy tematu
wiadomoci e-mail. Wartoci tego klucza jest android.intent.extra.SUBJECT.
Powinien wskazywa cig znakw stanowicy temat wiadomoci.

Stosowanie skadnikw
do bezporedniego przywoywania aktywnoci
Przeledzilimy kilka sposobw uruchamiania aktywnoci za pomoc intencji. Pokazalimy, jak
jawne dziaanie uruchamia aktywno oraz jak mona tego dokona, stosujc oglne dziaanie
za pomoc identyfikatora URI. Istnieje rwnie bardziej bezporedni sposb uruchomienia
aktywnoci: mona okreli jej klas ComponentName, stanowic abstrakcj powizan z nazw
pakietu danego obiektu oraz nazw klasy. Istnieje wiele metod klasy Intent pozwalajcych
na okrelenie skadnika:

Rozdzia 5 Intencje

163

setComponent(ComponentName name);
setClassName(String packageName, String classNameInThatPackage);
setClassName(Context context, String classNameInThatContext);
setClass(Context context, Class classObjectInThatContext);

Ostatecznie wszystkie te metody stanowi skrty do wywoywania jednej metody:


setComponent(ComponentName name);

Obiekt klasy ComponentName czy ze sob nazw pakietu oraz nazw klasy. Na przykad poniszy kod wywouje dostpn w emulatorze aktywno Contacts:
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.android.contacts"
,"com.android.contacts.DialContactsEntryActivity");
startActivity(intent)

Zauwamy, e nazwy pakietu oraz klasy s w peni kwalifikowane i zostaj uyte do skonstruowania obiektu klasy ComponentName, zanim przejd do klasy Intent.
Mona rwnie wykorzysta nazw klasy bezporednio, bez tworzenia obiektu ComponentName.
Rozwamy ponownie fragment kodu BasicViewActivity:
public class BasicViewActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.some-view);
}
}//eof-class

Biorc go pod uwag, mona wykorzysta poniszy kod do uruchomienia aktywnoci:


Intent directIntent = new Intent(activity, BasicViewActivity.class);
activity.start(directIntent);

Jeeli jednak kady rodzaj intencji ma uruchamia aktywno, naley j zarejestrowa w pliku
AndroidManifest.xml w nastpujcy sposb:
<activity android:name=".BasicViewActivity"
android:label="Aktywno testowa">

Do bezporedniego wywoania aktywnoci za pomoc nazwy klasy lub nazwy skadnika nie
s potrzebne filtry intencji. Jak ju wczeniej wyjanilimy, jest to tak zwana intencja jawna.
Poniewa taka intencja definiuje do wywoania w peni kwalifikowany skadnik systemu
Android, podczas przywoywania tego skadnika ignorowane s pozostae elementy intencji.

Kategorie intencji
Aktywnoci mona dzieli na kategorie, eby dao si je wyszukiwa na podstawie nazwy kategorii.
Na przykad podczas rozruchu system Android szuka aktywnoci znajdujcych si w kategorii
nazwanej CATEGORY_LAUNCHER. Pobiera nastpnie nazwy oraz ikony tych aktywnoci i umieszcza
je na ekranie startowym.

164 Android 3. Tworzenie aplikacji


Kolejny przykad: Android wyszukuje aktywno oznaczon etykiet CATEGORY_HOME, eby wywietli ekran startowy podczas uruchamiania. Podobnie etykieta CATEGORY_GADGET okrela aktywnoci, ktre nadaj si do osadzenia lub wykorzystania wewntrz innej aktywnoci.
Format cigu znakw dla kategorii takiej jak CATEGORY_LAUNCHER jest ustanowiony zgodnie
z konwencj definicji category:
android.intent.category.LAUNCHER

Znajomo cigw znakw w definicjach category bdzie potrzebna, poniewa kategorie s


rejestrowane przez aktywnoci w pliku AndroidManifest.xml jako cz ich definicji filtru aktywnoci. Poniej umiecilimy przykad:
<activity android:name=".HelloWorldActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

Aktywnoci mog posiada pewne waciwoci powodujce ich ograniczanie lub


uruchamianie, na przykad umoliwiajce osadzenie ich w nadrzdnej aktywnoci.
Takie waciwoci s definiowane poprzez kategorie.

Przejrzyjmy szybko niektre predefiniowane kategorie oraz dowiedzmy si, do czego s


wykorzystywane (tabela 5.1).
Wicej szczegw na temat wymienionych kategorii aktywnoci mona znale pod nastpujcym adresem, powiconym klasie Intent:
http://developer.android.com/reference/android/content/Intent.html#CATEGORY_
ALTERNATIVE
Kiedy intencja suy do uruchomienia aktywnoci, rodzaj tej aktywnoci mona wybra poprzez wskazanie kategorii. Ewentualnie mona wyszuka aktywnoci pasujce do okrelonej kategorii. Poniej zamiecilimy przykad uzyskiwania zestawu gwnych aktywnoci,
odpowiadajcych kategorii CATEGORY_LAUNCHER:
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PackageManager pm = getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);

PackageManager

jest kluczow klas, pozwalajc odkrywa aktywnoci dopasowane do okrelonych intencji bez koniecznoci ich wywoywania. Interfejs ResolveInfo pozwala na wymienianie otrzymywanych aktywnoci oraz wywoywanie ich w miar potrzeby. Poniej prezentujemy rozwinicie wczeniejszego kodu, w ktrym sprawdzana jest lista dostpnych aktywnoci
oraz wywoywana jest jedna z nich, jeli nazwy bd si zgadzay. W celach testowych wykorzystalimy wasn nazw.

for(ResolveInfo ri: list)


{

//ri.activityInfo.
Log.d("test",ri.toString());
String packagename = ri.activityInfo.packageName;

Rozdzia 5 Intencje

165

Tabela 5.1. Kategorie aktywnoci oraz ich omwienie


Nazwa kategorii

Opis

CATEGORY_DEFAULT

Aktywno mona zadeklarowa jako domyln, aby mc j wywoa


poprzez intencj niejawn. Jeeli nie zdefiniujemy tej kategorii wobec
aktywnoci, trzeba bdzie j za kadym razem jawnie wywoywa
za pomoc nazwy jej klasy. Dlatego wanie znajdujemy
specyfikacj domylnej kategorii w aktywnociach, ktre zostaj
wywoane za pomoc nazw oglnych dziaa lub innych dziaa.

CATEGORY_BROWSABLE

Aktywno mona zadeklarowa jako odpowiedni do przegldania,


gwarantujc w ten sposb, e nie bdzie po uruchomieniu
w przegldarce naruszaa jej regu bezpieczestwa.

CATEGORY_TAB

Aktywno tego rodzaju jest osadzalna w oznaczonej, nadrzdnej


aktywnoci.

CATEGORY_ALTERNATIVE

Aktywno moe zosta zadeklarowana jako alternatywna dla


pewnych przegldanych typw danych. Elementy te standardowo s
pokazywane jako cz menu opcji podczas ogldania dokumentu.
Na przykad widok wydruku jest uznawany za alternatywny
wobec widoku normalnego.

CATEGORY_SELECTED
_ALTERNATIVE

Aktywno moe zosta zadeklarowana jako alternatywna dla


pewnych typw danych. Przypomina to list dostpnych edytorw
do przegldania dokumentu tekstowego lub napisanego w jzyku
HTML.

CATEGORY_LAUNCHER

Przydzielenie tej kategorii do aktywnoci sprawi, e aktywno


ta zostanie wywietlona na ekranie startowym.

CATEGORY_HOME

Aktywno tego typu bdzie ekranem startowym. Zazwyczaj


powinna istnie tylko jedna aktywno tego rodzaju. W przypadku
obecnoci wikszej ich liczby system zada wybrania jednej z nich.

CATEGORY_PREFERENCE

Zaznaczone s w ten sposb aktywnoci zapewniaj obsug


ustawie, zatem bd one umieszczone na ekranie preferencji.

CATEGORY_GADGET

Aktywno tego typu jest osadzalna w aktywnoci nadrzdnej.

CATEGORY_TEST

Jest to aktywno testowa.

CATEGORY_EMBED

Kategoria ta zostaa zastpiona przez kategori CATEGORY_GADGET,


pozostawiono j jednak dla zachowania zgodnoci z wczeniejszymi
wersjami oprogramowania.

String classname = ri.activityInfo.name;


Log.d("test", packagename + ":" + classname);
if (classname.equals("com.ai.androidbook.resources.TestActivity"))
{
Intent ni = new Intent();
ni.setClassName(packagename,classname);
activity.startActivity(ni);
}
}

166 Android 3. Tworzenie aplikacji


Moemy rwnie rozpocz aktywno opart wycznie na takiej kategorii, jak CATEGORY_
LAUNCHER:
public static void invokeAMainApp(Activity activity)
{
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
activity.startActivity(mainIntent);
}

Do intencji bdzie pasowao kilka aktywnoci. Zatem ktr z nich wybierze Android? System
rozwizuje ten problem poprzez wywietlenie okna dialogowego Complete action using (dokocz
dziaanie za pomoc), w ktrym s widoczne wszystkie dopuszczalne aktywnoci. Naley wybra
jedn z nich.
A to inny przykad zastosowania intencji sucej do wywoywania ekranu startowego:
// Przechodzi do ekranu startowego
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_HOME);
startActivity(mainIntent);

Jeeli naley uy ekranu startowego innego ni domylny, mona napisa swj wasny i zadeklarowa aktywno do kategorii HOME. W tym przypadku powyszy kod da moliwo otwarcia
swojego ekranu startowego, poniewa zarejestrowano kilka jego aktywnoci:
// Podmienia ekran startowy na utworzony przez programist
<intent-filter>
<action android:value="android.intent.action.MAIN" />
<category android:value="android.intent.category.HOME"/>
<category android:value="android.intent.category.DEFAULT" />
</intent-filter>

Reguy przydzielania intencji do ich skadnikw


Poznalimy ju wiele aspektw dotyczcych intencji. Gwoli przypomnienia, omwilimy dziaania, identyfikatory URI danych, dane dodatkowe oraz kategorie. Biorc pod uwag te aspekty,
Android stosuje wiele rozwiza w procesie przydzielania intencji do docelowych aktywnoci
za pomoc filtrw intencji.
Podstawowe znaczenie ma przydzielenie nazwy skadnika do intencji. Jeeli tak si stanie, mamy
do czynienia z jawn intencj. W takim przypadku znaczenie ma wycznie nazwa skadnika;
kady inny aspekt lub atrybut intencji zostaje zignorowany. W przypadku niejawnej intencji
istnieje wiele rnych regu okrelajcych obiekty docelowe.
Podstawowa regua jest taka, e kade przychodzce dziaanie intencji, jej kategoria oraz
charakterystyka danych musz pasowa do odpowiednich obiektw okrelonych w filtrze
intencji (lub we waciwy sposb prezentowa te obiekty). W przeciwiestwie do intencji,
filtr intencji moe definiowa wiele dziaa, kategorii oraz charakterystyk danych. A zatem
jeden filtr moe spenia zaoenia wielu intencji, co oznacza, e jedna aktywno moe stanowi odpowied na rne intencje. Jednak znaczenie terminu dopasowanie jest rne dla
poszczeglnych rodzajw elementw: dziaa, atrybutw danych oraz kategorii. Przyjrzyjmy
si wic kryteriom dopasowania kadego elementu niejawnej intencji.

Rozdzia 5 Intencje

167

Dziaanie
Jeeli intencja zawiera w sobie dziaanie, filtr intencji musi posiada je na swojej licie dziaa
lub nie posiada adnej listy dziaa. Jeli zatem filtr intencji nie zdefiniuje adnego dziaania,
bdzie on dopasowany do dowolnego przychodzcego dziaania.
Jeeli w filtrze intencji zostanie zdefiniowane przynajmniej jedno dziaanie, musi ono odpowiada przychodzcemu dziaaniu intencji.

Dane
Jeeli w filtrze intencji nie zostan zdefiniowane charakterystyki danych, nie bdzie on dopasowany do przychodzcej intencji, przenoszcej jakiekolwiek dane lub ich atrybuty. Oznacza
to, e bdzie wyszukiwa wycznie intencje niepowizane z danymi.
Brak danych i brak dziaania (w filtrze) maj zupenie odwrotne dziaanie. Jeeli w filtrze nie
zostanie zdefiniowane adne dziaanie, kady obiekt bdzie dopasowany. Jeli w filtrze nie zostan umieszczone dane, kady bit informacji z intencji bdzie niedopasowany.

Typ danych
Aby typ danych przychodzcej intencji by dopasowany, musi si znajdowa na licie typw
danych okrelonych w filtrze intencji. Typ danych zamieszczony w intencji musi by rwnie
obecny w filtrze intencji.
Typ danych z przychodzcej intencji moe zosta okrelony na jeden z dwch sposobw. Po
pierwsze, jeli identyfikator URI danych jest identyfikatorem URI pliku lub treci, dostawca
treci lub sam system automatycznie rozpoznaj typ danych. Drugie rozwizanie polega na
sprawdzeniu jawnego typu danych intencji. Aby to si powiodo, przychodzca intencja nie
moe posiada ustanowionego identyfikatora URI danych, poniewa jest on nadawany automatycznie w momencie wywoania metody setType wobec intencji.
Jako cz specyfikacji typw MIME Android pozwala rwnie na wprowadzenie symbolu
gwiazdki (*), zastpujcego wszystkie moliwe podtypy.
Ponadto typ danych rozrnia wielko liter.

Schemat danych
Aby schemat danych by dopasowany, musi si znajdowa na licie schematw w filtrze intencji oraz odpowiada schematowi znajdujcemu si w przychodzcej intencji. Innymi sowy,
przychodzcy schemat danych musi by odzwierciedlony w filtrze intencji.
W przychodzcej intencji schemat stanowi pierwszy czon identyfikatora URI danych. W przypadku intencji nie ma adnego sposobu na ustanowienie schematu. Wywodzi si on wprost
z identyfikatora URI danych intencji i wyglda mniej wicej tak: http://www.jakasstrona.com/
jakassciezka.
Jeeli schemat danych z przychodzcego identyfikatora URI intencji rozpoczyna si od czonw
content: lub file:, jest on dopasowany bez wzgldu na schemat filtra, domen czy ciek.
Zgodnie z dokumentacj zestawu SDK wynika to z faktu, i kady skadnik powinien mc odczytywa dane z tych dwch rodzajw adresw URL, ktre w swej istocie s lokalne. Inaczej
mwic, od wszystkich skadnikw oczekuje si obsugi tych dwch typw adresw URL.
Schemat rwnie rozrnia wielko liter.

168 Android 3. Tworzenie aplikacji

Uprawnienia do danych
Jeeli w filtrze nie zostan uwzgldnione adne uprawnienia, wszelkie uprawnienia (lub nazwy
domenowe) przychodzcych identyfikatorw URI bd dopasowane. Jeeli w filtrze zdefiniowano jakie uprawnienie, na przykad www.jakasstrona.com, to do danego identyfikatora URI
intencji powinien pasowa jeden schemat oraz jedno uprawnienie.
Jeli na przykad uprawnieniem okrelonym w filtrze intencji jest www.jakasstrona.com,
a schematem jest https, adres http://www.jakasstrona.com/jakassciezka bdzie niedopasowany, poniewa http nie jest w tym przypadku obsugiwanym formatem.
Uprawnienie take rozrnia wielko liter.

cieka danych
Brak cieek w filtrze intencji oznacza dopasowanie do kadej cieki znajdujcej si w przychodzcym identyfikatorze URI. Jeeli w filtrze okrelono ciek, na przykad jakassciezka,
przychodzcemu identyfikatorowi URI danych w intencji powinien odpowiada jeden schemat,
jedno uprawnienie i jedna cieka danych.
Innymi sowy, schemat, uprawnienie i cieka wsppracuj ze sob w celu sprawdzenia poprawnoci przychodzcego identyfikatora URI, na przykad http://www.jakasstrona.com/
jakassciezka. Zatem elementy path, authority i scheme nie dziaaj oddzielnie, lecz
wsppracuj ze sob.
Podobnie jak we wczeniejszych przypadkach, cieka rozrnia wielko liter.

Kategorie intencji
Kada kategoria zawarta w przychodzcej intencji musi by wymieniona na licie kategorii
filtru intencji. Wiksza liczba kategorii w filtrze nie jest niczym zym. Jeeli filtr nie zawiera
adnych kategorii, dopasowana bdzie wycznie intencja nieposiadajca adnej zadeklarowanej kategorii.
Istnieje jednak pewne zastrzeenie. Android uwzgldnia wszystkie niejawne intencje przekazane do metody startActivity(), tak jakby posiaday przynajmniej jedn kategori:
android.intent.category.DEFAULT. Kod tworzcy t metod bdzie wyszukiwa jedynie te
aktywnoci, dla ktrych zdefiniowano kategori DEFAULT, ale tylko wtedy, gdy przychodzca
intencja jest niejawna. Zatem kada aktywno, ktra bdzie wywoana za pomoc niejawnej
intencji, musi zawiera domyln kategori w filtrze intencji.
Nawet jeli aktywno nie posiada domylnej kategorii zadeklarowanej w filtrze intencji, w przypadku gdy znamy jej jawne nazwy skadnikw, bdziemy mogli j uruchomi tak jak program
wywoujcy. Jeeli samodzielnie wyszukujemy w sposb jawny pasujce intencje, bez posiadania
domylnej kategorii jako kryterium wyszukiwania, to w ten sposb uruchomimy aktywnoci.
W tym sensie kategoria DEFAULT jest artefaktem implementacyjnym metody startActivity,
a nie naturalnym skadnikiem filtra intencji.
Istnieje jeszcze dodatkowy problem. System Android uznaje, i domylna kategoria jest niepotrzebna, w przypadku gdy aktywno ma by uruchamiana jedynie z poziomu ekranw programw wywoujcych. Zatem w filtrach intencji takich aktywnoci zazwyczaj umieszczane
s wycznie kategorie MAIN i LAUNCHER. Jednak kategoria DEFAULT moe rwnie zosta
opcjonalnie zdefiniowana dla tych aktywnoci.

Rozdzia 5 Intencje

169

Dziaanie ACTION_PICK
Jak na razie zajmowalimy si intencjami lub dziaaniami, ktre w gwnej mierze wywoyway
inn aktywno bez uzyskiwania wynikw. Przyjrzymy si nieco bardziej zaawansowanemu dziaaniu, w ktrym po jego wywoaniu otrzymujemy warto. Takim dziaaniem jest ACTION_PICK.
Dziaanie ACTION_PICK polega na uruchomieniu aktywnoci, ktra wywietla list elementw.
Aktywno powinna nastpnie umoliwi uytkownikowi wybranie jednego z elementw. Gdy
tak si stanie, aktywno przekae programowi dajcemu identyfikator URI wybranego elementu. Dziki temu mona wielokrotnie korzysta z funkcjonalnoci interfejsu uytkownika
do wybierania elementw okrelonego typu.
Naley wskaza zbir wybieranych elementw za pomoc typu MIME, ktry okrela kursor treci w Androidzie. Typ MIME takiego identyfikatora URI powinien wyglda mniej wicej
nastpujco:
vnd.android.cursor.dir/vnd.google.note

Aktywno powinna uzyska dane z tego dostawcy treci na podstawie identyfikatora URI.
Z tego wanie powodu dane powinny by umieszczane w dostawcach treci wszdzie, gdzie jest
to moliwe.
W adnym dziaaniu, ktre przekazuje dane w ten sposb, nie mona zastosowa metody
startActivity(), poniewa nie przekazuje ona adnego wyniku. Wynika to z faktu, e ta metoda
otwiera now aktywno, ktra przyjmuje posta modalnego okna dialogowego w oddzielnym
wtku, i pozostawia gwny wtek dla innych zdarze. Innymi sowy, metoda startActivity()
jest wywoaniem asynchronicznym, nieposiadajcym adnych wywoa zwrotnych, ktre
wskazywayby na to, co si stao z przywoan aktywnoci. Jeli przewidujemy otrzymywanie danych wynikowych, mona wykorzysta wariant metody startActivity(), nazwany
startActivityForResult(), ktry zawiera wywoanie zwrotne.
Przyjrzyjmy si sygnaturze metody startActivityForResult() klasy Activity:
public void startActivityForResult(Intent intent, int requestCode)

Metoda ta uruchamia aktywno, ktra ma wywietli wynik. Po zakoczeniu aktywnoci jej rdowa metoda onActivityResult() zostanie wywoana wraz z danym argumentem requestCode.
Sygnatur tej metody wywoywania zwrotnego jest:
protected void onActivityResult(int requestCode, int resultCode, Intent data)

Parametr requestCode reprezentuje dane przekazane metodzie startActivityForResult().


Jego wartociami mog by RESULT_OK, RESULT_CANCELLED lub kod niestandardowy. Nazwy kodw niestandardowych powinny si zaczyna od czonu RESULT_FIRST_USER. Parametr Intent
zawiera dodatkowe dane, ktre wywoana aktywno powinna przekaza jako wynik. W przypadku dziaania ACTION_PICK otrzymane w intencji dane wskazuj identyfikator URI jednego
elementu.
Listing 5.3 przedstawia wywoanie aktywnoci przekazujcej wynik.
Kod z listingu 5.3 opiera si na zaoeniu, e zainstalowano przykadowy projekt Notepad,
dostpny w zestawie Android SDK. Na kocu rozdziau umiecilimy odnonik
do wskazwek dotyczcych pobrania tej aplikacji. Bdzie to pomocne dla osb,
ktre jeszcze nie posiadaj zestawu SDK.

170 Android 3. Tworzenie aplikacji


Listing 5.3. Przekazywanie danych wynikowych po wywoaniu dziaania
public class SomeActivity extends Activity
{
.....
.....
public static void invokePick(Activity activity)
{
Intent pickIntent = new Intent(Intent.ACTION_PICK);
int requestCode = 1;
pickIntent.setData(Uri.parse(
"content://com.google.provider.NotePad/notes"));
activity.startActivityForResult(pickIntent, requestCode);
}
protected void onActivityResult(int requestCode
,int resultCode
,Intent outputIntent)
{

// Fragment ten suy do poinformowania nadrzdnej klasy (Activity)


// o fakcie, e aktywno zakoczya dziaanie oraz e klasa bazowa
// moe przeprowadzi niezbdne porzdki
super.onActivityResult(requestCode, resultCode, outputIntent);
parseResult(this, requestCode, resultCode, outputIntent);
}
public static void parseResult(Activity activity
, int requestCode
, int resultCode
, Intent outputIntent)
{
if (requestCode != 1)
{
Log.d("Test", "To wywoa kto inny. Nie my.");
return;
}
if (resultCode != Activity.RESULT_OK)
{
Log.d(Test, "Kod wyniku nie zgadza si:" + resultCode);
return;
}
Log.d("Test", "Kod wyniku zgadza si:" + resultCode);
Uri selectedUri = outputIntent.getData();
Log.d("Test", "Wynikowy identyfikator uri:" + selectedUri.toString());

// Wywietla notatk
outputIntent.setAction(Intent.ACTION_VIEW);
startActivity(outputIntent);
}

RESULT_OK, RESULT_CANCELED oraz RESULT_FIRST_USER


Activity. Ich wartoci numeryczne s nastpujce:

Stae

RESULT_OK = -1;
RESULT_CANCELED = 0;
RESULT_FIRST_USER = 1;

s zdefiniowane w klasie

Rozdzia 5 Intencje

171

Aby funkcja PICK dziaaa, element, ktry jej odpowiada, powinien zawiera kod jawnie odpowiadajcy wymaganiom intencji PICK. Zobaczymy, w jaki sposb cel ten osignito w przykadowej aplikacji Notepad. Po wybraniu elementu z listy system sprawdza, czy intencja,
ktra wywoaa aktywno, jest intencj PICK. Jeli tak jest, w nowej intencji ustanawia si
identyfikator URI danych, przekazywany poprzez metod setResult():
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);
String action = getIntent().getAction();
if (Intent.ACTION_PICK.equals(action) ||
Intent.ACTION_GET_CONTENT.equals(action))
{

// Program wywoujcy czeka na zwrcenie notatki wybranej przez


// uytkownika. Jedna z nich zostaa kliknita, wic bdzie teraz zwrcona.
setResult(RESULT_OK, new Intent().setData(uri));
} else {

// Uruchamia aktywno odpowiedzialn za przegldanie/edycj biecego elementu.


startActivity(new Intent(Intent.ACTION_EDIT, uri));
}
}

Dziaanie ACTION_GET_CONTENT
Dziaanie ACTION_GET_CONTENT jest podobne do dziaania ACTION_PICK. W przypadku tego
drugiego okrela si identyfikator URI, ktry wskazuje zbir elementw, na przykad kolekcj
notatek. Dziaanie ma pobra jedn z notatek i przekaza j programowi wywoujcemu.
W przypadku dziaania ACTION_GET_CONTENT potrzebny jest element okrelonego typu MIME.
Android przeszukuje wtedy zarwno aktywnoci zdolne do utworzenia takich elementw, jak
i aktywnoci, w ktrych mona wybiera elementy speniajce warunek waciwego typu MIME.
Za pomoc dziaania ACTION_GET_CONTENT mona wybra notatk ze zbioru notatek obsugiwanych przez aplikacj Notepad w nastpujcy sposb:
public static void invokeGetContent(Activity activity)
{
Intent pickIntent = new Intent(Intent.ACTION_GET_CONTENT);
int requestCode = 2;
pickIntent.setType("vnd.android.cursor.item/vnd.google.note");
activity.startActivityForResult(pickIntent, requestCode);
}

Zwrmy uwag na sposb, w jaki typ intencji zostaje dopasowany do typu MIME pojedynczej notatki. Porwnamy to z kodem ACTION_PICK, w ktrym na wejciu jest identyfikator
URI danych. Kod znajduje si poniej:
public static void invokePick(Activity activity)
{
Intent pickIntent = new Intent(Intent.ACTION_PICK);
int requestCode = 1;
pickIntent.setData(Uri.parse(
"content://com.google.provider.NotePad/notes"));
activity.startActivityForResult(pickIntent, requestCode);
}

172 Android 3. Tworzenie aplikacji


eby aktywno zareagowaa na dziaanie ACTION_GET_CONTENT, musi ona posiada zarejestrowany filtr intencji wskazujcy na to, e aktywno ta bdzie zdolna obsuy element o danym
typie MIME. Aplikacja Notepad spenia ten wymg w nastpujcy sposb:
<activity android:name="NotesList" android:label="@string/title_notes_list">
...
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
</intent-filter>
...
</activity>

Reszta kodu obsugujcego metod onActivityResult() jest taka sama jak w przykadzie
z dziaaniem ACTION_PICK. Jeeli istnieje wiele aktywnoci mogcych przekaza ten sam typ
MIME, zostanie wywietlony ekran wyboru, na ktrym mona wskaza jedn z nich.

Wprowadzenie do intencji oczekujcych


W Androidzie istnieje odmiana intencji, zwana intencj oczekujc (ang. pending intent).
W tym przypadku pewien skadnik moe przechowywa intencj do przyszego uytku w lokacji,
z ktrej bdzie mona j ponownie przywoa. Na przykad w menederze alarmw chcemy
uruchomi usug w momencie wczenia si alarmu. Android dokonuje tego poprzez utworzenie osonowej intencji oczekujcej wok intencji. Intencja ta jest przechowywana w bezpiecznym miejscu, tak e nawet w przypadku wyganicia procesu wywoywania zostanie wysana
do docelowego miejsca. W trakcie tworzenia intencji oczekujcej Android przechowuje wystarczajco wiele informacji na temat rdowego procesu, aby powiadczenia zabezpiecze mogy
zosta sprawdzone na etapie jej wysyania lub przywoywania.
Zobaczmy, w jaki sposb moemy stworzy intencj oczekujc.
Intent regularIntent;
PendingIntent pi = PendingIntent.getActivity(context, 0, regularIntent,...);

Drugi argument metody PendingIntent.getActivity() nosi nazw requestCode


i w naszym przykadzie jego warto wynosi 0. Argument ten suy do rozrniania
dwch intencji oczekujcych, wywodzcych si z tej samej intencji. Zagadnienie to
zostao omwione o wiele dokadniej w rozdziale 15., gdzie zajmujemy si intencjami
oczekujcymi menederw alarmw.

Nazwa metody PendingIntent.getActivity() wywouje pewne wtpliwoci. Jaka jest tu rola


aktywnoci? Poza tym dlaczego nie skorzystano z metody create podczas tworzenia intencji
oczekujcej, a zamiast tego wprowadzono metod get?
Aby zrozumie pierwsze zagadnienie, musimy zagbi si nieco bardziej w sposb uytkowania standardowej intencji. Zwyczajna intencja moe zosta wykorzystana do uruchomienia
aktywnoci lub usugi albo do przywoania odbiorcy komunikatw (w dalszej czci ksiki
zaznajomimy si dokadniej z usugami oraz odbiorcami komunikatw). W kadym wymienionym przypadku sposb wykorzystania intencji jest nieco odmienny. Aby pogodzi je ze
sob, kontekst Androida (superklasa aktywnoci) zawiera trzy oddzielne metody. S to:

Rozdzia 5 Intencje

173

startActivity(intent)
startService(intent)
sendBroadcast(intent)

W jaki sposb te metody pozwalaj na okrelenie, czy naley rozpocz aktywno, usug, czy
te przygotowa odbiorc wiadomoci na transmisj, jeli trzeba przechowa intencje do pniejszego wykorzystania? Wanie dlatego trzeba jawnie okreli przeznaczenie intencji oczekujcej w momencie jej tworzenia. Teraz ponisze trzy metody staj si zrozumiae:
PendingIntent.getActivity(context, 0, intent, ...)
PendingIntent.getService(context, 0, intent, ...)
PendingIntent.getBroadcast(context, 0, intent, ...)

Pozostaa nam jeszcze kwestia metody get. Android przechowuje intencje oraz umoliwia ich
ponowne zastosowanie. Jeeli dwukrotnie wywoamy oczekujc intencj za pomoc tego samego
obiektu intencji, otrzymamy identyczn intencj oczekujc. Stanie si to nieco bardziej zrozumiae, jeli przypatrzymy si penej sygnaturze metody PendingIntent.getActivity().
Oto ona:
PendingIntent.getActivity(Context context,

//pierwotny kontekst

int requestCode,

//1,2, 3 itd.
Intent intent, //oryginalna intencja
int flags ) //flagi

Jeeli naszym celem jest uzyskanie kolejnej kopii intencji oczekujcej, musimy dostarczy inn
warto argumentu requestCode. Konieczno ta zostaa wyjaniona o wiele dokadniej w rozdziale 15., podczas omawiania menederw alarmw. Dwie intencje s uznawane za identyczne, jeeli ich wewntrzne elementy s takie same. Powysze stwierdzenie nie dotyczy
dodatkowych obiektw.
Dla intencji oczekujcej rodzaj czynnoci jest definiowany za pomoc flag. Chodzi tu o takie
czynnoci, jak przekazanie wartoci null, nadpisanie elementw dodatkowych i tak dalej. Wicej
wiadomoci na temat istniejcych rodzajw flag znajdziemy pod nastpujcym adresem:
http://developer.android.com/reference/android/app/PendingIntent.html
Zazwyczaj, aby intencja zachowaa si w sposb domylny, przekazujemy warto
mentom requestCode oraz flagom.

argu-

Odnoniki
Pod poniszymi adresami mona znale wicej materiaw (w jzyku angielskim), uzupeniajcych informacje z tego rozdziau:
http://developer.android.com/reference/android/content/Intent.html pod tym
adresem znajdziemy oglne informacje dotyczce intencji. Poznamy tu najpopularniejsze
dziaania, obiekty dodatkowe itd.
http://developer.android.com/guide/appendix/g-app-intents.html znajduje si tu
lista intencji wykorzystywanych w rnych aplikacjach firmy Google. Dowiemy si,
w jaki sposb wywoa intencje takich aplikacji, jak Browser, Map, Dialer czy Google
Street View.
http://developer.android.com/reference/android/content/IntentFilter.html tu znajdziemy
informacje dotyczce filtrw intencji, przydatne podczas rejestrowania tych filtrw.

174 Android 3. Tworzenie aplikacji

http://developer.android.com/guide/topics/intents/intents-filters.html tu omwiono
reguy okrelajce filtry intencji.
http://developer.android.com/resources/samples/get.html za pomoc tego cza
moemy pobra przykadowy kod aplikacji Notepad. Bez wczytania tego projektu
nie przetestujemy niektrych intencji.
http://developer.android.com/resources/samples/NotePad/index.html wersja online
kodu rdowego aplikacji Notepad.
http://www.openintents.org/ witryna, ktrej zadaniem jest prba zebrania intencji
tworzonych przez rnych wydawcw.
ftp://ftp.helion.pl/przyklady/and3ta.zip z tego adresu moemy pobra testowy
projekt, zaprojektowany specjalnie na potrzeby niniejszego rozdziau. Waciwy plik
znajdziesz w katalogu o nazwie ProAndroid3_R05_Intencje.

Podsumowanie
W tym rozdziale zdefiniowalimy najwaniejsze elementy dotyczce intencji w systemie Android. Przejrzelimy rnorodne scenariusze wykorzystania intencji, a take wyjanilimy
zwizki istniejce pomidzy intencjami a identyfikatorami URI treci. Wytumaczylimy
rwnie, w jaki sposb mona wykorzysta intencje do wywoywania aktywnoci przekazujcych wyniki. Wprowadzilimy take pojcie intencji oczekujcych. Pojcie to zostanie dokadniej przeanalizowane w rozdziaach 15. i 22.

R OZDZIA

6
Budowanie
interfejsw uytkownika
oraz uywanie kontrolek

Do tego momentu zajmowalimy si podstawami Androida, lecz nie poruszalimy


tematu interfejsu uytkownika (UI). W tym rozdziale omwimy interfejsy uytkownika oraz kontrolki. Rozpoczniemy od oglnej filozofii projektowania interfejsw UI w Androidzie, a nastpnie dokonamy analizy standardowych kontrolek,
dostpnych w zestawie Android SDK. Przyjrzymy si take menederom ukadu
graficznego i adapterom widokw. Dyskusj zakoczymy na omwieniu aplikacji
Hierarchy Viewer narzdzia sucego do wyszukiwania bdw oraz optymalizowania interfejsw UI w Androidzie.

Projektowanie interfejsw UI w Androidzie


Projektowanie interfejsu uytkownika w Androidzie jest przyjemne. Wynika to
z faktu, e proces ten jest wzgldnie atwy. Mamy do dyspozycji prosty do zrozumienia szkielet oraz niewielki zestaw predefiniowanych kontrolek. Dostpny obszar ekranu jest zazwyczaj ograniczony. Android wykonuje za programist cik
prac, ktra zwykle jest zwizana z jakoci projektowania i tworzenia interfejsu
uytkownika. Te elementy oraz stwierdzenie, e uytkownik przewanie chce wykona w danej chwili tylko jedn czynno, sprawiaj, e moemy w atwy sposb zaprojektowa dobry interfejs UI, sprawiajcy dobre wraenie na uytkowniku.
Zestaw Android SDK zosta zaopatrzony w grup kontrolek, ktre mona wykorzysta
do budowania aplikacji. Podobnie jak w przypadku innych zestaww projektowych
(SDK) dostpne s pola tekstowe, przyciski, listy, siatki i tak dalej. Dodatkowo
Android posiada zbir kontrolek dopasowanych do urzdze przenonych.
Podstaw kontrolek standardowych s dwie klasy: android.view.View oraz
android.view.ViewGroup. Nazwa pierwszej z nich sugeruje, e klasa View reprezentuje widok View oglnego zastosowania. Kontrolki standardowe znacznie
rozwijaj t klas. Klasa ViewGroup take jest widokiem, zawiera w sobie jednak

176 Android 3. Tworzenie aplikacji


dwa inne widoki. Jest to klasa bazowa dla wielu klas ukadu graficznego. Podobnie jak pakiet
Swing, tak i Android wykorzystuje pojcie ukadu graficznego do zarzdzania rozmieszczeniem
kontrolek wewntrz pojemnika widoku. Jak si przekonamy, stosowanie ukadw graficznych
uatwi nam kontrolowanie pozycji oraz orientacji kontrolek w interfejsach UI.
Do projektowania interfejsw UI w Androidzie mona podej na kilka sposobw. Mona
konstruowa je, korzystajc tylko z kodu. Mona take definiowa je, stosujc same znaczniki
jzyka XML. Istnieje nawet moliwo poczenia obydwu technik zdefiniowanie interfejsu
uytkownika w jzyku XML, a nastpnie odnoszenie si do niego i modyfikowanie go w kodzie.
W celach demonstracyjnych zaprojektujemy prosty interfejs uytkownika, korzystajc ze
wszystkich trzech metod.
Zanim rozpoczniemy, musimy ustali pewn nomenklatur. W niniejszej ksice oraz pozostaej
literaturze powiconej Androidowi podczas omawiania procesu projektowania interfejsu
uytkownika pojawiaj si takie pojcia, jak widok (ang. view), kontrolka (ang. control), widet
(ang. widget), pojemnik lub kontener (ang. container) oraz ukad graficzny (ang. layout). Osoby
stykajce si po raz pierwszy z programowaniem w rodowisku Android lub z projektowaniem
interfejsw UI w ogle mog nie by zaznajomione z tymi pojciami. Zanim rozpoczniemy,
zostan one pokrtce omwione (tabela 6.1).
Tabela 6.1. Nomenklatura interfejsu graficznego
Pojcie

Opis

Widok, widet, kontrolka

Kade z tych poj reprezentuje element interfejsu uytkownika.


Z przykadw mona wymieni przycisk, siatk, list, okno, okno
dialogowe i tak dalej. W tym rozdziale pojcia widok, widet
oraz kontrolka s stosowane jako synonimy.

Pojemnik, kontener

W tym widoku przechowywane s inne widoki. Na przykad siatk


mona uzna za pojemnik, poniewa przechowuje komrki bdce
widokami.

Ukad graficzny

Wizualne rozmieszczenie pojemnikw oraz widokw, w ktrym


mog zosta zawarte inne ukady graficzne.

Rysunek 6.1 przedstawia zrzut ekranu aplikacji, ktr wkrtce zaprojektujemy. Obok zrzutu
znajduje si schemat hierarchii kontrolek i pojemnikw aplikacji w ukadzie graficznym.

Rysunek 6.1. Interfejs uytkownika oraz ukad graficzny aktywnoci

Podczas omawiania przykadowych programw bdziemy si odnosi do tej hierarchii ukadu


graficznego. Na razie wystarczy wiedza, e nasza aplikacja posiada jedn aktywno. Interfejs
uytkownika tej aktywnoci skada si z trzech pojemnikw: obejmujcego imi i nazwisko osoby,
obejmujcego adres oraz pojemnika zewntrznego, nadrzdnego wobec dwch pozostaych.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

177

Programowanie interfejsu uytkownika


wycznie za pomoc kodu
Pierwszy przykad, umieszczony na listingu 6.1, pokazuje, w jaki sposb naley skonstruowa
interfejs uytkownika wycznie za pomoc kodu. Mona sprbowa to zrobi, tworzc nowy
projekt Androida zawierajcy aktywno nazwan MainActivity, a nastpnie kopiujc kod
z tego listingu do klasy MainActivity.
Na kocu rozdziau podajemy adres URL, z ktrego mona pobra omawiane tu projekty.
W ten sposb, zamiast przepisywa kod zawarty w ksice, Czytelnik moe zaimportowa
projekt do rodowiska Eclipse.
Listing 6.1. Utworzenie prostego interfejsu uytkownika wycznie za pomoc kodu
package com.androidbook.controls;
import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MainActivity extends Activity
{
private LinearLayout nameContainer;
private LinearLayout addressContainer;
private LinearLayout parentContainer;

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
createNameContainer();
createAddressContainer();
createParentContainer();
setContentView(parentContainer);
}
private void createNameContainer()
{
nameContainer = new LinearLayout(this);
nameContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT));
nameContainer.setOrientation(LinearLayout.HORIZONTAL);
TextView nameLbl = new TextView(this);

178 Android 3. Tworzenie aplikacji


nameLbl.setText("Imi, nazwisko: ");
TextView nameValue = new TextView(this);
nameValue.setText("Gall Anonim");
nameContainer.addView(nameLbl);
nameContainer.addView(nameValue);
}
private void createAddressContainer()
{
addressContainer = new LinearLayout(this);
addressContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT));
addressContainer.setOrientation(LinearLayout.VERTICAL);
TextView addrLbl = new TextView(this);
addrLbl.setText("Adres:");
TextView addrValue = new TextView(this);
addrValue.setText("Ulica Sezamkowa 90210");
addressContainer.addView(addrLbl);
addressContainer.addView(addrValue);
}
private void createParentContainer()
{
parentContainer = new LinearLayout(this);
parentContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
parentContainer.setOrientation(LinearLayout.VERTICAL);
parentContainer.addView(nameContainer);
parentContainer.addView(addressContainer);
}
}

Jak wida na listingu 6.1, aktywno zawiera trzy ukady graficzne LinearLayout. Wspomnielimy wczeniej, e obiekty typu layout s przystosowane do pozycjonowania innych obiektw
w danej czci ekranu. Na przykad ukad graficzny LinearLayout okrela, czy obiekt ma by
umieszczony w pionie, czy w poziomie. W obiektach typu layout mona umieszcza dowolne typy widokw nawet inne ukady graficzne.
W obiekcie nameContainer s umieszczone dwie kontrolki TextView: jedna wywietla etykiet
Imi, nazwisko, a druga przechowuje dane imienia i nazwiska (w naszym przykadzie Gall
Anonim). Analogicznie obiekt addressContainer rwnie zawiera dwie kontrolki TextView.
Rnica pomidzy tymi pojemnikami jest taka, e ten pierwszy zosta umieszczony poziomo,
a obiekt drugi pionowo. Obydwa pojemniki znajduj si we wntrzu obiektu parentContainer,
stanowicego podstawowy widok aktywnoci. Po utworzeniu pojemnikw aktywno przypisuje
tre widoku do widoku gwnego poprzez wywoanie metody setContentView(parent
Container). Kiedy trzeba wywietli interfejs uytkownika tej aktywnoci, gwny widok jest

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

179

wywoywany do wywietlenia na ekranie. Widok ten nastpnie wywouje pojemniki podrzdne,


te z kolei wywouj swoje pojemniki podrzdne i tak dalej a do momentu wywietlenia
caego interfejsu.
Ponadto na listingu 6.1 wida kilka kontrolek LinearLayout. Dwie z nich s uoone pionowo, a jedna poziomo. Tym rodzynkiem jest kontrolka nameContainer. Oznacza to, e dwie
kontrolki TextView pojawiaj si tu obok siebie w poziomie. Obiekt addressContainer jest
umieszczony pionowo, a zatem dwie kolejne kontrolki TextView s na sobie uoone. Rwnie
pojemnik parentContainer jest zorientowany w pionie, dlatego obiekt nameContainer pojawia
si ponad obiektem addressContainer. Zauwamy subteln rnic pomidzy dwoma umieszczonymi pionowo pojemnikami addressContainer i parentContainer: ten drugi zosta tak
skonfigurowany, eby zaj ca dugo i szeroko ekranu:
parentContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));

Natomiast obiekt addressContainer otacza zawart w nim tre w pionie:


addressContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.WRAP_CONTENT));

Inaczej mwic, parametr WRAP_CONTENT oznacza, e widok zajmuje w danym wymiarze wycznie tyle przestrzeni, ile jest konieczne, a poza tym jest ograniczony rozmiarem swojego widoku nadrzdnego. W przypadku kontrolki addressContainer oznacza to, e bdzie ona zajmowaa dwie linijki tekstu, poniewa potrzebuje tylko tyle miejsca.

Tworzenie interfejsu uytkownika wycznie w pliku XML


Zaprogramujmy teraz identyczny interfejs UI za pomoc jzyka XML (listing 6.2). Przypominamy informacj z rozdziau 3., e pliki XML ukadu graficznego przechowywane s w katalogu zasobw (/res/), w podkatalogu layout. W celu wyprbowania tego przykadu naley
utworzy nowy projekt w rodowisku Eclipse. Domylnie zostanie utworzony plik ukadu graficznego main.xml, zlokalizowany w folderze /res/layout. Po dwukrotnym klikniciu nazwy tego
pliku edytor tekstowy rodowiska Eclipse wywietli jego zawarto. Prawdopodobnie na samej
grze tego widoku bdzie widnia mniej wicej taki cig znakw: Hello World, MainActivity!
lub co podobnego. Aby ujrze kod XML tego pliku, naley klikn zakadk main.xml u dou
widoku. Stan si widoczne kontrolki LinearLayout oraz TextView. Korzystajc z zakadek Layout
oraz main.xml, naley odtworzy kod z listingu 6.2 w pliku main.xml i zachowa zmiany.
Listing 6.2. Utworzenie interfejsu uytkownika wycznie w jzyku XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">

<!-- KONTENER IMIENIA I NAZWISKA -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Imi, nazwisko:" />

180 Android 3. Tworzenie aplikacji


<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Gall Anonim" />
</LinearLayout>

<!-- KONTENER ADRESU -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Adres:" />
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Ulica Sezamkowa 90210" />
</LinearLayout>
</LinearLayout>

W katalogu src tego projektu istnieje domylny plik .java, zawierajcy definicj klasy Activity.
Dwukrotne kliknicie nazwy tego pliku spowoduje wywietlenie jego treci. Zwrmy uwag na
instrukcj setContentView(R.layout.main). Fragment kodu XML umieszczony na listingu
6.2 w poczeniu z wywoaniem setContentView(R.layout.main) spowoduje wywietlenie
takiego samego interfejsu uytkownika jak w przypadku listingu 6.1. Nie trzeba omawia pliku
XML, warto jednak zwrci uwag, e zdefiniowano trzy widoki pojemnikw. Pierwszy, Linear
Layout, jest odpowiednikiem pojemnika nadrzdnego. Jego pooenie zostaje ustalone na pionowe poprzez zdefiniowanie odpowiedniej waciwoci android:orientation="vertical".
W pojemniku nadrzdnym s umieszczone dwa podrzdne elementy LinearLayout, reprezentujce, odpowiednio, pojemniki nameContainer oraz addressContainer.
Uruchomienie tej aplikacji spowoduje wygenerowanie takiego samego ukadu graficznego jak
w poprzednim przykadzie. Wywietlane etykiety i wartoci bd takie same jak na rysunku 6.1.

Konstruowanie interfejsu uytkownika


za pomoc kodu oraz jzyka XML
Kod na listingu 6.2 stanowi raczej wydumany przykad trwae zakodowanie wartoci kontrolek TextView w ukadzie graficznym XML nie ma sensu. Najlepiej byoby zaprojektowa
interfejs uytkownika w jzyku XML, a nastpnie utworzy odniesienia do kontrolek. Taka
technika pozwala na doczanie dynamicznie zmieniajcych si danych do kontrolek zdefiniowanych w trakcie tworzenia projektu. W istocie jest to zalecana metoda. Budowanie ukadw graficznych w jzyku XML, a nastpnie stosowanie kodu do wypeniania tych ukadw
dynamicznymi danym jest cakiem prost czynnoci.
Na listingu 6.3 zosta pokazany ten sam interfejs UI, lecz z nieco zmodyfikowanym kodem
XML. Przydzielono tutaj identyfikatory kontrolek TextView, dziki czemu moemy si do nich
odnie w kodzie.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

181

Listing 6.3. Utworzenie interfejsu uytkownika w kodzie XML z doczonymi identyfikatorami


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

<!-- KONTENER IMIENIA I NAZWISKA -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="@string/name_text" />
<TextView android:id="@+id/nameValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

<!-- KONTENER ADRESU -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="@string/addr_text" />
<TextView android:id="@+id/addrValue"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

Poza dodaniem identyfikatorw do kontrolek TextView, ktre pniej wypenimy danymi z kodu,
moemy take uy etykietowych kontrolek TextView. Ich wartoci s wypeniane tekstem
z pliku zasobw. S to kontrolki TextView nieposiadajce identyfikatora zawartego w atrybucie
android:text. Jeeli przypomnimy sobie informacje z rozdziau 3., waciwe cigi znakw,
przeznaczone dla tych kontrolek, znajduj si w pliku strings.xml umieszczonym w folderze
/res/values. Listing 6.4 przedstawia taki wykorzystany w naszym przykadzie plik strings.xml.
Listing 6.4. Plik strings.xml wsppracujcy z kodem z listingu 6.3
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Kontrolki standardowe</string>
<string name="name_text">Imi i nazwisko:</string>
<string name="addr_text">Adres:</string>
</resources>;

Kod umieszczony na listingu 6.5 demonstruje, w jaki sposb uzyska odniesienia do kontrolek
zdefiniowanych w kodzie XML w celu skonfigurowania ich waciwoci. Moemy wstawi go
do metody onCreate() zawartej w naszej aktywnoci.

182 Android 3. Tworzenie aplikacji


Listing 6.5. Tworzenie odniesie do kontrolek w zasobach w czasie dziaania
setContentView(R.layout.main);
TextView nameValue = (TextView)findViewById(R.id.nameValue);
nameValue.setText("Gall Anonim");
TextView addrValue = (TextView)findViewById(R.id.addrValue);
addrValue.setText("Ulica Sezamkowa 90210");

Powyszy kod nie jest skomplikowany, jednak naley zauway, e zanim wywoamy metod
findViewById(), wczytujemy zasb poprzez wywoanie setContentView(R.layout.main)
nie moemy odnie zasobw do widokw, jeeli zasoby nie zostay jeszcze zaadowane.
Twrcy systemu Android wykonali kawa dobrej roboty, umoliwiajc konfigurowanie waciwie kadego aspektu kontrolek za pomoc kodu lub jzyka XML. Preferowanym rozwizaniem
jest ustanawianie atrybutw kontrolek w pliku XML, bez koniecznoci uywania kodu. Jednak
bdzie jeszcze wiele okazji do wykorzystania kodu, na przykad w przypadku ustanawiania
wartoci, ktra bdzie wywietlana uytkownikowi.

FILL_PARENT a MATCH_PARENT
Staa FILL_PARENT zostaa wycofana w wersji 2.2 i zastpiono j sta MATCH_PARENT. Zmiana
dotyczy jednak wycznie nazwy. Warto tej staej cigle wynosi -1. Podobnie w przypadku
ukadw graficznych tworzonych w jzyku XML argument fill_parent zosta zastpiony
argumentem match_parent. Zatem jaka warto jest tu waciwie stosowana? Zamiast staej
FILL_PARENT lub MATCH_PARENT moglibymy po prostu wprowadzi warto -1 i nic by si nie
stao. Nie jest ona jednak odczytywana w prosty sposb, poza tym nie istnieje rwnowana,
nienazwana warto, ktr mona by zastosowa do ukadw graficznych tworzonych w jzyku
XML. Znamy lepsze rozwizanie.
W zalenoci od poziomu interfejsu API, ktry chcemy wykorzysta w aplikacji, moemy stworzy program wykorzystujcy wersj Androida starsz od 2.2 i liczy na kompatybilno
w przd albo przygotowa aplikacj pod ktem wersji co najmniej 2.2 i ustawi w argumencie
minSdkVersion najstarsz wersj systemu, na jakiej nasze dzieo bdzie pracowa. Jeli na
przykad wystarcz nam funkcje zawarte w wersji 1.6 Androida, to wanie dla niej tworzymy
aplikacj i wykorzystujemy argumenty FILL_PARENT oraz fill_parent. Powinna ona bez
problemu dziaa rwnie w nowszych wersjach systemu. Jeeli wymagana jest funkcjonalno
wersji 2.2 Androida, piszemy przystosowany do niej program, stosujemy MATCH_PARENT
i match_parent, natomiast w argumencie minSdkVersion wstawiamy warto starszej wersji
interfejsw API, na przykad 4 (odpowiada ona wersji 1.6 Androida). Moemy wdroy aplikacj napisan pod wersj 2.2 Androida do jego starszej wersji, musimy jednak uwaa na
stosowane klasy oraz (lub) metody, ktre s w niej niedostpne. Zawsze znajdzie si jakie rozwizanie, choby stosowanie refleksji lub klas osonowych, aby zniwelowa rnice pomidzy wersjami Androida. Nie zajmujemy si tu jednak tym zagadnieniem.

Standardowe kontrolki Androida


Zajmiemy si teraz omwieniem standardowych kontrolek, dostpnych w zestawie Android SDK.
Rozpoczniemy od kontrolek tekstu, a nastpnie przejdziemy do przyciskw, pl wyboru, przyciskw opcji, list, siatek, kontrolek daty i czasu oraz widoku mapy. Przedyskutujemy take kwesti kontrolek ukadu graficznego.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

183

Kontrolki tekstu
Prawdopodobnie kontrolki tekstu s pierwszym rodzajem kontrolek, na ktre natykaj si
programici. Android posiada peny, lecz nieprzytaczajcy ogromem zestaw kontrolek tekstu.
W kolejnych podpunktach omwimy kontrolki TextView, EditText, AutoCompleteTextView
oraz MultiCompleteTextView. Na rysunku 6.2 pokazalimy dziaanie tych kontrolek.

Rysunek 6.2. Kontrolki tekstu w Androidzie

TextView
Widzielimy ju prost specyfikacj kontrolki TextView w jzyku XML (listing 6.3) oraz sposb jej definiowania w kodzie (listing 6.4). Zwrmy uwag na sposb okrelania identyfikatora,
szerokoci, wysokoci oraz wartoci tekstu w pliku XML oraz mechanizm ustanawiania wartoci
za pomoc metody setText(). Kontrolka TextView wywietla tekst, jednak nie pozwala na jego
edycj. Mona by wysnu wniosek, e jest to jedynie zwyka etykieta. Nieprawda. Kontrolka ta
posiada kilka interesujcych waciwoci, dziki ktrym staje si bardzo przydatna. Jeeli na
przykad wiadomo, e zawartoci kontrolki TextView bdzie adres URL lub adres e-mail,
mona ustanowi waciwo autoLink wobec obiektu email|web, dziki czemu kontrolka
znajdzie i podwietli dany adres URL lub e-mail. Co wicej, kiedy uytkownik kliknie jeden z podwietlonych elementw, system uruchomi aplikacj pocztow z otwart do edycji wiadomoci
z ju wpisanym adresem e-mail lub przegldark stron WWW z wpisanym adresem URL. W jzyku XML atrybut ten znajdowaby si wewntrz znacznika TextView i wygldaby nastpujco:
<TextView ... android:autoLink="email|web" ... />

gdzie okrelamy ograniczony zbir wartoci, takich jak web, email, phone, map lub none (domylnie) albo all. Jeeli chcemy ustawi waciwo autoLink w kodzie, a nie w pliku XML,
odpowiednie wywoanie metody nosi nazw setAutoLinkMask(). Odczytuje ona wartoci
typu int, reprezentujce podobne do widzianego wczeniej poczenie wartoci, na przykad
Linkify.EMAIL_ADDRESSES|Linkify.WEB_ADDRESSES. W tym celu kontrolka TextView wykorzystuje klas android.text.util.Linkify. Na listingu 6.6 przedstawiono przykad automatycznego korzystania z czy za pomoc kodu.

184 Android 3. Tworzenie aplikacji


Listing 6.6. Zastosowanie klasy Linkify z kontrolk TextView
TextView tv =(TextView)this.findViewById(R.id.tv);
tv.setAutoLinkMask(Linkify.ALL);
tv.setText("Odwied moj stron, http://www.androidbook.com
lub napisz do mnie na adres davemac327@gmail.com.");

Zwrmy uwag, e opcje automatycznego korzystania z czy konfigurujemy w kontrolce


TextView przed ustawieniem tekstu. Jest to wane, poniewa ustawienie tych opcji po wpisaniu
tekstu nie wpynie na ten istniejcy tekst. Poniewa hipercza dodajemy w tekcie za pomoc
kodu, nasz fragment jzyka XML dotyczcy kontrolki TextView z listingu 6.6 nie wymaga
adnych dodatkowych atrybutw i moe by taki prosty:
<TextView android:id="@+id/tv" android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

Jeli chcemy, moemy przywoa statyczn metod addLinks() klasy Linkify w celu znalezienia czy oraz dodania ich do kontrolek TextView lub Spannable. Zamiast korzysta z metody
setAutoLinkMask(), mona wpisa poniszy wiersz ju po wstawieniu tekstu:
Linkify.addLinks(tv, Linkify.ALL);

Kliknicie takiego cza powoduje wywoanie domylnej intencji tego dziaania. Na przykad
po klikniciu adresu URL zostanie uruchomiona przegldarka internetowa z wklejonym adresem. Kliknicie numeru telefonu otworzy ekran wybierania numeru itd. Klasa Linkify moe
sprzga te czynnoci bez najmniejszego problemu.
Klasa Linkify umoliwia rwnie wykrywanie niestandardowych wzorcw, okrela, czy dany
obiekt moe zareagowa na kliknicie, a take decyduje, w jaki sposb uruchomi intencj wywoujc jakie dziaanie po klikniciu. Nie bdziemy wnika w szczegy, warto jednak wiedzie,
e takie opcje s dostpne.
Istnieje duo wicej funkcjonalnoci kontrolki TextView, takich jak atrybuty fontw, minLines
i maxLines, a take wiele, wiele innych. Wikszo z nich posiada do oczywiste przeznaczenie i zachcamy Czytelnikw do eksperymentowania z nimi. Naley jednak pamita, e niektre funkcje tej klasy nie znajd zastosowania dla pl tylko do odczytu. Do obsugi pl
edytowalnych su odpowiednie podklasy, z ktrych jedna zostaa omwiona poniej.

EditText
Kontrolka EditText jest kontrolk klasy TextView. Jak sugeruje jej nazwa, umoliwia
edytowanie tekstu. EditText nie jest tak rozbudowana jak niektre kontrolki tego typu dostpne w internecie, jednak uytkownicy urzdze obsugujcych Androida nie bd raczej
tworzyli w nich rozbudowanych dokumentw co najwyej kilka akapitw. Zatem klasa ta
posiada ograniczony, lecz rozsdnie dobrany zestaw funkcji, ktry moe nawet zaskoczy
niejedn osob. Przykadowo jedn z najwaniejszych waciwoci kontrolki EditText jest
inputType. Moemy skonfigurowa waciwo inputType w taki sposb, aby flaga textAutoCorrect poprawiaa pospolitsze bdy w trakcie pisania. Waciwo textCapWords powoduje przeksztacanie maych liter w due na pocztku zdania, wyrazw itd. Istniej rwnie
pewne opcje stosowane wycznie dla numerw telefonw, hase itp.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

185

Istniej starsze, obecnie uznane za przestarzae, sposoby definiowania duych liter, tekstu mieszczcego si w wielu wierszach oraz wielu innych cech. Jeeli zostan one okrelone przy braku
waciwoci inputType, bd normalnie odczytywane, jednak jeli gdziekolwiek waciwo
inputType zostanie zdefiniowana, starsze typy waciwoci bd ignorowane.
Dawnym domylnym zachowaniem kontrolki EditText byo wywietlanie tekstu w jednym
wierszu oraz, w razie potrzeby, w kolejnych wierszach. Inaczej mwic, jeeli uytkownik
wypeni tekstem cay pierwszy wiersz, pojawia si wiersz drugi, trzeci i tak dalej. Mona
byo jednak wymusi korzystanie tylko z jednego wiersza poprzez ustawienie wartoci true we
waciwoci singleLine. W takim wypadku uytkownik musia zmieci cay tekst w jednym
wierszu. W przypadku waciwoci inputType, jeeli nie zdefiniujemy waciwoci textMultiLine,
kontrolka EditText bdzie domylnie ograniczona wycznie do jednej linii tekstu. Jeli wic
chcemy wprowadzi dawny typ domylnego zachowania, umoliwiajcy pisanie w wielu wierszach, musimy we waciwoci inputType ustawi flag textMultiLine.
Jedn z przyjemniejszych funkcji kontrolki EditText jest moliwo okrelenia tekstu podpowiedzi. Taka podpowied bdzie wywietlana nieco janiej i zniknie w momencie, gdy uytkownik zacznie wpisywa swj tekst. Zadaniem podpowiedzi jest powiadomienie uytkownika
o przeznaczeniu danego pola tekstowego, bez koniecznoci zaznaczania i usuwania domylnego
tekstu. W pliku XML atrybut ten wyglda nastpujco: android:hint="Tutaj wprowadzamy
tekst podpowiedzi" lub android:hint="@string/nazwa_podpowiedzi", gdzie nazwa_
podpowiedzi stanowi nazw cigu znakw umieszczonego w pliku /res/values/strings.xml. Piszc
kod, wywoujemy metod setHint(), ktrej argumentem jest cig znakw CharSequence lub
identyfikator zasobu.

AutoCompleteTextView
Kontrolka AutoCompleteTextView jest obiektem klasy TextView z funkcjonalnoci automatycznego wypeniania. Innymi sowy, podczas pisania tekstu przez uytkownika w oknie TextView
bd wywietlane sugestie dokoczenia wyrazu. Na listingu 6.7 zaprezentowano kod kontrolki
AutoCompleteTextView.
Listing 6.7. Stosowanie kontrolki AutoCompleteTextView
<AutoCompleteTextView android:id="@+id/actv"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
AutoCompleteTextView actv = (AutoCompleteTextView) this.findViewById(R.id.actv);
ArrayAdapter<String> aa = new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line,
new String[] {"Angielski", "Hebrajski", "Hindi", "Hiszpaski", "Niemiecki", "Grecki" });
actv.setAdapter(aa);

Kontrolka AutoCompleteTextView przedstawiona na listingu 6.7 sugeruje uytkownikowi


okrelony jzyk. Jeeli na przykad zostanie wpisany tekst an, kontrolka zasugeruje jzyk angielski.
Po wpisaniu gr zostanie zaproponowany jzyk grecki i tak dalej.
Kontrolki z funkcj podpowiedzi lub podobne kontrolki z funkcj automatycznego wypeniania
skadaj si z dwch czci: kontrolki widoku tekstu oraz kontrolki odpowiedzialnej za wywietlanie podpowiedzi. Taka jest oglna koncepcja. eby mc stosowa tak kontrolk, naley

186 Android 3. Tworzenie aplikacji


utworzy najpierw j, a nastpnie list podpowiedzi, ktr trzeba przypisa do tej kontrolki.
Trzeba te okreli sposb wywietlania podpowiedzi. Ewentualnie mona utworzy drug kontrolk w celu wywietlania podpowiedzi, a nastpnie powiza obydwie kontrolki ze sob.
Automatyczne wprowadzanie tekstu w Androidzie jest proste, czego dowodem jest listing 6.7.
eby korzysta z kontrolki AutoCompleteTextView, mona j zdefiniowa w pliku ukadu graficznego, a nastpnie odnosi si do niego w aktywnoci. Nastpnie tworzy si klas przejciow
(ang. adapter class), przechowujc podpowiedzi, oraz definiuje si identyfikator kontrolki,
ktra bdzie je wywietlaa (w tym przypadku prost list elementw). Drugi parametr przedstawionego na listingu 6.7 obiektu ArrayAdapter wskazuje klasie przejciowej, e podpowiedzi
maj by przedstawiane w postaci prostej listy. Ostatnim krokiem jest powizanie klasy przejciowej z kontrolk AutoCompleteTextView za pomoc metody setAdapter(). Nie przejmujmy
si na razie adapterami. Zajmiemy si nimi w dalszej czci rozdziau.

MultiAutoCompleteTextView
Osoby korzystajce z kontrolki AutoCompleteTextView wiedz, e oferuje ona jedynie podpowiedzi dla caego tekstu w oknie widoku. Inaczej mwic, podczas pisania zdania nie bd wywietlane sugestie dla kadego wyrazu oddzielnie. Do takich zastosowa jest przeznaczona
kontrolka MultiAutoCompleteTextView. Suy ona do wywietlania podpowiedzi w trakcie
wpisywania tekstu przez uytkownika. Na rysunku 6.2 pokazano, e uytkownik wpisa wyraz
Angielski, a po przecinku Ni, co powoduje wywietlenie sugerowanego wyrazu Niemiecki.
Jeeli uytkownik bdzie wypisywa nazwy innych jzykw, aplikacja wywietli kolejne sugestie.
MultiAutoCompleteTextView uywa si tak samo jak obiektu AutoComplete
TextView. Rnica polega na koniecznoci okrelenia miejsca wywietlania kolejnej podpo-

Kontrolki

wiedzi. Na przykad na rysunku 6.2 pokazano, e sugestie mog by proponowane na pocztku


zdania oraz po przecinku. Konieczne jest zatem umieszczenie tokenizera analizujcego zdanie oraz
wskazujcego kontrolce MultiAutoCompleteTextView, kiedy ma wywietli kolejn podpowied.
Na listingu 6.8 zaprezentowano plik XML oraz kod kontrolki MultiAutoCompleteTextView.
Listing 6.8. Stosowanie kontrolki MultiAutoCompleteTextView
<MultiAutoCompleteTextView android:id="@+id/mactv"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
MultiAutoCompleteTextView mactv = (MultiAutoCompleteTextView) this
.findViewById(R.id.mactv);
ArrayAdapter<String> aa2 = new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line,
new String[] {"Angielski", "Hebrajski", "Hindi", "Hiszpaski", "Niemiecki",
"Grecki" });
mactv.setAdapter(aa2);
mactv.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());

Jedyn istotn rnic pomidzy listingiem 6.7 a 6.8 jest zastosowanie klasy MultiAuto
CompleteTextView oraz wywoanie metody setTokenizer(). Poniewa w tym wypadku
uwzgldniono obiekt CommaTokenizer, po wprowadzeniu przecinka w polu EditText znw pojawi si okno podpowiedzi korzystajce z tablicy cigw znakw. Inne znaki nie spowoduj
wywietlenia pola sugestii. Zatem jeli nawet uytkownik wpisze wyrazy Francuski Hiszpa,
nie pojawi si sugestia dokoczenia drugiego sowa, poniewa przed nim nie ma przecinka.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

187

Innym rodzajem tokenizera obsugiwanego przez Androida jest ten przeznaczony dla adresw e-mail. Nosi on nazw Rfc822Tokenizer. W razie potrzeby zawsze mona utworzy
swj wasny tokenizer.

Kontrolki przyciskw
Przyciski s standardem w kadym rodowisku obsugujcym widety, a Android nie jest wyjtkiem. Do dyspozycji mamy typowy zestaw przyciskw oraz kilka dodatkw. W nastpnych
podpunktach zajmiemy si trzema rodzajami kontrolek przyciskw: przyciskiem podstawowym, przyciskiem obrazkowym oraz przyciskiem przeczania. Na rysunku 6.3 zosta pokazany
interfejs uytkownika zawierajcy wszystkie trzy rodzaje przyciskw. S to kolejno: przycisk
podstawowy, przycisk obrazkowy oraz przycisk przeczania.

Rysunek 6.3. Kontrolki przyciskw w Androidzie

Zajmijmy si najpierw przyciskiem podstawowym.

Kontrolka Button
Klas przycisku podstawowego w Androidzie jest android.widget.Button. Niewiele mona
powiedzie na temat tego rodzaju przyciskw, poza omwieniem obsugi zdarze wyzwalanych
klikniciem. Na listingu 6.9 zosta zaprezentowany fragment ukadu graficznego zapisanego
w jzyku XML dla kontrolki Button, a take kod Java, ktry mona wprowadzi do metody
onCreate() naszej aktywnoci. Taki podstawowy przycisk bdzie wyglda tak jak grny
przycisk widoczny na rysunku 6.3.
Listing 6.9. Obsuga zdarze wyzwalanych klikniciem w obiekcie Button
<Button android:id="@+id/ccbtn1"
android:text="@string/basicBtnLabel"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
Button btn = (Button)this.findViewById(R.id.ccbtn1);
btn.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse(http://www.androidbook.com));

188 Android 3. Tworzenie aplikacji


startActivity(intent);
}
});

Na listingu 6.9 zaprezentowano sposb rejestrowania zdarzenia wywoanego klikniciem. Dokonuje si tego poprzez wywoanie metody setOnClickListener() wobec interfejsu onClick
Listener. Aby obsuy zdarzenia wywoywane klikniciem obiektu (przycisku) btn, na
bieco utworzono anonimowy obiekt nasuchujcy. Kliknicie przycisku powoduje wywoanie
metody onClick() obiektu nasuchujcego, co w naszym przypadku powoduje otwarcie okna
przegldarki.
Wraz z wydaniem rodowiska Android SDK w wersji 1.6 wprowadzono atwiejszy sposb konfigurowania obsugi kliknicia przycisku (przyciskw). Na listingu 6.10 zosta ukazany fragment
XML dla obiektu Button, w ktrym okrelamy atrybut procedury obsugi, a take kod Java stanowicy procedur obsugi kliknicia.
Listing 6.10. Konfigurowanie procedury obsugi kliknicia dla przycisku
<Button ... android:onClick="myClickHandler" ... />
public void myClickHandler(View target) {
switch(target.getId()) {
case R.id.ccbtn1:
...

Dla obiektu klasy View, reprezentujcego nacinity przycisk, nastpi wywoanie funkcji obsugi
kliknicia wraz z zestawem usug oczekiwanych od tej funkcji. Naley zwrci uwag, w jaki
sposb instrukcja switch zawarta w omawianej metodzie obsugi kliknicia wykorzystuje
identyfikatory zasobw przycisku do uruchomienia procesu. Stosowanie tej metody oznacza,
e obiekty klasy Button nie bd jawnie tworzone w kodzie oraz e ta sama metoda moe by
wykorzystywana take do obsugi wielu przyciskw. Dziki temu struktura interfejsu staje si
bardziej zrozumiaa i przejrzysta. Metoda ta dziaa rwnie w przypadku pozostaych typw
przyciskw. Nie zadziaa ona jednak w wersji 1.5 Androida i starszych. Nie pojawi si informacja
o bdzie; po prostu kliknicie przycisku nie wywoa adnej reakcji.

Kontrolka ImageButton
Przyciski obrazkowe s dostpne w Androidzie dziki klasie android.widget.ImageButton.
Uywanie tego rodzaju obiektu przypomina korzystanie z przycisku podstawowego (listing
6.11). Przycisk obrazkowy bdzie przypomina rodkowy przycisk, widoczny na rysunku 6.3.
Listing 6.11. Stosowanie kontrolki ImageButton
<ImageButton android:id="@+id/imageBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:onClick=myClickHandler
android:src=@drawable/icon />
ImageButton btn = (ImageButton)this.findViewById(R.id.imageBtn);
btn.setImageResource(R.drawable.icon);

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

189

W tym fragmencie kodu utworzylimy w pliku XML przycisk obrazkowy. Obraz dla tego przycisku
znajduje si w zasobach typu drawable. Plik z tym obrazem musi si znajdowa w katalogu
/res/drawable. W naszym przypadku wykorzystalimy domyln ikon Androida. Na listingu
6.11 pokazalimy rwnie dynamiczny sposb konfiguracji przycisku obrazkowego poprzez
wywoanie metody setImageResource() na przycisku i przekazanie jej identyfikatora zasobu.
Warto zapamita, e wystarczy zastosowa tylko jeden z tych dwch sposobw. Nie trzeba
definiowa przycisku obrazkowego jednoczenie w kodzie oraz w pliku XML.
Interesujc funkcj przycisku obrazkowego jest moliwo ustawienia przezroczystego ta.
W ten sposb dowolny obraz mona ustawi tak, aby zachowywa si jak przycisk.
Poniewa przycisk obrazkowy moe si zasadniczo rni od zwykego przycisku, mona dostosowa jego wygld, gdy znajduje si w dwch pozostaych stanach. Warto bowiem przypomnie, e oprcz normalnego stanu przyciski mog si znale w stanie uaktywnienia oraz
zosta wcinite. Stan uaktywnienia oznacza po prostu, e przycisk znajduje si w stanie gotowoci. Moemy uaktywni przycisk za pomoc klawiszy strzaek klawiatury lub D-pada1. Przycisk jest wcinity, gdy jego wygld zmienia si po wciniciu, ale uytkownik nie zdy go
jeszcze puci. Aby zdefiniowa trzy obrazy dla jednego przycisku oraz przypisa kady
z nich do okrelonego stanu, konfigurujemy selektor. Jest to niewielki plik XML, umieszczony
w katalogu /res/drawable projektu. Jest to zachowanie cokolwiek sprzeczne z logik, poniewa
w katalogu tym umieszczamy plik XML, a nie rysunek. Mimo to wanie tutaj musi si znale
selektor. Zawarto pliku selektora zostaa ukazana na listingu 6.12.
Listing 6.12. Wykorzystanie selektora wraz z kontrolk ImageButton
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/button_pressed" /> <!-- wcinity -->
<item android:state_focused="true"
android:drawable="@drawable/button_focused" />
<item android:drawable="@drawable/icon" />

<!-- uaktywniony -->


<!-- domylny -->

</selector>

Naley zapamita kilka informacji o selektorze. Po pierwsze, nie definiujemy znacznika


<resources> spotykanego w innych plikach XML. Po drugie, istotna jest kolejno wymieniania w selektorze obrazw przypisanych do poszczeglnych stanw przycisku. Android sprawdza
dopasowanie kadego obiektu umieszczonego w selektorze zgodnie z ich kolejnoci, my natomiast chcemy, aby obraz przypisany do normalnego stanu przycisku by stosowany wycznie
wtedy, gdy przycisk nie jest wcinity ani uaktywniony. Jeeli obraz przypisany do normalnego
stanu przycisku zostanie wskazany na pocztku selektora, bdzie zawsze wybierany, nawet jeli przycisk zostanie uaktywniony lub wcinity. Oczywicie pliki obiektw typu drawable musz
si znajdowa w katalogu /res/drawables. W kocu, definiujc przycisk za pomoc pliku XML,
powinnimy ustanowi powizanie z selektorem we waciwoci android:src, tak jakbymy
mieli do czynienia z zasobem typu drawable, na przykad:
<Button ... android:src="@drawable/imagebuttonselector" ... />

tzw. krzyak, kontroler stosowany w konsolach do gier przyp. red.

190 Android 3. Tworzenie aplikacji

Kontrolka ToggleButton
Kontrolka ToggleButton, taka jak pole wyboru lub przycisk opcji, reprezentuje kategori przyciskw dwustanowych. Przycisk taki moe si znajdowa w stanie wczonym lub wyczonym.
Domylnym zachowaniem przycisku ToggleButton jest wywietlanie zielonego paska w stanie
wczonym i wyszarzonego w stanie wyczonym. Co wicej, tekst przycisku brzmi On, gdy
przycisk jest wczony, i zmienia si na Off po jego wyczeniu. Istnieje moliwo modyfikowania tekstu pojawiajcego si w poszczeglnych stanach kontrolki ToggleButton, jeeli domylne ustawienia nie pasuj do tworzonej aplikacji. Jeli na przykad taki przycisk ma umoliwia kontrol procesu przebiegajcego w tle, mona umieci wyrazy Uruchom oraz Zatrzymaj
poprzez zdefiniowanie waciwoci android:textOn oraz android:textOff.
Na listingu 6.13 ukazano przykad. Przycisk przeczania jest widoczny na dole rysunku 6.3
i znajduje si w pozycji On, wic na etykiecie umieszczonej pod nim widnieje napis Stop.
Listing 6.13. Przycisk przeczania w Androidzie
<ToggleButton android:id="@+id/cctglBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Przycisk przeczania"
android:textOn="Uruchom"
android:textOff="Zatrzymaj"/>

Poniewa teksty wywietlane podczas wczenia lub wyczenia przycisku ToggleButton s


oddzielnymi atrybutami, atrybut android:text waciwie nie jest wykorzystywany. Jest on dostpny, poniewa zosta odziedziczony (po obiekcie TextView), jednak w tym przypadku okazuje si niepotrzebny.

Kontrolka CheckBox
Kontrolka CheckBox jest kolejnym przyciskiem dwustanowym, umoliwiajcym uytkownikowi
wybranie stanu. Rnica polega na tym, e w wielu sytuacjach uytkownicy nie postrzegaj
jej jako przycisku bezporednio wywoujcego akcj. Jednak z punktu widzenia programisty
Androida takie pole wyboru jest przyciskiem i mona na nim wykonywa te same czynnoci
co na przycisku.
W Androidzie pole wyboru jest tworzone poprzez ustanowienie instancji klasy

android.

widget.CheckBox (listing 6.14 oraz rysunek 6.4).

Listing 6.14. Tworzenie pl wyboru


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<CheckBox android:id=@+id/chickenCB android:text=Kurczak android:checked=true
android:layout_width=wrap_content android:layout_height="wrap_content" />
<CheckBox android:id=@+id/fishCB android:text="Ryba"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<CheckBox android:id=@+id/steakCB android:text="Stek" android:checked=true
android:layout_width="wrap_content" android:layout_height="wrap_content" />
</LinearLayout>

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

191

Rysunek 6.4. Zastosowanie kontrolki CheckBox

Zarzdzanie stanem pola wyboru odbywa si za pomoc wywoania metod setChecked()


lub toggle(). Informacje o stanie uzyskiwane s dziki wywoaniu metody isChecked().
Jeeli po zaznaczeniu pola wyboru lub usuniciu tego zaznaczenia ma nastpi okrelone wydarzenie, mona je zarejestrowa poprzez wywoanie metody setOnCheckedChangeListener()
wraz z implementacj interfejsu OnCheckedChangeListener. Konieczne bdzie take zaimplementowanie metody onCheckedChanged(), wywoywanej po zaznaczeniu lub usuniciu
zaznaczenia pola wyboru. Na listingu 6.15 przedstawiamy kod zajmujcy si obsug kontrolki CheckBox.
Listing 6.15. Stosowanie kontrolek CheckBox w kodzie
public class CheckBoxActivity extends Activity {
/** Wywoane podczas pierwszego utworzenia aktywnoci. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.checkbox);
CheckBox fishCB = (CheckBox)findViewById(R.id.fishCB);
if(fishCB.isChecked())
fishCB.toggle(); // jeeli pole wyboru byo zaznaczone, zaznaczenie zostaje usunite
fishCB.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
Log.v("CheckBoxActivity", "Pole wyboru Ryba jest teraz "
+ (isChecked?"zaznaczone":"niezaznaczone"));
}});
}
}

Ciekaw moliwoci konfiguracji obiektu nasuchujcego OnCheckedChangeListener jest


przekazywanie nowego stanu przycisku CheckBox. Alternatywnie mona by wykorzysta element nasuchujcy onClickListener, tak jak pokazano w przypadku podstawowych przyciskw. Podczas wywoania metody onClick() trzeba by wtedy samodzielnie zdefiniowa nowy
stan przycisku, odpowiednio oddajc jego waciwoci, a take zaprogramowa wywoanie

192 Android 3. Tworzenie aplikacji


metody isChecked() dla tego stanu. Listing 6.16 pokazuje w analogiczny sposb, jak mgby
wyglda kod w przypadku dodania wiersza android:onClick="myClickHandler" do definicji
XML przycisku CheckBox (pamitajmy, e funkcja ta zadziaa dopiero od wersji 1.6 Androida).
Listing 6.16. Stosowanie przyciskw CheckBox wraz z waciwoci android:onClick
public void myClickHandler(View view) {
switch(view.getId()) {
case R.id.steakCB:
Log.v("CheckBoxActivity", "Pole zaznaczenia Stek jest teraz " +
(((CheckBox)view).isChecked()?"zaznaczone":"niezaznaczone"));
}
}

Kontrolka RadioButton
Kontrolki przyciskw opcji s integralnym elementem kadego rodowiska projektowego
interfejsw UI. Przycisk opcji daje uytkownikowi kilka moliwoci wyboru, ale tylko jedna
z nich moe zosta zaznaczona. eby taki model dziaa skutecznie, przyciski opcji przewanie
nale do grupy. W takiej grupie w danym momencie moe by zaznaczony tylko jeden przycisk opcji.
eby utworzy grup przyciskw opcji w Androidzie, naley najpierw stworzy element Radio
a nastpnie wypeni go tymi przyciskami. Na listingu 6.17 zosta zaprezentowany przykad, a rysunek 6.5 stanowi jego ilustracj.

Group,

Listing 6.17. Stosowanie widetw RadioButton w Androidzie


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RadioGroup android:id="@+id/rBtnGrp" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<RadioButton android:id=@+id/chRBtn android:text="Kurczak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton android:id=@+id/fishRBtn android:text="Ryba" android:checked="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton android:id=@+id/stkRBtn android:text="Stek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RadioGroup>
</LinearLayout>

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

193

Rysunek 6.5. Uywanie przyciskw opcji

W Androidzie grupa opcji jest implementowana za pomoc klasy android.widget.RadioGroup,


a przycisk opcji poprzez klas android.widget.RadioButton.
Zwrmy uwag, e wszystkie przyciski opcji wewntrz grupy opcji s domylnie niezaznaczone,
chocia istnieje moliwo zaznaczenia jednego z nich w definicji XML, co zrobilimy w przypadku opcji Ryba. eby zaprogramowa takie predefiniowane zaznaczenie jednego z przyciskw
opcji, mona utworzy odniesienie do tego przycisku i wywoa metod setChecked():
RadioButton rbtn = (RadioButton)this.findViewById(R.id.stkRBtn);
rbtn.setChecked(true);

Mona rwnie wykorzysta metod toggle() do przeczania stanw przycisku. Podobnie


jak w przypadku kontrolki CheckBox, po wywoaniu metody setOnCheckedChangeListener()
wraz z implementacj interfejsu OnCheckedChangeListener przy kadym zdarzeniu zaznaczenia
przycisku opcji lub cofnicia zaznaczenia bdzie wywietlane powiadomienie. Istnieje tu jednak
pewna rnica. W istocie mamy tutaj do czynienia z inn klas ni poprzednio. Tym razem,
patrzc z technicznego punktu widzenia, jest to klasa RadioGroup.OnCheckedChangeListener,
a nie jak poprzednio CompoundButton.OnCheckedChangeListener.
W elemencie RadioGroup mog zosta umieszczone rwnie inne widoki, nie tylko przyciski
opcji. Na przykad na listingu 6.18 po ostatnim przycisku opcji zostaa dodana kontrolka TextView.
Warto te zauway, e przycisk opcji zosta umieszczony poza grup opcji.
Listing 6.18. Grupa opcji zawierajca nie tylko przyciski opcji
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RadioButton android:id="@+id/anotherRadBtn"
android:text="Na zewntrz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioGroup android:id="@+id/radGrp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton android:id="@+id/chRBtn"
android:text="Kurczak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton android:id="@+id/fishRBtn"

194 Android 3. Tworzenie aplikacji


android:text="Ryba"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton android:id="@+id/stkRBtn"
android:text="Stek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:text="Moje ulubione danie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RadioGroup>
</LinearLayout>

Listing 6.18 stanowi dowd, e mona umieci kontrolki niebdce czci klasy RadioGroup
wewntrz grupy opcji. Powinnimy take wiedzie, e grupa opcji moe wymusza zaznaczenie
tylko jednego przycisku jedynie wobec przyciskw opcji znajdujcych si w tym pojemniku.
Inaczej mwic, przycisk opcji o identyfikatorze anotherRadBtn nie bdzie objty dziaaniem
grupy opcji przedstawionej na listingu 6.18, poniewa nie jest jej elementem podrzdnym.
Istnieje moliwo programowego sterowania obiektami klasy RadioGroup. Na przykad mona
w ten sposb uzyska odniesienie do grupy opcji oraz doda przycisk opcji (lub inny rodzaj
kontrolki). Koncepcja ta zostaa zademonstrowana na listingu 6.19.
Listing 6.19. Dodanie w kodzie kontrolki RadioButton do pojemnika RadioGroup
RadioGroup radGrp = (RadioGroup)findViewById(R.id.radGrp);
RadioButton newRadioBtn = new RadioButton(this);
newRadioBtn.setText("Wieprzowina");
radGrp.addView(newRadioBtn);

Po zaznaczeniu przez uytkownika przycisku opcji w grupie opcji nie bdzie mona usun
tego zaznaczenia za pomoc powtrnego kliknicia. Jedynym sposobem usunicia zaznaczenia wszystkich przyciskw opcji w tej grupie jest wywoanie metody clearCheck() w obiekcie
RadioGroup.
Oczywicie, Czytelnik moe zechcie wykorzysta klas RadioGroup do czego bardziej interesujcego. Prawdopodobnie nie chce za kadym razem sprawdza, czy kady przycisk RadioButton
jest zaznaczony. Na szczcie klasa RadioGroup posiada kilka metod, ktre mog si tu przyda.
Przedstawiamy je na listingu 6.20. Odpowiednik XML tego kodu znajduje si na listingu 6.18.
Listing 6.20. Wykorzystanie klasy RadioGroup w sposb programowy
public class RadioGroupActivity extends Activity {
protected static final String TAG = "RadioGroupActivity";

/** Wywoana podczas pierwszego utworzenia aktywnoci. */


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

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

195

RadioGroup radGrp = (RadioGroup)findViewById(R.id.radGrp);


int checkedRadioButtonId = radGrp.getCheckedRadioButtonId();
radGrp.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup arg0, int id) {
switch(id) {
case -1:
Log.v(TAG, "Wybrane elementy wyczyszczone!");
break;
case R.id.chRBtn:
Log.v(TAG, "Wybrano kurczaka");
break;
case R.id.fishRBtn:
Log.v(TAG, "Wybrano ryb");
break;
case R.id.stkRBtn:
Log.v(TAG, "Wybrano stek");
break;
default:
Log.v(TAG, "He?");
break;
}
}});
}
}

Zawsze mona pobra najnowszy zaznaczony obiekt RadioButton za pomoc metody


getCheckedRadioButtonId(), ktra zwraca identyfikator zaznaczonego obiektu lub warto -1, jeli adna opcja nie zostao zaznaczona (by moe nie wprowadzono domylnej opcji,
a uytkownik jeszcze adnej nie wybra). Poprzednio zademonstrowalimy j w metodzie
onCreate(), w rzeczywistoci jednak powinnimy j wykorzysta w odpowiednim momencie
w celu odczytania biecego zaznaczenia uytkownika. Moemy rwnie wprowadzi obiekt
nasuchujcy, ktry by od razu informowa o wybraniu przez uytkownika jakiej opcji. Zwrmy uwag, e metoda onCheckedChanged() przyjmuje parametr RadioGroup, co pozwala nam
na uycie tego samego obiektu nasuchujcego OnCheckedChangeListener dla wielu klas
RadioGroup. By moe niektrzy Czytelnicy zauwayli warto -1 instrukcji switch. Opcja ta
zostanie wybrana, jeli przyciski wyboru opcji klasy RadioGroup zostan wyczyszczone programistycznie.

Kontrolka ImageView
Jedn z najwaniejszych kontrolek, ktrych jeszcze nie omwilimy, jest ImageView. Jest ona
stosowana do wywietlania obrazw, pochodzcych z plikw, dostawcw treci lub zasobw,
na przykad typu drawable. Mona rwnie zdefiniowa wycznie kolor, ktry kontrolka
ImageView bdzie wywietlaa. Na listingu 6.21 zaprezentowano kilka kontrolek ImageView,
a nastpnie przedstawiono przykadowy kod ukazujcy proces tworzenia tej klasy.

196 Android 3. Tworzenie aplikacji


Listing 6.21. Kontrolki ImageView w pliku XML oraz w kodzie
<ImageView android:id="@+id/image1"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/icon" />
<ImageView android:id="@+id/image2"
android:layout_width="125dip" android:layout_height="25dip"
android:src="#555555" />
<ImageView android:id="@+id/image3"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<ImageView android:id="@+id/image4"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/manatee02"
android:scaleType="centerInside"
android:maxWidth="35dip" android:maxHeight="50dip"
/>
ImageView imgView = (ImageView)findViewById(R.id.image3);
imgView.setImageResource( R.drawable.icon );
imgView.setImageBitmap(BitmapFactory.decodeResource(
this.getResources(), R.drawable.manatee14) );
imgView.setImageDrawable(
Drawable.createFromPath("/mnt/sdcard/dave2.jpg") );
imgView.setImageURI(Uri.parse("file://mnt/sdcard/dave2.jpg"));

W tym przykadzie definiujemy cztery obrazy za pomoc jzyka XML. Pierwszy stanowi po prostu ikon naszej aplikacji. Drugi jest szarym paskiem, szerokim, ale niezbyt wysokim. Trzecia definicja nie wskazuje rda obrazu w kodzie XML, natomiast przypisuje identyfikator
(image3), za pomoc ktrego mona programowo ustawi obraz. Czwarty obraz jest kolejnym
z zasobw typu drawable, dla ktrego nie tylko okrelamy ciek do pliku rdowego, lecz
rwnie jego maksymalne rozmiary, a take wskazujemy, co ma si sta z tym obrazem, jeli
przekroczy naoone ograniczenia rozmiarw. W tym przypadku klasa ImageView wyrodkuje
go i przeskaluje do zaoonych rozmiarw.
W kodzie Java z listingu 6.21 widzimy kilka sposobw ustawiania obrazu image3. Oczywicie,
najpierw musimy uzyska odniesienie do kontrolki ImageView za pomoc identyfikatora
zasobw. Pierwsza metoda ustawiania, setImageResource(), zwyczajnie wykorzystuje identyfikator obrazu do jego zlokalizowania oraz dostarczenia go kontrolce ImageView. Druga metoda ustawiania korzysta z klasy BitmapFactory do wczytania zasobu obrazu do obiektu Bitmap,
a nastpnie ustanawia kontrolk ImageView wobec tego obiektu. Warto wiedzie, e w obiekcie
Bitmap mona wprowadza pewne modyfikacje przed wczytaniem go do kontrolki ImageView,
jednak w naszym przykadowym kodzie niczego nie zmieniamy. Ponadto klasa BitmapFactory
zawiera kilka metod sucych do tworzenia obiektu Bitmap, na przykad z tablicy bajtw albo
z klasy InputStream. Moglibymy wykorzysta metod InputStream do odczytania obrazu
z serwera sieciowego, utworzy obraz Bitmap, a nastpnie ustawi klas ImageView.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

197

Trzecie ustawienie okrela obiekt Drawable jako rdo obrazu. W naszym przykadzie rdo
to wskazuje plik znajdujcy si na karcie SD. eby opisywany kod zadziaa, trzeba umieci
na karcie SD plik z odpowiedni nazw. Podobnie jak miao to miejsce w przypadku klasy
BitmapFactory, klasa Drawable posiada kilka metod pozwalajcych na tworzenie obiektw
typu Drawable, w tym ze strumienia XML.
Ostatnia metoda ustawiania obrazu polega na pobraniu identyfikatora URI obrazu i wykorzystaniu go jako rda obrazu. Nie naley jednak sdzi, e identyfikator URI kadego obrazu nadaje si do tego celu. Ta metoda suy do wykorzystywania obrazw dostpnych lokalnie, znajdujcych si w urzdzeniu, a nie obrazw wyszukiwanych w internecie. Aby takie obrazy internetowe
mogy by rdami dla kontrolki ImageView, najlepiej zastosowa klasy BitmapFactory oraz
InputStream.

Kontrolki daty i czasu


Kontrolki daty i czasu s standardem w wielu zestawach widetw. W Androidzie zawarto kilka
kontrolek zwizanych z dat i czasem, niektre z nich zostan omwione w nastpnych podpunktach. W szczeglnoci zajmiemy si kontrolkami DatePicker, TimePicker, DigitalClock
oraz AnalogClock.

Kontrolki DatePicker oraz TimePicker


Zgodnie z nazwami kontrolka DatePicker suy do wybierania daty, natomiast kontrolka
TimePicker umoliwia ustawianie godziny. Listing 6.22 oraz rysunek 6.6 przedstawiaj przykady tych kontrolek.
Listing 6.22. Kontrolki DatePicker oraz TimePicker w kodzie XML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="@+id/dateDefault"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
<DatePicker android:id="@+id/datePicker"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<TextView android:id="@+id/timeDefault"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
<TimePicker android:id="@+id/timePicker"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
</LinearLayout>

Jeeli przyjrze si ukadowi graficznemu w kodzie XML, to mona stwierdzi, e definiowanie


tych kontrolek nie jest skomplikowane. Podobnie jak w przypadku innych kontrolek w Androidzie, take i w tym przypadku mona zaprogramowa je, eby uruchamiay si lub eby
mona byo pobiera z nich dane. Na przykad kod inicjalizacji tych kontrolek moe wyglda
tak jak na listingu 6.23.

198 Android 3. Tworzenie aplikacji

Rysunek 6.6. Interfejsy UI kontrolek DatePicker i TimePicker


Listing 6.23. Inicjalizacja daty w kontrolce DatePicker oraz godziny w kontrolce TimePicker
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.datetimepicker);
TextView dateDefault = (TextView)findViewById(R.id.dateDefault);
TextView timeDefault = (TextView)findViewById(R.id.timeDefault);
DatePicker dp = (DatePicker)this.findViewById(R.id.datePicker);

// Warto miesica rozpoczyna si od zera. Trzeba doda 1 do wywietlanej wartoci


dateDefault.setText("Domylna data " + (dp.getMonth() + 1) + "/" +
dp.getDayOfMonth() + "/" + dp.getYear());

// A tutaj odejmujemy 1 od wartoci Grudzie (12), eby by wywietlany waciwy miesic


dp.init(2008, 11, 10, null);
TimePicker tp = (TimePicker)this.findViewById(R.id.timePicker);
java.util.Formatter timeF = new java.util.Formatter();
timeF.format("Domylny czas %d:%02d", tp.getCurrentHour(),
tp.getCurrentMinute());
timeDefault.setText(timeF.toString());
tp.setIs24HourView(true);
tp.setCurrentHour(new Integer(10));
tp.setCurrentMinute(new Integer(10));
}
}

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

199

Kod na listingu 6.23 ustawia dat na 10 grudnia 2008 roku. Zwrmy uwag, e dla nazw miesicy warto wewntrzna rozpoczyna si od zera, co oznacza, e stycze posiada warto 0,
a grudzie 11. W przypadku klasy TimePicker wybrano godzin 10:10. Warto wiedzie,
e kontrolka obsuguje wywietlanie czasu w formacie dwudziestoczterogodzinnym. Jeeli
w tych kontrolkach nie zostan ustawione adne wartoci, domylnymi bd aktualne data i czas,
skonfigurowane w urzdzeniu.
Android wykorzystuje rwnie te kontrolki jako okna dialogowe, na przykad DatePickerDialog oraz TimePickerDialog. Kontrolki te przydaj si, w przypadku gdy maj zosta wywietlone uytkownikowi w celu zmuszenia go do dokonania jakiego wyboru. Okna dialogowe
zostay szczegowo omwione w rozdziale 8.

Kontrolki DigitalClock i AnalogClock


Na rysunku 6.7 przedstawilimy dostpne w Androidzie kontrolki
AnalogClock.

DigitalClock

oraz

Rysunek 6.7. Zastosowanie kontrolek AnalogClock i DigitalClock

Jak wida, w zegarze cyfrowym mona dodatkowo odczyta sekundy. Zegar analogowy w Androidzie posiada dwie wskazwki, jedna wskazuje godziny, a druga minuty. Aby umieci te
zegary w ukadzie graficznym, moemy wykorzysta wzy XML widoczne na listingu 6.24.
Listing 6.24. Dodawanie obiektw DigitalClock i AnalogClock w jzyku XML
<DigitalClock
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<AnalogClock
android:layout_width="wrap_content" android:layout_height="wrap_content" />

Kontrolki te pozwalaj jedynie na wywietlanie biecego czasu, nie zapewniaj jednak moliwoci modyfikowania daty ani czasu. S to zatem zwyke zegary, ktrych jedyn funkcj jest
wywietlanie aktualnej godziny. Zatem w przypadku potrzeby zmiany daty lub czasu naley
stosowa kontrolki DatePicker i TimePicker lub DatePickerDialog i TimePickerDialog.
Przydatnym szczegem jest, e obydwie kontrolki DigitalClock oraz AnalogClock
bd automatycznie aktualizowa czas, bez koniecznoci ustawiania czegokolwiek. Oznacza
to, e w przypadku zegara cyfrowego sekundy bd same odliczane, a w zegarze analogowym
wskazwki bd si poruszay samoistnie, bez potrzeby zapewniania dodatkowej obsugi.

200 Android 3. Tworzenie aplikacji

Kontrolka MapView
Kontrolka com.google.android.maps.MapView umoliwia wywietlanie mapy. Mona utworzy
egzemplarz tej kontrolki w pliku XML ukadu graficznego lub w kodzie Java, jednak wykorzystujca j aktywno musi rozszerzy klas MapActivity. Klasa ta obsuguje przetwarzanie
wielowtkowych da adowania mapy, przeprowadzanie procesu buforowania i tak dalej.
Na listingu 6.25 zosta zaprezentowany przykad utworzenia obiektu MapView.
Listing 6.25. Utworzenie kontrolki MapView w pliku XML ukadu graficznego
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.google.android.maps.MapView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:enabled="true"
android:clickable="true"
android:apiKey="myAPIKey"
/>
</LinearLayout>

Szczegowe informacje na temat kontrolki MapView zostay zawarte w rozdziale 17., w ktrym
opiszemy usugi oparte na wyznaczaniu pooenia geograficznego. Znajduj si tam rwnie
informacje, w jaki sposb uzyska wasny klucz API mapowania.

Dziaanie adapterw
Zanim zajmiemy si kontrolkami listy w Androidzie, musimy przedstawi pojcie adaptera.
Kontrolki listy su do wywietlania zbiorw danych. Jednak zamiast uywa jednego typu kontrolki zarwno do obsugi wywietlania, jak i zarzdzania danymi, Android dzieli te dwa zadania
pomidzy kontrolki listy i adaptery. Kontrolki listy s rozszerzeniem klasy android.widget.
AdapterView i dziel si na nastpujce kategorie: ListView, GridView, Spinner oraz Gallery (rysunek 6.8).
Sama klasa AdapterView rozszerza klas android.widget.ViewGroup, co oznacza, e widoki
ListView, GridView i inne s kontrolkami-pojemnikami. Innymi sowy, kontrolki listy wywietlaj zbir widokw potomnych. Zadaniem adaptera jest zarzdzanie danymi pojemnika
AdapterView oraz dostarczenie mu potomnych widokw. Przyjrzyjmy si, jak to dziaa, analizujc
adapter SimpleCursorAdapter.

Zapoznanie si z klas SimpleCursorAdapter


Adapter SimpleCursorAdapter zosta naszkicowany na rysunku 6.9.
Zrozumienie tego rysunku odgrywa niebagateln rol. Po lewej stronie widzimy klas Adapter
potomnych widokw
reprezentowane przez
zestaw wynikowy z wierszami danych, pochodzcymi z zapytania wysanego do dostawcy treci.

View. W tym przykadzie jest to pojemnik ListView utworzony z


TextView. Po prawej stronie mamy do czynienia z danymi; tu s one

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

201

Rysunek 6.8. Hierarchia klasy AdapterView

Rysunek 6.9. Klasa SimpleCursorAdapter

Aby odwzorowa dane w kontrolce ListView, klasa SimpleCursorAdapter musi posiada dostp do identyfikatora potomnego ukadu graficznego. Ten potomny ukad musi opisywa ukad
graficzny wszystkich elementw danych (widocznych po prawej stronie), ktre maj zosta wywietlone po lewej stronie. W tym przypadku ukad graficzny nie rni si od ukadw, ktre
prezentowalimy podczas omawiania aktywnoci, musi on tylko opisywa ukad graficzny pojedynczego wiersza z pojemnika ListView. Jeli na przykad posiadamy zestaw wynikowy dostarczony od dostawcy treci Contacts, a w pojemniku ListView chcemy wywietla wycznie
nazw danego kontaktu, trzeba okreli ukad graficzny opisujcy wygld takiego pola zawierajcego nazw kontaktu. Aby w kadym wierszu pojemnika ListView wywietla nazw i obraz,
ktre pochodz z zestawu wynikowego, taki ukad graficzny musiaby definiowa sposb wywietlania nazwy oraz obrazu.
Nie oznacza to wcale, e trzeba dostarcza oddzieln specyfikacj ukadu graficznego dla kadego
pola w zestawie wynikowym ani e w zestawie wynikowym maj znale si fragmenty danych, za
pomoc ktrych trzeba wypeni wszystkie wiersze pojemnika ListView. Jako przykad za moment pokaemy, w jaki sposb mona wybiera wiersze za pomoc pl wyboru umieszczonych
w widoku ListView, gdzie te pola nie musz by zestawami danych z zestawu wynikowego. Zademonstrujemy take, w jaki sposb uzyska dostp do danych z zestawu wynikowego, jeli te
dane nie s czci pojemnika ListView. A chocia cay czas rozmawiamy o widokach ListView,

202 Android 3. Tworzenie aplikacji


TextView, kursorach i zestawach wynikowych, naley pamita, e koncepcja adapterw posiada

oglniejszy charakter. Wracajc do rysunku 6.9, obszar po lewej stronie moe by galeri, natomiast prawa strona prost tabel z obrazami. Na razie nie utrudniajmy sobie jednak zadania
i przyjrzyjmy si dokadniej klasie SimpleCursorAdapter.
Konstruktor klasy SimpleCursorAdapter wyglda nastpujco:
SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to)

Adapter ten przeksztaca krotk w kursorze do widoku podrzdnego wzgldem kontrolkipojemnika. Definicja widoku potomnego zostaa umieszczona w zasobie XML (parametr
childLayout). Zwrmy uwag, e krotka w kursorze moe zawiera wiele kolumn. Aby
wskaza, ktre maj zosta zaznaczone w adapterze SimpleCursorAdapter, definiuje si
tablic z nazwami kolumn. W tym celu stosuje si parametr from.
W podobny sposb, poniewa kada wybrana kolumna musi zosta odwzorowana w obiekcie
klasy View ukadu graficznego, naley utworzy identyfikatory dla parametru to. Pomidzy
wybran kolumn a kontrolk View wywietlajc dane w kolumnie istnieje odwzorowanie
typu jeden do jednego, zatem tablice wartoci parametrw from i to musz zawiera t sam
liczb elementw. Jak ju wczeniej wspomnielimy, widok potomny moe by innym typem
widoku; to wcale nie musi by kontrolka TextView. Mona zamiast tego wprowadzi na przykad kontrolk ImageView.
Widok ListView i nasz adapter wsppracuj ze sob w przemylany sposb. Kiedy pojemnik
ListView prbuje wywietli wiersz danych, wywouje metod getView() adaptera i przekazuje
pooenie wywietlanego wiersza. Adapter w odpowiedzi tworzy odpowiedni widok potomny
za pomoc ukadu graficznego ustanowionego w swoim konstruktorze, uwzgldniajc dane
pobrane z waciwego rekordu pochodzcego z zestawu wynikowego. Zatem widok ListView
nie musi obsugiwa danych po stronie adaptera. Widok ten wycznie wywouje potrzebne
potomne widoki. Jest to punkt krytyczny, gdy w ten sposb pojemnik ListView nie musi tworzy oddzielnego widoku potomnego dla kadego wiersza danych. Pojemnik ListView tworzy
tylko tyle widokw potomnych, ile trzeba wywietli. Z technicznego punktu widzenia, jeeli
przewidujemy wywietlanie tylko dziesiciu wierszy, widok ListView mgby utworzy tylko
dziesi potomnych ukadw graficznych, nawet jeli zestaw wynikowy skadaby si z setek rekordw. W rzeczywistoci system moe wywoywa wicej widokw potomnych, poniewa zazwyczaj Android przechowuje dodatkowe obiekty na wypadek potrzeby szybszego wywietlenia
nowego wiersza. Wniosek wynika z tego taki, e potomne widoki pojemnika ListView mog
ulega cigemu przetwarzaniu. Zajmiemy si tym dokadniej w dalszej czci ksiki.
Na rysunku 6.9 mona zauway pewn elastyczno w stosowaniu adapterw. Poniewa kontrolka listy korzysta z adaptera, mona podstawia rne rodzaje adapterw w zalenoci od
rodzaju danych oraz widokw podrzdnych. Jeeli na przykad klasa AdapterView nie bdzie
zapeniana danymi z dostawcy treci lub bazy danych, nie ma potrzeby, eby uywa adaptera
SimpleCursorAdapter. Mona wtedy zastosowa jeszcze prostszy adapter ArrayAdapter.

Zapoznanie si z klas ArrayAdapter


Klasa ArrayAdapter jest najprostszym adapterem dostpnym w Androidzie. Jej grup docelow
s kontrolki listy. Dziaanie obiektw tej klasy opiera si na zaoeniu, e kontrolki TextView
reprezentuj elementy listy (na przykad widoki potomne). Utworzenie adaptera ArrayAdapter
moe by bardzo proste:

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

203

ArrayAdapter<String> adapter = new ArrayAdapter<String>(


this,android.R.layout.simple_list_item_1,
new string[]{"Dave","Satya",Dylan});

W dalszym cigu przekazujemy kontekst (np. this) oraz identyfikator zasobu potomnego ukadu graficznego. Zamiast jednak przekazywa tablic from specyfikacji pola danych, jako rzeczywiste dane przekazujemy tablic cigw znakw. Nie przekazujemy kursora ani tablicy identyfikatorw zasobw obiektu View. Zakadamy tutaj, e potomny ukad graficzny skada si
z pojedynczej kontrolki TextView oraz e bdzie on wykorzystywany przez klas ArrayAdapter
jako miejsce docelowe dla cigw znakw przechowywanych w tablicy danych.
Zaprezentujemy teraz przyjemny skrt dla identyfikatora zasobu childLayout. Zamiast tworzy
wasny plik ukadu graficznego do obsugi obiektw listy, moemy skorzysta z predefiniowanych ukadw graficznych Androida. Zauwamy, e przedrostkiem w identyfikatorze potomnego
ukadu graficznego jest android.. Zamiast przeszukiwa lokalny katalog /res, Android przeszukuje swj wasny. Mona przejrze ten folder poprzez otwarcie katalogu zawierajcego zestaw Android SDK i wybranie platforms/<wersja-androida>/data/res/layout. Znajdziemy tu
element simple_list_item_1.xml, w ktrym wida definicj prostej kontrolki TextView. To
wanie t kontrolk wykorzystuje klasa ArrayAdapter do utworzenia widoku (w metodzie
getView()), ktry zostanie przekazany pojemnikowi ListView. Warto przejrze zawarte tu
katalogi, eby znale predefiniowane ukady graficzne dla wszelakich rodzajw zastosowa.
W dalszej czci ksiki wykorzystamy jeszcze niektre z nich.
Klasa ArrayAdapter posiada rwnie inne konstruktory. Jeeli potomny ukad graficzny nie jest
prostym widokiem TextView, mona przekaza identyfikator ukadu graficznego wiersza oraz
identyfikator kontrolki TextView otrzymujcej dane. Jeli nie mamy przygotowanej do przekazania tablicy cigw znakw, moemy zastosowa metod createFromResource(). Listing 6.26
stanowi przykad, w ktrym tworzymy klas Adapter dla obiektu typu Spinner:
Listing 6.26. Utworzenie adaptera ArrayAdapter z pliku zasobw typu string
<Spinner android:id="@+id/spinner"
android:layout_width="wrap_content" android:layout_height="wrap_content" />

Spinner spinner = (Spinner) findViewById(R.id.spinner);


ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.planets, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);

<?xml version="1.0" encoding="utf-8"?>

<!-- Plik ten znajduje si w /res/values/planets.xml -->


<resources>
<string-array name="planets">
<item>Merkury</item>
<item>Wenus</item>
<item>Ziemia</item>
<item>Mars</item>
<item>Jowisz</item>

204 Android 3. Tworzenie aplikacji


<item>Saturn</item>
<item>Uran</item>
<item>Neptun</item>
</string-array>
</resources>

Listing 6.26 skada si z trzech czci. Pierwsza stanowi ukad graficzny obiektu Spinner zapisany w pliku XML. Druga cz, napisana w jzyku Java, ukazuje nam, w jaki sposb mona
utworzy klas ArrayAdapter, ktrej rdo danych zostao zdefiniowane w pliku zasobw typu
String. Za pomoc tej metody moemy nie tylko uzewntrzni zawarto listy w pliku XML,
lecz rwnie korzysta ze zlokalizowanych wersji list. Obiektami typu Spinner zajmiemy si
nieco pniej, na razie wystarczy nam wiedza, e obiekt tego typu posiada widok pozwalajcy na
wywietlenie aktualnie wybranej wartoci oraz widok listy elementw, ktre mona wybra.
W zasadzie mamy tu do czynienia z list rozwijaln. Trzeci cz listingu 6.26 stanowi plik zasobw umieszczony w katalogu /res/values/planets.xml, ktry jest wczytywany w celu uruchomienia klasy ArrayAdapter.
Warto wspomnie, e klasa ArrayAdapter pozwala na dynamiczne modyfikowanie wykorzystywanych danych. Na przykad metoda add() dodaje now warto na kocu tablicy. Metoda
insert() wprowadza now warto w okrelonej pozycji tablicy, natomiast metoda remove()
usuwa obiekt z tablicy. Moemy take wywoa metod sort(), ktra uporzdkuje tablic.
Oczywicie, po wykonaniu tych wszystkich czynnoci tablica danych zostaje zdesynchronizowana z pojemnikiem ListView, zatem naley wtedy wywoa metod notifyDataSetChanged()
adaptera. W ten sposb zsynchronizujemy ponownie kontrolk ListView z adapterem.
Ponisza lista podsumowuje rodzaje adapterw dostpnych w systemie Android:
ArrayAdapter<T>. Adapter ten znajduje si na szczycie oglnej tabeli wasnych
obiektw. Jest przeznaczony do stosowania z kontrolkami ListView.
CursorAdapter. Adapter ten, rwnie uywany przy kontrolkach ListView,
dostarcza dane listy poprzez kursor.
SimpleAdapter. Jak sama nazwa sugeruje, mamy do czynienia z prostym adapterem.
Zazwyczaj uywany jest do zapeniania listy danymi statycznymi (rwnie z zasobw).
ResourceCursorAdapter. Ten adapter rozszerza klas CursorAdapter i tworzy
widoki z zasobw.
SimpleCursorAdapter. Adapter ten rozszerza klas ResourceCursorAdapter i tworzy
widoki TextView/ImageView z kolumn w kursorze. Widoki s zdefiniowane w zasobach.
Przedstawilimy wystarczajco dobrze zagadnienie adapterw, aby zaprezentowa rzeczywiste przykady korzystania z nich oraz z kontrolek listy (znanych take pod nazw kontrolek
AdapterView). Do dziea.

Wykorzystywanie adapterw
wraz z kontrolkami AdapterView
Po zapoznaniu si z tematyk adapterw czas zaprzc je do pracy i dostarczy im dane przesyane do kontrolek listy. W tym podrozdziale rozpoczniemy od omwienia pierwszej kontrolki
tego typu ListView. Nastpnie przyjrzymy si mechanizmowi tworzenia wasnego adaptera,
a na kocu opiszemy inne rodzaje kontrolek listy: GridView, obiekty typu Spinner i galerie.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

205

Podstawowa kontrolka listy ListView


Kontrolka ListView wywietla pionowo list elementw. Inaczej mwic, jeli posiadamy list
przegldanych elementw i przekracza ona rozmiary ekranu, moemy j przewin, aby zobaczy pozostae elementy. Przewanie stosuje si t kontrolk poprzez napisanie nowej aktywnoci, rozszerzajcej klas android.app.ListActivity. Klasa ListActivity zawiera kontrolk
ListView, a dane s w niej umieszczane poprzez wywoanie metody setListAdapter(). Jak
ju wczeniej omwiono, adaptery cz kontrolki listy z danymi i bior udzia w przygotowaniu widokw potomnych dla tych kontrolek. Elementy w pojemniku ListView mona klikn,
co spowoduje natychmiastow odpowied, mona te je zaznacza, dziki czemu mona pniej pracowa na zbiorze wybranych elementw. Rozpoczniemy od najprostszych czynnoci
i stopniowo bdziemy dodawa nowe funkcje.

Wywietlanie wartoci w kontrolce ListView


Rysunek 6.10 przedstawia kontrolk ListView w jej najprostszej postaci.

Rysunek 6.10. Zastosowanie kontrolki ListView

W kolejnym wiczeniu wypenimy cay ekran kontrolk ListView, wic nie trzeba jej nawet
okrela w pliku ukadu graficznego main.xml. Na listingu 6.27 umieszczono kod Java kontrolki
ListActivity.
Listing 6.27. Dodawanie elementw do kontrolki ListView
public class ListDemoActivity extends ListActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Cursor c = managedQuery(People.CONTENT_URI,
null, null, null, People.NAME);
String[] cols = new String[] {People.NAME};

206 Android 3. Tworzenie aplikacji


int[] views = new int[] {android.R.id.text1};
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,
c, cols, views);
this.setListAdapter(adapter);
}
}

Kod z listingu 6.27 powoduje utworzenie kontrolki ListView, zapenionej pobran z urzdzenia list kontaktw. W tym przykadzie damy od urzdzenia listy kontaktw. W celach demonstracyjnych zaznaczamy wszystkie pola pojemnika Contacts (na przykad za pomoc pierwszego
parametru null w metodzie managedQuery()) i stosujemy sortowanie wedug wartoci pola
People.NAME (do czego suy ostatni parametr we wspomnianej metodzie managedQuery()).
Nastpnie tworzymy projekcj (kolumn), dziki ktrej wybieramy wycznie nazwy kontaktw
dla pojemnika ListView projekcja definiuje interesujce nas kolumny. Kolejnym etapem jest
utworzenie tablicy identyfikatorw zasobw (widokw), ktra pozwalaaby na odzwierciedlanie
nazwy kolumny (People.NAME) wobec kontrolki TextView (android.R.id.text1). W kolejnym etapie tworzymy adapter kursora i konfigurujemy adapter listy. Klasa adaptera jest przystosowana do przegldania krotek w danych rdowych i pobierania nazw kontaktw w sposb umoliwiajcy zapenienie interfejsu uytkownika.
Musimy wykona jeszcze jedn czynno, zanim aplikacja zadziaa. Poniewa w tym wiczeniu
aplikacja uzyskuje dostp do listy kontaktw telefonu, naley przydzieli jej odpowiednie uprawnienia. Informacje dotyczce zabezpiecze przedstawiono w rozdziale 10., teraz wic wyjanimy
jedynie, w jaki sposb udostpni dane kontrolce ListView. Naley dwukrotnie klikn nazw
pliku AndroidManifest.xml w projekcie, a nastpnie wybra zakadk Permissions. W dalszej
kolejnoci trzeba klikn przycisk Add, zaznaczy opcj Uses Permission i na kocu klikn OK. Naley przewin list Name a do pozycji android.permission.READ_CONTACTS.
Okno rodowiska Eclipse powinno wyglda tak jak na rysunku 6.11. Mona teraz zapisa
plik AndroidManifest.xml i uruchomi aplikacj w emulatorze. Moe zaistnie potrzeba dodania
kontaktw za pomoc aplikacji Kontakty, zanim jakiekolwiek nazwiska pojawi si w naszym
przykadowym programie.
Zauwamy, e metoda onCreate() nie ustanawia widoku treci danej aktywnoci. Poniewa
bazowa klasa ListActivity zawiera ju kontrolk ListView, naley jedynie zapewni jej dostp do danych. Wykorzystalimy w tym przykadzie kilka skrtw; pierwszy polega na zastosowaniu gwnego ukadu graficznego w pojemniku ListView. Wykorzystalimy rwnie predefiniowany ukad graficzny Androida w potomnym widoku (identyfikator android.R.layout.
simple_list_item_1), w ktrym znajduje si predefiniowana kontrolka TextView (android.
R.id.text1). Podsumowujc, ta konfiguracja wcale nie jest skomplikowana.

Elementy reagujce na kliknicie w pojemniku ListView


Oczywicie, po uruchomieniu tej przykadowej aplikacji atwo zauway, e moemy przewija
list kontaktw w gr i w d, ale nic poza tym. W jaki sposb mona zrobi co bardziej interesujcego z t aplikacj, na przykad uruchomi aplikacj Kontakty po klikniciu przez uytkownika jednego z elementw pojemnika ListView? Na listingu 6.28 znajdziemy modyfikacj
wczeniejszego kodu, umoliwiajc reagowanie na czynnoci uytkownika.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

207

Rysunek 6.11. Modyfikowanie pliku AndroidManifest.xml, umoliwiajce uruchomienie aplikacji


Listing 6.28. Przyjmowanie danych wprowadzanych przez uytkownika w pojemniku ListView
public class ListViewActivity2 extends ListActivity implements OnItemClickListener
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
ListView lv = getListView();
Cursor c = managedQuery(People.CONTENT_URI,
null, null, null, People.NAME);
String[] cols = new String[]{People.NAME};
int[] views = new int[] {android.R.id.text1};
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,
c, cols, views);
this.setListAdapter(adapter);
lv.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> adView, View target, int position, long id) {
Log.v("ListViewActivity", "w metodzie onItemClick z " + ((TextView)
target).getText()
+
". Pozycja = " + position + ". Id = " + id);
Uri selectedPerson = ContentUris.withAppendedId(
People.CONTENT_URI, id);

208 Android 3. Tworzenie aplikacji


Intent intent = new Intent(Intent.ACTION_VIEW, selectedPerson);
startActivity(intent);
}
}

W efekcie nasza aktywno implementuje interfejs onItemClickListener, co oznacza, e bdziemy otrzymywa wywoanie zwrotne za kadym razem, gdy uytkownik kliknie jaki element pojemnika ListView. Jak wida po zapoznaniu si z metod onItemClick(), otrzymujemy
wiele informacji na temat kliknitego elementu, w tym takie jak kliknity widok, pooenie
kliknitego elementu w pojemniku ListView oraz zgodny z adapterem identyfikator tego elementu. Poniewa wiemy, e pojemnik ListView skada si z kontrolek TextView, zakadamy,
e otrzymalimy wanie tak kontrolk i e generujemy j przez wywoanie metody getText(),
sucej do odczytania nazwy kontaktu. Warto pooenia reprezentuje umiejscowienie elementu na penej licie obiektw w pojemniku ListView i jest ona liczona od zera. Zatem pierwszy element na licie posiada przypisan warto 0.
Warto identyfikatora cakowicie zaley od adaptera oraz rda danych. W naszym przykadzie wysyamy zapytania do dostawcy treci Contacts, zatem zgodnie z adapterem mamy
tu do czynienia z identyfikatorem _ID rekordu od dostawcy treci. Jednak w innych przypadkach rdo danych moe nie pochodzi od dostawcy treci, wic nie naley sdzi, e moemy
zawsze tworzy identyfikator URI, jak w omawianym przykadzie. Jeli korzystalimy z adaptera
ArrayAdapter odczytujcego wartoci z pliku XML zasobw, uzyskany przez nas identyfikator
bdzie prawdopodobnie stanowi pozycj danej wartoci w tablicy danych oraz, w istocie, moe
by dokadnie wartoci tej pozycji.
Podczas omawiania klasy ArrayAdapter wspominalimy, e metoda notifyDataSetChanged()
suy do synchronizowania adaptera z pojemnikiem ListView w przypadku modyfikowania
danych. Przeprowadmy may eksperyment na naszym przykadzie. Kliknijmy jeden z elementw
listy, co spowoduje wywietlenie aplikacji Kontakty. Edytujmy teraz ten kontakt i zmiemy jego
nazw. Naley klikn przycisk Gotowe, a nastpnie Cofnij, aby wrci do naszej aplikacji. Zauwaymy, e nazwa kontaktu w pojemniku ListView zostaa automatycznie zaktualizowana.
wietne, nieprawda? Pojemnik ListView zosta automatycznie zaktualizowany za pomoc klasy
SimpleCursorAdapter i dostawcy treci Contacts. Jednak w przypadku klasy ArrayAdapter
trzeba samodzielnie przywoa metod notifyDataSetChanged().
To nie byo wcale takie trudne. Utworzylimy wasny pojemnik ListView zawierajcy nazwy
kontaktw, a po klikniciu danego elementu zostaa uruchomiona aplikacja Kontakty z informacjami o wybranej osobie. A co w przypadku, gdy chcemy najpierw zaznaczy kilka nazwisk
i w jaki sposb dziaa na takiej grupie? W nastpnej aplikacji zmodyfikujemy ukad graficzny
listy i dodamy do niej pola wyboru, nastpnie za wprowadzimy do interfejsu uytkownika przycisk pozwalajcy na przetwarzanie podgrupy zaznaczonych elementw.

Dodawanie innych kontrolek do pojemnika ListView


Jeeli chcemy wprowadzi dodatkowe kontrolki do gwnego ukadu graficznego, moemy stworzy wasny plik XML ukadu graficznego, wstawi do niego pojemnik ListView i doda podane
kontrolki. Mona na przykad wstawi w interfejsie UI przycisk poniej kontrolki ListView,
ktry pozwala na wysanie listy zaznaczonych elementw, co zostao pokazane na rysunku 6.12.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

209

Rysunek 6.12. Dodatkowy przycisk umoliwiajcy uytkownikowi wysanie listy zaznaczonych elementw

Gwny ukad graficzny naszej aplikacji zosta umieszczony na listingu 6.29 i zawiera definicj
interfejsu aktywnoci kontrolek ListView oraz Button.
Listing 6.29. Przesonicie kontrolki ListView, do ktrej odnosi si klasa ListActivity
<?xml version="1.0" encoding="utf-8"?>

<!-- Ten plik umieszczony jest w podkatalogu /res/layout/list.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"/>
<Button android:id=@+id/btn android:onClick=doClick
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Wylij zaznaczone" />
</LinearLayout>

Zwrmy uwag na specyfikacj identyfikatora dla pojemnika ListView. Musielimy wprowadzi


warto @android:id/list, poniewa aktywno ListActivity oczekuje znalezienia pojemnika ListView w tak nazwanym ukadzie graficznym. Gdybymy polegali na domylnym pojemniku ListView utworzonym przez aktywno ListActivity, posiadaby on wanie taki
identyfikator.
Inn kwesti, na ktr warto zwrci uwag, jest sposb okrelenia wysokoci pojemnika
ListView w ukadzie LinearLayout. Chcemy, aby przycisk by widoczny na ekranie przez cay
czas, bez wzgldu na liczb elementw dostpnych w widoku ListView, i nie chcemy przecie

210 Android 3. Tworzenie aplikacji


przewija ekranu na sam d, aby znale tam ten przycisk. W tym celu ustawiamy warto
argumentu layout_height na 0, a nastpnie wprowadzamy waciwo layout_weight,
dziki ktrej kontrolka moe zaj cae dostpne miejsce w nadrzdnym pojemniku. Dziki tej
sztuczce rezerwujemy miejsce na przycisk oraz pozostawiamy moliwo przewijania pojemnika
ListView. Wicej informacji o ukadach graficznych i wagach znajdziemy w dalszej czci
rozdziau.
Implementacja tej aktywnoci bdzie przypominaa kod widoczny na listingu 6.30.
Listing 6.30. Odczytywanie danych wprowadzanych przez uytkownika w aktywnoci ListActivity
public class ListViewActivity3 extends ListActivity
{
private static final String TAG = "ListViewActivity3";
private ListView lv = null;
private Cursor cursor = null;
private int idCol = -1;
private int nameCol = -1;
private int notesCol = -1;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
lv = getListView();
cursor = managedQuery(People.CONTENT_URI,
null, null, null, People.NAME);
String[] cols = new String[]{People.NAME};
idCol = cursor.getColumnIndex(People._ID);
nameCol = cursor.getColumnIndex(People.NAME);
notesCol = cursor.getColumnIndex(People.NOTES);
int[] views = new int[]{android.R.id.text1};
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_multiple_choice,
cursor, cols, views);
this.setListAdapter(adapter);
lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
public void doClick(View view) {
int count=lv.getCount();
SparseBooleanArray viewItems = lv.getCheckedItemPositions();
for(int i=0; i<count; i++) {
if(viewItems.get(i)) {
cursor.moveToPosition(i);
long id = cursor.getLong(idCol);
String name = cursor.getString(nameCol);
String notes = cursor.getString(notesCol);

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

211

Log.v(TAG, name + " jest zaznaczony/a. Uwagi: " + notes +


". Polozenie = " + i + ". Id = " + id);
}
}
}
}

Wracamy tutaj do wywoywania metody setContentView() w celu ustawienia interfejsu uytkownika w aktywnoci. Natomiast w konfiguracji adaptera przekazujemy kolejny predefiniowany widok wobec elementu pojemnika ListView (android.R.layout.simple_list_item_
multiple_choice), w wyniku czego kady wiersz zawiera kontrolki TextView i CheckBox.
Jeeli zajrzymy do pliku zawierajcego ukad graficzny, zauwaymy kolejn podklas kontrolki
TextView, noszc nazw CheckedTextView. Ten specjalny rodzaj kontrolki TextView zosta
stworzony z myl o pojemnikach ListView. Mwilimy przecie, e w tym folderze, zawierajcym predefiniowane pliki ukadw graficznych, mona znale interesujce rzeczy! Warto
zauway, e identyfikator tej kontrolki posiada warto text1, ktr musielimy przekaza
w tablicy widokw konstruktorowi klasy SimpleCursorAdapter.
Poniewa chcemy, aby uytkownik mg zaznacza poszczeglne wiersze, wprowadzamy tryb
wybierania CHOICE_MODE_MULTIPLE. Domyln wartoci tego trybu jest CHOICE_MODE_NONE.
Ostatni moliwoci, jak moemy wybra, jest CHOICE_MODE_SINGLE. Aby uy tego ostatniego
trybu, trzeba by wprowadzi inny ukad graficzny, najprawdopodobniej android.R.layout.
simple_list_item_single_choice.
W tym przykadzie zaimplementowalimy podstawowy przycisk, wywoujcy metod doClick()
naszej aktywnoci. Aby nie utrudnia sprawy, nazwy elementw zaznaczanych przez uytkownika bd zapisywane w oknie LogCat. Dobr wieci jest, e wprowadzenie takiego rozwizania jest bardzo proste, z drugiej strony jednak Android wyewoluowa do tego stopnia, e jego
implementacja moe zalee od wersji systemu. Ukazane tu rozwizanie polegajce na wykorzystaniu pojemnika ListView dziaa od wersji 1 Androida (chocia przy wywoywaniu
zwrotnym przycisku korzystamy ze skrtu dostpnego od wersji 1.6 Androida). Oznacza to,
e metoda getCheckedItemPositions() jest stara, ale cigle skuteczna. W wyniku jej dziaania
otrzymujemy tablic okrelajc, czy dany element zosta zaznaczony, czy nie. Zatem teraz
mona sprawdzi wszystkie elementy za pomoc metody array.viewItems.get(i). Metoda
ta przekae warto true, jeli dany wiersz w pojemniku ListView zosta zaznaczony. Dostp
do danych mona uzyska za pomoc kursora. Zatem zamiast wyszukiwa dane w pojemniku
ListView, sprawdzamy informacje zawarte w kursorze. Widok ListView powie nam, w ktrym miejscu adaptera naley szuka.
Po uzyskaniu numeru pozycji zaznaczonego elementu moemy uy metody moveToPosition()
kursora, aby przygotowa aplikacj do odczytu danych. Istnieje inna metoda, speniajca niemal identyczne zadanie getItemAtPosition() klasy ListView. W naszym przypadku element przekazany przez t metod przeksztaciby si na obiekt CursorWrapper. Jak ju wczeniej stwierdzilimy, w innych przypadkach moemy otrzyma odmienne typy obiektw. Obiekt
CursorWrapper pojawia si tylko dlatego, e pracujemy z dostawc treci. Naley rozumie
rdo danych oraz adapter, eby wiedzie, czego si spodziewa.
Moemy nastpnie wykorzysta obiekt Cursor (lub CursorWrapper, jeli go otrzymalimy)
do odczytania informacji powizanych z zaznaczonym wierszem pojemnika ListView. Zauwamy, e w naszym przykadzie odczytujemy nie tylko nazw kontaktu, ale take uwagi na
jego temat, chocia nigdzie ich nie odzwierciedlalimy w tym kontenerze. Jest tak, poniewa

212 Android 3. Tworzenie aplikacji


podczas konfigurowania kursora dla adaptera wybralimy wszystkie dostpne pola. W praktyce
nie trzeba wybiera wszystkich pl, powinnimy ogranicza zapytania tylko do potrzebnych
elementw. Ale w tym konkretnym przypadku wysalimy zapytania dotyczce wikszej liczby
pl, ni byo potrzebne do wywietlania w pojemniku ListView. W atwy wic sposb uzyskalimy dostp do tych pl w trakcie wywoywania zwrotnego przycisku.

Alternatywna metoda odczytywania zaznaczonych elementw


w pojemniku ListView
W wersji 1.6 Androida wprowadzono inn metod odczytywania listy zaznaczonych elementw
w pojemniku ListView; mowa tu o metodzie getCheckItemIds(). Z kolei w wersji 2.2 zostaa
ona wycofana i zastpiona metod getCheckedItemIds(). Nastpia nieznaczna zmiana nazwy,
ale sposb korzystania z tej klasy jest zasadniczo taki sam. Poza tym w tej wersji zmodyfikowano
sposb obsugiwania kontaktw. W nastpnym przykadzie wykorzystamy system Android 2.2,
aby pokaza dziaanie tej metody. Listing 6.31 prezentuje odpowiedni kod Java, natomiast plik
XML ukadu graficznego list.xml moe by taki sam jak na listingu 6.29.
Listing 6.31. Alternatywny sposb odczytywania danych wprowadzanych przez uytkownika
w klasie ListActivity
public class ListViewActivity4 extends ListActivity
{
private static final String TAG = "ListViewActivity4";
private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI;
private SimpleCursorAdapter adapter = null;
private ListView lv = null;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
lv = getListView();
String[] projection = new String[] {ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME};
Cursor c = managedQuery(CONTACTS_URI,
projection, null, null, ContactsContract.Contacts.DISPLAY_NAME);
String[] cols = new String[] {ContactsContract.Contacts.DISPLAY_NAME};
int[] views = new int[] {android.R.id.text1};
adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_multiple_choice,
c, cols, views);
this.setListAdapter(adapter);
lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
public void doClick(View view) {
if(!adapter.hasStableIds()) {

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

213

Log.v(TAG, "Dane sa niestabilne");


return;
}
long[] viewItems = lv.getCheckedItemIds();
for(int i=0; i<viewItems.length; i++) {
Uri selectedPerson = ContentUris.withAppendedId(
CONTACTS_URI, viewItems[i]);
Log.v(TAG, selectedPerson.toString() + " jest zaznaczony/a.");
}
}
}

W tej przykadowej aplikacji po klikniciu przycisku nastpuje wywoanie zwrotnej metody


getCheckedItemIds(). Tym razem uzyskujemy tablic identyfikatorw rekordw (z adaptera),
ktre zostay zaznaczone w widoku ListView, podczas gdy w poprzedniej aplikacji otrzymalimy tablic pozycji zaznaczonych elementw pojemnika ListView. Teraz mona pomin widok
ListView oraz kursor, poniewa identyfikatory w poczeniu z dostawcami treci pozwalaj
na podjcie dowolnej podanej akcji. W tym przykadzie konstruujemy po prostu identyfikator
URI, reprezentujcy okrelony rekord z dostawcy treci Contacts, i zapisujemy ten identyfikator w dzienniku LogCat. Moglibymy bezporednio operowa na danych za pomoc dostawcy treci. Mechanizm ten dziaa rwnie dobrze w przypadku starszych wersji dostawcy
treci Contacts oraz wprowadzonej w wersji 1.6 Androida metody getCheckItemIds().
Kolejn rnic jest zaznaczenie tylko kilku pl na etapie tworzenia obiektu Cursor. Jest to cakowicie naturalne rozwizanie, poniewa nie trzeba odczytywa wikszej iloci danych, ni jest
to konieczne. Ostatni rzecz, na ktr warto zwrci uwag, jest fakt, e metoda getChecked
ItemIds() wymaga stabilnoci danych przechowywanych w adapterze. Zatem bardzo zalecamy wywoanie metody hasStableIds() w adapterze, zanim wywoamy metod getChecked
ItemIds() w pojemniku ListView. W omawianym przykadzie skorzystalimy ze skrtu
dany fakt zosta zwyczajnie odnotowany w dzienniku. Aplikacja uytkowa powinna jako
na ten fakt zareagowa, na przykad uruchomi wtek przebiegajcy w tle, sucy do wykonywania powtrze oraz wywietlajcy okno dialogowe informujce uytkownika o trwaniu
procesu przetwarzania.
Powyej zademonstrowalimy rne scenariusze korzystania z kontrolki ListView. Pokazalimy, jak wiele pracy wykonuj adaptery podczas obsugi pojemnikw ListView. Teraz zajmiemy si pozostaymi rodzajami kontrolek listy, poczwszy od widoku GridView.

Kontrolka GridView
Wikszo narzdzi do tworzenia widetw ma przynajmniej jedn kontrolk definiujc siatk.
Android posiada kontrolk GridView, dziki ktrej dane s wywietlane w takiej siatce. Pamitajmy, e poprzez dane rozumiemy tu tekst, rysunki i tak dalej.
Kontrolka GridControl wywietla informacje w siatce. Algorytm wykorzystania tej kontrolki
polega na zdefiniowaniu siatki w pliku XML ukadu graficznego (listing 6.32), a nastpnie powizaniu danych z t siatk za pomoc klasy android.widget.ListAdapter. Naley te doda
etykiet Uses Permission do pliku AndroidManifest.xml, w przeciwnym wypadku przykadowy
kod nie zadziaa.

214 Android 3. Tworzenie aplikacji


Listing 6.32. Definiowanie kontrolki GridView w pliku XML ukadu graficznego oraz w kodzie Java
<?xml version="1.0" encoding="utf-8"?>

<!-- Plik ten jest umieszczony w podkatalogu /res/layout/gridview.xml -->


<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dataGrid"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10px"
android:verticalSpacing="10px"
android:horizontalSpacing="10px"
android:numColumns="auto_fit"
android:columnWidth="100px"
android:stretchMode="columnWidth"
android:gravity="center"
/>

public class GridViewActivity extends Activity


{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gridview);
GridView gv = (GridView)this.findViewById(R.id.gridView);
Cursor c = managedQuery(People.CONTENT_URI,
null, null, null, People.NAME);
String[] cols = new String[] {People.NAME};
int[] views = new int[]
{android.R.id.text1};
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1 ,c,cols,views);
gv.setAdapter(adapter);
}
}

Na listingu 6.32 zdefiniowano prost kontrolk GridView w pliku XML ukadu graficznego.
Siatka ta zostaje wczytana do widoku treci aktywnoci. Wygenerowany interfejs uytkownika
mona ujrze na rysunku 6.13.
Siatka przedstawiona na rysunku 6.13 wywietla nazwy kontaktw przechowywanych w urzdzeniu. Postanowilimy umieci kontrolki TextView z tymi nazwami, jednak rwnie dobrze
mona w ich miejsce wstawi obrazy lub inne kontrolki. Ponownie skorzystalimy z moliwoci
predefiniowanych ukadw graficznych. W rzeczywistoci przykad ten jest bardzo podobny do
kodu zamieszczonego na listingu 6.27, istnieje jednak pomidzy nimi kilka istotnych rnic.
Po pierwsze, klasa GridViewActivity rozszerza klas Activity, nie ListActivity. Po drugie,
musimy wywoa metod setContentView() w celu ustanowienia ukadu graficznego dla pojemnika GridView nie ma tu adnych domylnych widokw. Na koniec warto zauway,
e w celu ustawienia adaptera wywoujemy metod setAdapter() na obiekcie GridView, a nie
metod setListAdapter() wobec klasy Activity.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

215

Rysunek 6.13. Kontrolka GridView wypeniona nazwami kontaktw

Niewtpliwie zdylimy zauway, e adapterem siatki jest ListAdapter. Listy s zazwyczaj


jednowymiarowe, podczas gdy siatki posiadaj dwa wymiary. Wynika z tego wniosek, e siatka
tak naprawd wywietla dane w postaci listy. Okazuje si take, e lista jest wywietlana wierszami. Inaczej mwic, lista zostaje ukadana najpierw w jednym rzdzie, nastpnie w drugim
i tak dalej.
Podobnie jak przedtem, mamy tu do czynienia z kontrolk listy, ktra wsppracuje z adapterem
zarzdzajcym danymi oraz z generowaniem widokw potomnych. Techniki stosowane
wczeniej powinny rwnie dziaa z kontrolkami GridView. Jedyna rnica polega na sposobie
wybierania. W przeciwiestwie do kodu widocznego na listingu 6.30, nie ma moliwoci wprowadzenia funkcji wielokrotnego wyboru.

Kontrolka Spinner
Kontrolka Spinner peni funkcj rozwijanego menu. Zazwyczaj stosuje si j do wybierania
opcji ze stosunkowo krtkiej listy. Jeeli lista jest zbyt duga do wywietlania, zostaje automatycznie dodany pasek przewijania. Mona j utworzy w pliku XML ukadu graficznego w tak
prosty sposb:
<Spinner
android:id="@+id/spinner" android:prompt="@string/spinnerprompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

Chocia z technicznego punktu widzenia obiekt typu Spinner jest kontrolk listy, przypomina
on bardziej prosty widok TextView. Innymi sowy, kiedy kontrolka Spinner pozostaje nieaktywna, wywietlana jest tylko jedna warto. Zadaniem tej kontrolki jest umoliwienie uytkownikowi wyboru z zestawu predefiniowanych wartoci: po klikniciu niewielkiej strzaki lista
wywietla si i uytkownik moe wybra z niej jaki element. Lista ta jest zapeniana tak samo jak
w przypadku pozostaych kontrolek listy, to znaczy za pomoc adaptera. Poniewa kontrolka
typu Spinner czsto jest stosowana w formie rozwijanego menu, powszechnym rozwizaniem
jest pobieranie przez adapter listy opcji z pliku zasobw. Przykadowy sposb wykorzystania
kontrolki Spinner wraz z plikiem zasobw zosta pokazany na listingu 6.33. Zwrmy uwag
na nowy atrybut android:prompt, ustanawiajcy zacht na szczycie listy opcji. Waciwy tekst
zachty znajduje si w pliku /res/values/strings.xml. Jak mona si spodziewa, klasa Spinner
posiada rwnie odpowiedni metod, pozwalajc na umieszczenie zachty w kodzie.

216 Android 3. Tworzenie aplikacji


Listing 6.33. Kod tworzcy obiekt klasy Spinner z pliku zasobw
public class SpinnerActivity extends Activity {

/** Wywoane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.spinner);
Spinner spinner = (Spinner)findViewById(R.id.spinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.planets, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
}
}

By moe Czytelnik pamita, e plik planets.xml widnia rwnie na listingu 6.26. W omawianym przykadzie ukazujemy sposb utworzenia kontrolki Spinner. Po skonfigurowaniu adaptera zostaje on doczony do tego obiektu. Na rysunku 6.14 widzimy obiekt Spinner w akcji.

Rysunek 6.14. Obiekt Spinner umoliwiajcy wybr planety

Jedn z cech odrniajcych ten obiekt od pozostaych kontrolek list jest obecno dodatkowego
ukadu graficznego, z ktrego naley korzysta podczas pracy z klas Spinner. Na lewym zrzucie
ekranu z rysunku 6.14 widzimy normalny tryb dziaania kontrolki Spinner widoczny jest tu
biecy wybr. W tym przypadku wybrano planet Saturn. Obok znajduje si strzaka informujca, e mamy do czynienia z kontrolk typu Spinner, a jej nacinicie spowoduje wywietlenie si listy dostpnych wartoci. Pierwszy ukad graficzny, dostarczany w postaci para-

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

217

metru metodzie ArrayAdapter.createFromResource(), definiuje wygld obiektu Spinner


w trybie normalnym. Na prawym zrzucie ekranu z rysunku 6.14 widzimy ten obiekt w trybie
listy rozwijanej, oczekujcy na wybr nowej wartoci przez uytkownika. Ukad graficzny tej
listy jest ustawiany za pomoc metody setDropDownViewResource(). Jest to kolejny przykad
sytuacji, gdzie wykorzystujemy dwa predefiniowane ukady graficzne, zatem jeeli chcemy zapozna si z ich definicjami, powinnimy odwiedzi katalog /res/layout Androida. Oczywicie,
moemy rwnie utworzy wasne definicje ukadw graficznych, aby osign z gry zamierzony, niestandardowy efekt.

Kontrolka Gallery
Kontrolka Gallery tworzy list przewijan w poziomie, ktra eksponuje elementy widoczne
w rodkowej czci tej listy. Kontrolka ta przewanie jest wykorzystywana do tworzenia galerii
obrazw, gdzie nawigacja midzy obrazami odbywa si w trybie dotykowym. Mona j utworzy
w pliku XML ukadu graficznego lub w kodzie Java:
<Gallery
android:id="@+id/gallery"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>

Kontrolka typu Gallery jest najczciej uywana do wywietlania obrazw, zatem adapter
zostanie najprawdopodobniej dostosowany do ich obsugi. W nastpnym punkcie, dotyczcym
niestandardowych adapterw, zaprezentujemy adapter wyspecjalizowany do obsugi obrazw.
Wygld kontrolki Gallery zosta zaprezentowany na rysunku 6.15.

Rysunek 6.15. Galeria zdj krw morskich

218 Android 3. Tworzenie aplikacji

Tworzenie niestandardowych adapterw


Standardowe adaptery systemu Android s atwe w uyciu, posiadaj jednak pewne ograniczenia. Problem ten zosta rozwizany za pomoc abstrakcyjnej klasy BaseAdapter, ktr mona
rozszerzy w przypadku koniecznoci utworzenia niestandardowego adaptera. Adaptery takie
okazuj si przydatne w przypadku potrzeby wdroenia niestandardowych sposobw zarzdzania danymi lub w celu zapewnienia wikszej kontroli nad wywietlaniem potomnych widokw. Stosowanie niestandardowych adapterw pozwala rwnie na zwikszenie wydajnoci,
gdy moemy stosowa techniki buforowania w pamici podrcznej. Pokaemy teraz, w jaki
sposb mona utworzy taki niestandardowy adapter.
Na listingu 6.34 widzimy przykadowy plik XML oraz kod Java tworzce niestandardowy adapter. W omawianym przykadzie adapter posuy do obsugi zdj krw morskich, zatem nazwiemy go ManateeAdapter. Utworzymy go rwnie we wntrzu aktywnoci.
Listing 6.34. Nasz niestandardowy adapter: ManateeAdapter
<?xml version="1.0" encoding="utf-8"?>

<!-- Plik ten znajduje si w /res/layout/gridviewcustom.xml -->


<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip"
android:verticalSpacing="10dip"
android:horizontalSpacing="10dip"
android:numColumns="auto_fit"
android:gravity="center"
/>

public class GridViewCustomAdapter extends Activity


{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.gridviewcustom);
GridView gv = (GridView)findViewById(R.id.gridview);
ManateeAdapter adapter = new ManateeAdapter(this);
gv.setAdapter(adapter);
}
public static class ManateeAdapter extends BaseAdapter {
private static final String TAG = "ManateeAdapter";
private static int convertViewCounter = 0;
private Context mContext;
private LayoutInflater mInflater;
static class ViewHolder {
ImageView image;
}

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

private int[] manatees = {


R.drawable.manatee00, R.drawable.manatee01,
R.drawable.manatee03, R.drawable.manatee04,
R.drawable.manatee06, R.drawable.manatee07,
R.drawable.manatee09, R.drawable.manatee10,
R.drawable.manatee12, R.drawable.manatee13,
R.drawable.manatee15, R.drawable.manatee16,
R.drawable.manatee18, R.drawable.manatee19,
R.drawable.manatee21, R.drawable.manatee22,
R.drawable.manatee24, R.drawable.manatee25,
R.drawable.manatee27, R.drawable.manatee28,
R.drawable.manatee30, R.drawable.manatee31,
R.drawable.manatee33 };

R.drawable.manatee02,
R.drawable.manatee05,
R.drawable.manatee08,
R.drawable.manatee11,
R.drawable.manatee14,
R.drawable.manatee17,
R.drawable.manatee20,
R.drawable.manatee23,
R.drawable.manatee26,
R.drawable.manatee29,
R.drawable.manatee32,

private Bitmap[] manateeImages = new Bitmap[manatees.length];


private Bitmap[] manateeThumbs = new Bitmap[manatees.length];
public ManateeAdapter(Context context) {
Log.v(TAG, "Tworzenie adaptera ManateeAdapter");
this.mContext = context;
mInflater = LayoutInflater.from(context);
for(int i=0; i<manatees.length; i++) {
manateeImages[i] = BitmapFactory.decodeResource(
context.getResources(), manatees[i]);
manateeThumbs[i] = Bitmap.createScaledBitmap(manateeImages[i],
100, 100, false);
}
}
@Override
public int getCount() {
Log.v(TAG, "w getCount()");
return manatees.length;
}
public int getViewTypeCount() {
Log.v(TAG, "w getViewTypeCount()");
return 1;
}
public int getItemViewType(int position) {
Log.v(TAG, "w getItemViewType() dla pozycji " + position);
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
Log.v(TAG, "w getView dla pozycji " + position +
", convertView is " +
((convertView == null)?"null":"ponowne przetwarzanie"));
if (convertView == null) {
convertView = mInflater.inflate(R.layout.gridimage, null);

219

220 Android 3. Tworzenie aplikacji


convertViewCounter++;
Log.v(TAG, convertViewCounter + " convertViews zostaly utworzone");
holder = new ViewHolder();
holder.image = (ImageView) convertView.findViewById(R.id.gridImageView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.image.setImageBitmap( manateeThumbs[position] );
return convertView;
}
@Override
public Object getItem(int position) {
Log.v(TAG, "w getItem() dla pozycji " + position);
return manateeImages[position];
}
@Override
public long getItemId(int position) {
Log.v(TAG, "w getItemId() dla pozycji " + position);
return position;
}
}
}

Po uruchomieniu aplikacji powinnimy zobaczy widok przedstawiony na rysunku 6.16.

Rysunek 6.16. Widok siatki zawierajcy zdjcia krw morskich

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

221

Chocia przedstawiony kod wydaje si wzgldnie prosty, wiele elementw wymaga tu objanienia. Zaczniemy od klasy Activity, bardzo przypominajcej te, z ktrymi pracowalimy
wczeniej w tym punkcie. Wida te gwny ukad graficzny z pliku gridviewcustom.xml, w ktrym zawarta jest wycznie definicja kontrolki GridView. Musimy uzyska odniesienie do tej
kontrolki z wntrza ukadu graficznego, zatem definiujemy i ustawiamy widok gv. Tworzymy
nasz obiekt ManateeAdapter, przekazujemy mu kontekst i ustanawiamy go wobec kontrolki
GridView. Na razie nie zrobilimy niczego odkrywczego, jednak bez wtpienia zauwaylimy,
e na etapie tworzenia nasz niestandardowy adapter nie wykorzystuje nawet czci tych parametrw co adaptery predefiniowane. Wynika to gwnie z faktu, e mamy cakowit kontrol
nad tym konkretnym adapterem i wykorzystujemy go tylko w tej jednej aplikacji. Gdybymy
chcieli utworzy adapter oglniejszego przeznaczenia, najprawdopodobniej wprowadzilibymy
wicej parametrw. Idmy zatem dalej.
Zadaniem adaptera jest zarzdzanie przekazywaniem danych do obiektw typu View Androida.
Obiekty te s wykorzystywane przez kontrolk listy (w tym przypadku GridView). Dane pochodz z jakiego rda danych. We wczeniejszych przykadach dane byy dostarczane poprzez
obiekt kursora, ktry by przekazywany adapterowi. W omawianym przypadku nasz niestandardowy adapter ma wszystkie informacje o danych oraz ich rdach. Jest w stanie skonstruowa interfejs uytkownika, jeli otrzyma takie danie od kontrolki. Moe rwnie przekaza do ponownego wykorzystania widoki, ktre przestan by potrzebne. Moe wydawa si
dziwne, e adapter musi mie zdolno konstruowania widokw, ale ostatecznie wszystko
razem nabiera sensu.
W trakcie tworzenia instancji omawianego niestandardowego adaptera ManateeAdapter zwyczajowo przekazuje si mu kontekst, ktry bdzie w nim przetrzymywany. Jego przechowywanie bardzo czsto okazuje si przydatne. Drugim zadaniem tego adaptera jest przechowanie
klasy Inflater. To pozwala na popraw wydajnoci w momencie utworzenia nowego widoku,
zwracanego kontrolce listy. Trzecim typowym zadaniem adaptera jest utworzenie obiektu
ViewHolder, przechowujcego obiekty typu View dla zarzdzanych danych. W omawianym
przykadzie przechowujemy po prostu widok ImageView, ale gdyby trzeba byo obsuy dodatkowe pola, wprowadzilibymy je do definicji obiektu ViewHolder. Gdybymy na przykad
posiadali pojemnik ListView, na ktrego kady wiersz skadaby si jeden widok ImageView
i dwie kontrolki TextView, obiekt ten przechowywaby dokadnie jeden widok ImageView
i dwie kontrolki TextView.
Poniewa omawiany adapter suy do obsugi obrazw krw morskich, ustanawiamy tablic
identyfikatorw tych zasobw, za pomoc ktrych zostan utworzone mapy bitowe. Definiujemy rwnie tablic map bitowych, ktre bd stanowiy list danych.
Jak wynika z kodu konstruktora klasy ManateeAdapter, zapisujemy kontekst, tworzymy i przechowujemy klas Inflater, a nastpnie iterujemy poprzez identyfikatory zasobw obrazw
i budujemy tablic bitmap. Ta ostatnia bdzie stanowia nasze dane.
Jak ju si wczeniej dowiedzielimy, ustanowienie adaptera spowoduje, e kontrolka GridView
bdzie wywoywaa wobec niego metody definiujce wywietlane w niej dane. Na przykad
kontrolka gv bdzie wywoywaa metod getCount() adaptera w celu okrelenia liczby wywietlanych obiektw. Bdzie take wywoywana metoda getViewTypeCount() suca do
okrelenia, jak wiele rnych typw widokw moe by wywietlanych wewntrz pojemnika
GridView. W naszym przykadzie przypisujemy jej warto 1. Jeeli jednak chcielibymy
uwzgldni kontener ListView i wprowadzi separatory pomidzy wiersze z danymi, potrzebne byyby dwa typy danych, a wtedy metoda getViewTypeCount() powinna zwraca warto 2.

222 Android 3. Tworzenie aplikacji


Moemy zaplanowa dowoln liczb rnych typw widokw, byle tylko metoda ta przekazywaa odpowiedni warto. Pokrewn metod jest getItemViewType(). Przed momentem
stwierdzilimy, e adapter moe przekazywa wiksz liczb typw widokw. eby jednak
uproci spraw, wynikiem dziaania metody getItemViewType() moe by wycznie liczba
cakowita, dziki czemu moe wskazywa, jaki typ widoku dotyczy danego fragmentu danych.
Jeeli zatem otrzymujemy dwa typy widokw, metoda getItemViewType() za pomoc wartoci 0 i 1 wskazywaaby, ktry typ jest w danej chwili potrzebny. W przypadku obecnoci trzech
typw danych dostpne byyby wartoci 0, 1 i 2.
Jeeli omawiany adapter obsuguje separatory w pojemniku ListView, musz by one traktowane
jako cz danych. Oznacza to, e separator zajmuje pozycj danych. Po wywoaniu metody
getView() przez kontrolk listy w celu odczytania waciwego widoku dla tej pozycji metoda ta
przekae separator jako widok w miejsce normalnych danych. Jeli za chodzi o typ danych dla
tej wanie pozycji, to metoda getItemViewType() przekae odpowiedni warto cakowit,
odpowiadajc temu typowi widoku. W przypadku korzystania z separatorw naley take
zaimplementowa metod isEnabled(). Przekazywaaby ona warto true dla elementw listy, a false dla separatorw, poniewa ten drugi typ danych nie powinien by zaznaczany ani
reagowa na kliknicia.
Najbardziej interesujce w klasie ManateeAdapter jest wywoanie metody getView(). Po okreleniu przez widok gv liczby dostpnych obiektw, rozpoczyna on wysyanie zapyta o dane.
Teraz moemy mwi o wielokrotnym wykorzystywaniu widokw. Kontrolka listy moe pokazywa tylko tyle elementw potomnych, ile zmieci si na ekranie. Oznacza to, e wywoywanie
metody getView() dla kadego fragmentu danych dostpnych w adapterze nie ma sensu. Czynno ta nabiera sensu dopiero w przypadku jej wywoywania dla takiej liczby elementw,
ktra zmieci si na ekranie. Kiedy gv pobiera widoki z adaptera, okrela jednoczenie, jak wiele
elementw mona wywietli na wywietlaczu o danych rozmiarach. Gdy wywietlacz zostanie
ju wypeniony danymi, gv zaprzestaje wywoywania metody getView.
Jeeli przyjrze si oknu LogCat po uruchomieniu tej przykadowej aplikacji, wida rnorodne
wywoania, stwierdzimy jednak rwnie, e metoda getView() zaprzestaa wywoywania,
zanim zadano wszystkich obrazw. Jeeli zaczniemy teraz przewija widok GridView w gr
i w d, w dzienniku LogCat pojawi si wicej wywoa metody getView(). Okae si take, e
po utworzeniu okrelonej liczby widokw potomnych zostaje ona wywoana wraz z parametrem convertView posiadajcym warto inn od null. Oznacza to, e Android wykorzystuje
ponownie stare widoki potomne co znacznie poprawia wydajno.
Jeeli warto parametru convertView bdzie niezerowa, oznacza to, e pojemnik gv ponownie
wykorzystuje dany widok. W ten sposb unikamy nadmiernego obciania ukadu graficznego
XML, nie trzeba te odnajdywa kontrolki ImageView. Poprzez poczenie obiektu ViewHolder
z uzyskiwanym obiektem View proces odwieania widoku moe zosta przeprowadzony o wiele
szybciej nastpnym razem, gdy tylko ten widok bdzie ponownie potrzebny. Jedyne, co trzeba
zrobi w metodzie getView(), to ponownie uzyska obiekt ViewHolder i przydzieli waciwe
dane do widoku.
Chcielimy w tym przykadzie pokaza, e w widoku nie musz by koniecznie umieszczane
dokadnie te same dane, ktre s zawarte w rdle tych danych. Metoda createScaledBitmap()
suy do tworzenia mniejszych wersji obrazw, ktre bd nastpnie wywietlane jako miniaturki. Polega to na tym, e kontrolka listy nie wywouje metody getItem(). Zostaje ona wywoana przez inny kod, ktry pod wpywem okrelonych dziaa uytkownika moe w jaki sposb
zmodyfikowa dane znajdujce si w kontrolce listy. Znowu wida, jak wane jest zrozumienie

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

223

przeprowadzanych dziaa w przypadku adapterw. Nie zawsze trzeba polega na danych


przechowywanych w widoku stanowicym cz kontrolki listy, utworzonym w adapterze przez
metod getView(). Czasami naley wywoa metod getItem() adaptera, eby otrzyma rzeczywiste dane, na ktrych mona operowa. A czasami, podobnie jak w poprzednich przykadach z kontrolk ListView, obsug danych zapewni kursor. Wszystko zaley od adaptera
oraz od pochodzenia danych. Chocia w tym przykadzie wykorzystalimy metod createScaled
Bitmap(), w wersji 2.2 Androida wprowadzono kolejn klas, ktra tutaj moe okaza si
przydatna ThumbnailUtils. Zawiera ona pewne statyczne metody, suce do generowania
miniaturek obrazw z bitmap oraz plikw wideo.
Ostatni kwesti, na ktr warto zwrci uwag w tym przykadzie, jest wywoanie metody
We wczeniejszych przykadach z kontrolkami ListView i kontaktami identyfikator obiektu posiada warto _ID otrzymywan od dostawcy treci. cile rzecz ujmujc,
w ostatnim przykadzie rol identyfikatora speniaa informacja o pooeniu obiektu. Cay sens
istnienia identyfikatorw polega na zapewnieniu mechanizmu, ktry pozwala na odnoszenie
si do danych z pozycji takiego identyfikatora. Jest to prawd zwaszcza wtedy, gdy dane s
znacznie oddzielone od adaptera, co miao miejsce w przypadku kontaktw. Kiedy posiadamy
tak bezporedni kontrol nad danymi, podobnie jak w przypadku zdj krw morskich, oraz
wiemy, w jaki sposb dosta si do waciwych danych w aplikacji, powszechnym rozwizaniem jest wykorzystanie pozycji jako identyfikatora elementu. Jest to prawdziwe stwierdzenie
zwaszcza w naszym przypadku, gdy nie pozwalamy na dodawanie lub usuwanie danych.

getItemId().

Inne kontrolki w Androidzie


W Androidzie istnieje wiele rnych kontrolek. Dotychczas omwilimy kilka z nich, a kolejnymi zajmiemy si w dalszych rozdziaach (na przykad MapView w rozdziale 17., VideoView
i MediaController w rozdziale 19., a GLSurfaceView w rozdziale 20.). Poniewa kontrolki
te wywodz si z klasy View, stwierdzimy, e czy je wiele wsplnych cech z dotychczas
omwionymi pojemnikami. Teraz jedynie wspomnimy o kilku kontrolkach, ktre warto pozna samodzielnie.
Kontrolka ScrollBar suy do ustawiania w kontenerze typu View pionowego paska przewijania. Jest to bardzo przydatna kontrolka, w przypadku gdy tre nie mieci si na ekranie.
W podrozdziale Odnoniki zamieszczono adres do bloga Romain Guya, w ktrym omwiono
sposb korzystania z tej kontrolki.
Kontrolki ProgressBar i RatingBar przypominaj suwaki. Pierwszy z nich w sposb wizualny
prezentuje stopie postpu jakiej czynnoci (na przykad pobieranie pliku lub odsuchiwanie
muzyki), natomiast za pomoc drugiego jest prezentowana skala oceniania za pomoc gwiazdek.
Kontrolka Chronometer stanowi czasomierz. Jeeli chcemy wprowadzi funkcj stopera, mona
wykorzysta klas CountDownTimer, nie jest ona jednak czci klasy View.
WebView

stanowi bardzo specyficzny widok pozwalajcy na wywietlanie stron HTML. Jego


funkcjonalno na tym si nie koczy. Kontrolka ta moe rwnie obsugiwa pliki cookies,
jzyk JavaScript oraz poczenia z kodem Java, znajdujcym si w naszej aplikacji. Zanim
jednak zaimplementujemy przegldark internetow w tworzonym programie, warto ostronie
rozway moliwo przywoywania przegldarki wbudowanej w urzdzenie. Ta wbudowana
przegldarka przeprowadzaaby wszystkie wymagane operacje.
W ten sposb koczymy wprowadzenie do kontrolek. Zajmiemy si teraz stylami i motywami
modyfikujcymi wygld i zachowanie kontrolek, a nastpnie ukadami graficznymi pozwalajcymi na rozmieszczanie kontrolek na ekranie.

224 Android 3. Tworzenie aplikacji

Style i motywy
Android posiada kilka mechanizmw pozwalajcych na zmian stylu widokw w aplikacji.
Najpierw zajmiemy si znacznikami wprowadzanymi do cigw znakw, a nastpnie zaprezentujemy sposb uycia obiektw klasy Spannable, dziki ktrym zmienimy okrelone, wizualne atrybuty tekstu. Jednak co mona zrobi w razie potrzeby kontrolowania wygldu kontrolek za pomoc specyfikacji wsplnej dla wielu widokw lub dla caej aktywnoci czy aplikacji?
Odpowiedzi udzielimy w trakcie omawiania stylw i motyww stosowanych w systemie Android.

Stosowanie stylw
Czasami chcemy podwietli lub zaznaczy odmiennym stylem jaki fragment treci zawartej
w kontrolce klasy View. Mona tego dokona w sposb statyczny lub dynamiczny. Metoda statyczna polega na wstawieniu znacznikw bezporednio do cigu znakw w zasobach typu string,
na przykad:
<string name="styledText"> Styl <i>statyczny</i> w polu <b>TextView</b>.</string>

Moemy nastpnie utworzy odniesienie w pliku XML lub kodzie. Warto wiedzie, e w zasobach
typu string dostpne s znaczniki <i>, <b> oraz <u> jzyka HTML, odpowiadajce, kolejno,
pochyleniu czcionki, jej pogrubieniu oraz podkreleniu. Istniej take takie znaczniki, jak
<sup> (indeks grny), <sub> (indeks dolny), <strike> (przekrelenie), <big>, <small> oraz
<monospace>. Moemy nawet tworzy zagniedenia, na przykad pomniejszone indeksy dolne.
Style dziaaj nie tylko w kontrolce TextView, ale take w innych, na przykad w przyciskach.
Na rysunku 6.17 widzimy wygld tekstu zmodyfikowanego za pomoc stylw i motyww, na
ktrym mona zobaczy wiele przykadw omwionych w tym podrozdziale.

Rysunek 6.17. Przykadowe style i motywy

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

225

Programistyczne tworzenie stylw treci umieszczonej w kontrolce TextView wymaga nieco


wicej wysiku, jednak oferuje o wiele wicej moliwoci (listing 6.35), poniewa efekty zastosowania danego stylu mog stawa si widoczne podczas dziaania aplikacji. Taka elastyczno moe zosta jednak osignita wycznie za pomoc obiektu klasy Spannable. Kontrolka
EditText standardowo obsuguje w ten sposb wewntrzny tekst, podczas gdy widok TextView
zwykle nie korzysta z takich obiektw. Obiekt klasy Spannable jest przewanie zwykym cigiem znakw, do ktrego mona wprowadza style. Aby kontrolka TextView przechowywaa
tekst w postaci obiektu Spannable, mona wywoa metod setText() w nastpujcy sposb:
tv.setText("Ten tekst jest przechowywany w obiekcie klasy Spannable",
TextView.BufferType.SPANNABLE);

Nastpnie, podczas wywoywania metody tv.getText(), otrzymamy obiekt klasy Spannable.


Jak zostao pokazane na listingu 6.35, moemy pobra zawarto kontrolki EditText (w postaci
obiektu klasy Spannable), a nastpnie ustanawia style dla poszczeglnych fragmentw tekstu.
Kod widoczny na listingu pogrubia tekst i pochyla go, a take generuje czerwone to. Moemy
tu zastosowa wszystkie wymienione wczeniej opcje formatowania tekstu.
Listing 6.35. Dynamiczne umieszczanie stylw w treci kontrolki EditText
EditText et =(EditText)this.findViewById(R.id.et);
et.setText("Dynamiczne przypisywanie stylw zawartoci pola EditText");
Spannable spn = (Spannable) et.getText();
spn.setSpan(new BackgroundColorSpan(Color.RED), 11, 31,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spn.setSpan(new StyleSpan(android.graphics.Typeface.BOLD_ITALIC)
, 11, 31, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

Te dwie techniki formatowania tekstu dziaaj tylko w stosunku do tego widoku, do ktrego je
zastosowano. W Androidzie istnieje rwnie mechanizm umoliwiajcy definiowanie oglnego
stylu, ktry bdzie wykorzystywany przez wiele widokw, a take mechanizm motyww, ktry
w oglnej zasadzie pozwala na zastosowanie danego stylu w obrbie caej aktywnoci lub aplikacji. Najpierw jednak musimy omwi style.
Stylem nazywamy zbir atrybutw obiektu klasy View, posiadajcy osobn nazw, moliwo
przypisywania do widokw oraz taki, do ktrego moemy si pniej odnosi. Na przykad na
listingu 6.36 widzimy plik XML, zapisany w katalogu res/values, ktry moe by stosowany dla
komunikatw o wszystkich rodzajach bdw.
Listing 6.36. Definiowanie stylu, ktry bdzie wykorzystywany w wielu widokach
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ErrorText">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">#FF0000</item>
<item name="android:typeface">monospace</item>
</style>
</resources>

226 Android 3. Tworzenie aplikacji


Zdefiniowano tu rozmiar widoku, jak rwnie kolor czcionki (w tym przypadku czerwony),
a take jej krj. Zauwamy, e atrybut name znacznika elementu jest rwnie atrybutem uywanym w plikach ukadu graficznego, a warto znacznika item nie wymaga ju cudzysowu.
Moemy wykorzysta ten styl w kontrolce TextView wywietlajcej komunikaty o bdach, tak
jak zostao to zaprezentowane na listingu 6.37.
Listing 6.37. Umieszczanie stylu w widoku
<TextView android:id="@+id/errorText
style="@style/ErrorText"
android:text="W tej chwili nie ma bdw"
/>

Istotna jest informacja, e nazwa atrybutu dla stylu w tej definicji obiektu klasy View nie rozpoczyna si od czonu android:. Naley na to uwaa, poniewa wszystkie inne parametry zawieraj w sobie czon android:. Jeeli mamy w aplikacji wiele widokw wspdzielcych dany
styl, jego zmiana w jednym miejscu jest o wiele atwiejsza, wystarczy zmieni dane atrybuty
w pojedynczym pliku zasobw. Moemy, oczywicie, rwnie tworzy wiele rnych stylw
dla oddzielnych kontrolek. Na przykad przyciski mog korzysta z jednego stylu, ktry bdzie
si rni od stylu zastosowanego w tekcie z menu.
Jednym z najprzyjemniejszych aspektw stylw jest moliwo utworzenia ich hierarchii. Na
podstawie stylu ErrorText moemy utworzy oddzielny styl dla komunikatw o naprawd
gronych bdach. Na listingu 6.38 zaprezentowalimy jedn z propozycji.
Listing 6.38. Definiowanie stylu na podstawie stylu nadrzdnego
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ErrorText.Danger" >
<item name="android:textStyle">bold</item>
</style>
</resources>

Przykad ten pokazuje, e moemy w prosty sposb nazwa nasz potomny styl, stosujc nazw
stylu nadrzdnego jako przedrostek. Zatem styl ErrorText.Danger jest potomny wobec
stylu ErrorText i dziedziczy atrybuty rodzica. Nastpnie dodaje now warto we waciwoci textStyle. W podobny sposb mona utworzy cae drzewo hierarchii stylw.
Podobnie jak mielimy do czynienia z ukadami graficznymi, system Android zosta wyposaony w spory zestaw predefiniowanych stylw. Aby wykorzysta ktry z nich, stosujemy nastpujc skadni:
style="@android:style/TextAppearance"

W ten sposb zdefiniowano domylny styl formatowania tekstu w Androidzie. Gwny plik stylw
styles.xml znajdziemy w katalogu Android SDK/platforms/<wersja-androida>/data/res/values/.
Wewntrz tego pliku znajdziemy kilka przygotowanych stylw, ktre moemy wykorzysta lub
rozszerza. Jestemy jeszcze winni Czytelnikowi ostrzeenie odnonie do rozszerzania predefiniowanych stylw: wspomniana wczeniej metoda dodawania przedrostka do nazwy w przypadku tych stylw nie zadziaa. Zamiast tego trzeba wykorzysta nadrzdny atrybut znacznika
style, na przykad tak:

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

227

<style name="CustomTextAppearance" parent="@android:style/TextAppearance">


<item ... tutaj umieszczamy rozszerzenia ... />
</style>

Nie musimy zawsze stosowa caego stylu wobec widoku. Moemy w razie potrzeby wprowadzi tylko jego fragment. Jeeli na przykad chcemy, aby kolor tekstu w kontrolce TextView odpowiada kolorowi systemowemu, moemy tego dokona w poniszy sposb:
<EditText id="@+id/et2"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:textColor="?android:textColorSecondary"
android:text="@string/hello_world" />

Zauwamy, e w tym przykadzie warto atrybutu textColor rozpoczyna si od symbolu ?,


a nie @. Dziki temu znakowi Android poszukuje wartoci stylu w biecym motywie. Poniewa
mamy do czynienia z czonem ?android, szukamy tej wartoci w motywie systemowym
Androida.

Stosowanie motyww
Problemem dotyczcym stylw jest konieczno dodawania specyfikacji atrybutu style
="@style/..." do kadej definicji widoku, do ktrego dany styl ma zosta zastosowany. Jeeli
chcemy wprowadzi pewne elementy formatowania w zakresie caej aktywnoci lub aplikacji,
do tego celu najlepiej nadaje si motyw. Zasadniczo motyw jest stylem, ktry moe zosta
zastosowany w szerszym zakresie, natomiast pod ktem definiowania nie rni si niczym
od stylu. W rzeczywistoci style i motywy s do czsto stosowane zamiennie, poniewa mona
rozszerzy motyw o styl albo odnosi si w motywie do stylu. Zazwyczaj potrafimy rozpozna
jedynie po nazewnictwie, czy styl peni rol stylu, czy te motywu.
W celu zdefiniowania motywu dla aktywnoci lub aplikacji musimy doda odpowiedni atrybut
w znaczniku <activity> lub <application> w pliku AndroidManifest.xml danego projektu.
Ten kod moe wyglda nastpujco:
<activity android:theme="@style/MyActivityTheme">
<application android:theme="@style/MyApplicationTheme">
<application android:theme="@android:style/Theme.NoTitleBar">

Predefiniowane motywy Androida mona znale w tym samym katalogu co predefiniowane


style. Zostay one umieszczone w pliku themes.xml. Po otwarciu tego pliku ujrzymy olbrzymi
zbir zdefiniowanych stylw, ktrych nazwy rozpoczynaj si od czonu Theme. Warto take
zauway, e w kodzie tych stylw i motyww bardzo czsto wida zapisy wiadczce o rozszerzeniach, dlatego nie powinna dziwi taka nazwa stylu jak na przykad Theme.Dialog.AppError.
Na tym zakoczymy omawianie zestawu kontrolek w Androidzie. Jak zostao wspomniane na
pocztku rozdziau, opanowanie sztuki budowania interfejsw uytkownika wymaga znajomoci dwch elementw: zestawu kontrolek oraz menederw ukadu graficznego. W nastpnym
podrozdziale zajmiemy si drugim z wymienionych skadnikw.

Menedery ukadu graficznego


Android zawiera zbir klas widoku, penicych rol pojemnikw na widoki. Te kontenerowe
klasy nosz nazw ukadw graficznych (ang. layout) lub menederw ukadw graficznych
(ang. layout manager). Kada z nich wnosi okrelony sposb zarzdzania rozmiarem oraz pozycj

228 Android 3. Tworzenie aplikacji


elementw podrzdnych. Na przykad klasa LinearLayout umieszcza elementy potomne jeden
za drugim w orientacji poziomej lub pionowej. Wszystkie menedery ukadw graficznych wywodz si z klasy View, zatem moemy je zagnieda jeden w drugim.
Dostpne w zestawie Android SDK menedery ukadu graficznego zdefiniowano w tabeli 6.2.
Tabela 6.2. Menedery ukadu graficznego w Androidzie
Meneder ukadu
graficznego

Opis

LinearLayout

Rozmieszcza elementy podrzdne w pionie lub poziomie.

TableLayout

Rozmieszcza elementy podrzdne w postaci tabelarycznej.

RelativeLayout

Rozmieszcza elementy podrzdne wzgldem innych elementw


lub pojemnika nadrzdnego.

FrameLayout

Umoliwia dynamiczne zmienianie kontrolki (kontrolek) w ukadzie graficznym.

Wymienione menedery ukadu graficznego zostan omwione w nastpnych punktach.


Meneder AbsoluteLayout jest ju przestarzay, wic zostanie w tej ksice pominity.

Meneder ukadu graficznego LinearLayout


Klasa LinearLayout stanowi przykad najprostszego ukadu graficznego. Meneder ten
rozmieszcza elementy potomne w poziomie lub w pionie, zalenie od wartoci waciwoci
orientation. Do tej pory zdylimy ju kilkakrotnie wykorzysta ten ukad graficzny. Na
listingu 6.39 przedstawiono konfiguracj poziom elementw potomnych.
Listing 6.39. Klasa LinearLayout z wprowadzon konfiguracj poziom
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">

<!-- tutaj s dodawane elementy potomne-->


</LinearLayout>

Klas LinearLayout mona przeksztaci do orientacji pionowej poprzez zmian wartoci


waciwoci orientation na vertical. Poniewa menedery ukadw graficznych mog by
zagniedane, moglibymy na przykad utworzy formularz skadajcy si z pionowego ukadu
graficznego zawierajcego w sobie poziome menedery. Wszystkie wiersze posiadayby etykiet
tu obok kontrolki EditText. Kady taki wiersz byby swoim wasnym poziomym ukadem
graficznym, ale zbir tych wierszy byby uoony w pionie.

Ciar oraz grawitacja


Waciwo orientation jest pierwszym z istotnych atrybutw rozpoznawanych przez meneder LinearLayout. Innymi wanymi waciwociami, wpywajcymi na rozmiary oraz rozmieszczenie kontrolek potomnych, s ciar (ang. weight) i grawitacja (ang. gravity). Dziki
ciarowi przypisywany jest stopie wanoci kontrolki w odniesieniu do innych kontrolek

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

229

w pojemniku. Zamy, e w pojemniku umieszczono trzy kontrolki: jedna ma zdefiniowany


ciar rwny 1, pozostaym dwm zostaa natomiast przypisana warto 0. W takim przypadku
kontrolka o wartoci ciaru 1 zajmie ca niezajt przestrze pojemnika. Grawitacja w zasadzie okrela sposb wyrwnania elementu w ukadzie graficznym. Jeeli na przykad tekst etykiety ma zosta wyrwnany do prawej strony, naley przypisa atrybutowi gravity warto
right. Dostpnych jest kilka rnych wartoci atrybutu gravity, na przykad left, center,
right, top, bottom, center_vertical, clip_horizontal i inne. Szczegy dotyczce tych oraz
pozostaych wartoci tej waciwoci mona znale, korzystajc z adresw umieszczonych
w podrozdziale Odnoniki.
Menedery ukadu graficznego rozszerzaj klas android.widget.ViewGroup,
podobnie jak wiele klas pojemnikw opartych na kontrolkach, na przykad ListView.
Chocia rozszerzaj one t sam klas, klasy menederw ukadu graficznego su
wycznie do definiowania rozmiarw oraz rozmieszczenia kontrolek, a nie do interakcji
uytkownika z kontrolkami potomnymi. Na przykad porwnajmy obiekty LinearLayout
i ListView. Na ekranie wygldaj podobnie, gdy obydwa mog organizowa elementy
potomne w orientacji pionowej. Jednak kontrolka ListView zawiera interfejsy API
umoliwiajce uytkownikowi zaznaczanie elementw, czego nie mona powiedzie
o klasie LinearLayout. Innymi sowy, pojemnik oparty na kontrolkach (ListView)
obsuguje interakcj uytkownika z elementami w nich umieszczonymi, podczas
gdy meneder ukadu graficznego (LinearLayout) zajmuje si jedynie okrelaniem
rozmiarw i rozmieszczaniem potomkw.

Przyjrzyjmy si przykadowi zwizanemu z waciwociami ciaru i grawitacji (rysunek 6.18).

Rysunek 6.18. Stosowanie menedera ukadu graficznego LinearLayout

Na rysunku 6.18 pokazano trzy interfejsy uytkownika wykorzystujce meneder Linear


z ktrych kady posiada inn konfiguracj ciaru i grawitacji. Interfejs ukazany po
lewej stronie posiada domylne ustawienia ciaru i grawitacji. Plik XML tego ukadu graficznego zawiera kod zaprezentowany na listingu 6.40.

Layout,

230 Android 3. Tworzenie aplikacji


Listing 6.40. Trzy pola tekstowe umieszczone pionowo w menederze LinearLayout
przy domylnych ustawieniach ciaru i grawitacji
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="raz"/>
<EditText android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="dwa"/>
<EditText android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="trzy"/>
</LinearLayout>

rodkowy interfejs na rysunku 6.18 zawiera domyln warto ciaru, ale parametry argumentu android:gravity zostay zdefiniowane dla kontrolek w kolejnoci: left, center, right.
W przykadzie po prawej atrybut android:layout_weight rodkowego elementu wynosi 1.0,
natomiast w pozostaych dwch kontrolkach nie zmieniono domylnej wartoci 1.0 (listing
6.41). Sprawiamy w ten sposb, e rodkowy element zajmie ca woln przestrze pojemnika
nadrzdnego, a dwie skrajne kontrolki pozostan przy swoich domylnych rozmiarach.
Listing 6.41. Konfigurowanie ciaru w menederze LinearLayout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText android:layout_width="fill_parent" android:layout_weight="0.0"
android:layout_height="wrap_content" android:text="raz"
android:gravity="left"/>
<EditText android:layout_width="fill_parent" android:layout_weight="1.0"
android:layout_height="wrap_content" android:text="dwa"
android:gravity="center"/>
<EditText android:layout_width="fill_parent" android:layout_weight="0.0"
android:layout_height="wrap_content" android:text="trzy"
android:gravity="right"
/>
</LinearLayout>

Analogicznie, jeeli chcemy, eby dwie kontrolki z trzech podzieliy midzy siebie pozosta
woln przestrze, wprowadzamy im warto ciaru rwn 1.0, w trzeciej wartoci natomiast
pozostawiamy ten argument niezmieniony (0.0). W kocu trzeci moliwoci jest podzielenie
ekranu midzy trzy kontrolki w rwnym stopniu, osignite poprzez przydzielenie kadej z nich
wartoci ciaru wynoszcej 1.0. W ten sposb kade pole tekstowe zostanie rozcignite w takim samym stopniu.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

231

Porwnanie atrybutw android:gravity i android:layout_gravity


Zwrmy uwag, e w Androidzie zdefiniowano dwa podobne atrybuty: android:gravity oraz
android:layout_gravity. Rnica polega na tym, e android:gravity jest uywany przez
widok, a android:layout_gravity przez pojemnik (android.view.ViewGroup). Na przykad
mona ustanowi warto atrybutu android:gravity na center, aby wyrodkowa tekst zawarty w kontrolce EditText. Natomiast aby umieci kontrolk EditText po prawej stronie
pojemnika LinearLayout, naley wpisa nastpujcy wiersz: android:layout_gravity="right"
(rysunek 6.19 i listing 6.42).

Rysunek 6.19. Wprowadzanie ustawie grawitacji


Listing 6.42. Pokazanie rnicy pomidzy atrybutami android:gravity a android:layout_gravity
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText android:layout_width="wrap_content" android:gravity="center"
android:layout_height="wrap_content" android:text="raz"
android:layout_gravity="right"/>
</LinearLayout>

Na rysunku 6.19 wida, e wewntrz kontrolki EditText zawarto jest wyrodkowana,


natomiast sama kontrolka zostaa umieszczona po prawej stronie pojemnika LinearLayout.

Meneder ukadu graficznego TableLayout


Meneder ukadu graficznego TableLayout jest rozwiniciem menedera LinearLayout. Potomne kontrolki s w nim ukadane w wierszach i kolumnach. Na listingu 6.43 pokazano przykad:
Listing 6.43. Prosty meneder TableLayout
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow>
<TextView android:text="Imi:"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<EditText android:text="Edgar"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
</TableRow>
<TableRow>

232 Android 3. Tworzenie aplikacji


<TextView android:text="Nazwisko"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<EditText android:text="Poe"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
</TableRow>
</TableLayout>

W celu skorzystania z menedera TableLayout naley utworzy jego instancj, a nastpnie


umieci w nim elementy TableRow. W nich s przechowywane kontrolki tabeli. Interfejs uytkownika utworzony na listingu 6.43 zosta pokazany na rysunku 6.20.

Rysunek 6.20. Meneder ukadu graficznego TableLayout

Poniewa elementy menedera TableLayout s definiowane w wierszach, a nie w kolumnach,


Android okrela liczb kolumn tabeli poprzez wyszukanie wiersza zawierajcego najwiksz
liczb komrek. Na przykad na listingu 6.44 utworzono dwa wiersze, gdzie pierwszy wiersz
zawiera dwie komrki, a drugi trzy (rysunek 6.21). W takim przypadku Android tworzy tabel zawierajc dwa wiersze i trzy kolumny. Komrka znajdujca si w trzeciej kolumnie pierwszego wiersza jest pusta.
Listing 6.44. Definicja nieregularnej tabeli
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow>
<TextView android:text="Imi:"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<EditText android:text="Edgar"
android:layout_width="wrap_content" android:layout_height="wrap_content" />

</TableRow>
<TableRow>
<TextView android:text="Nazwisko:"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<EditText android:text="Allan"

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

233

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


<EditText android:text="Poe"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
</TableRow>
</TableLayout>

Rysunek 6.21. Meneder TableLayout reprezentujcy nieregularn tabel

Na listingach 6.43 oraz 6.44 wypenilimy meneder TableLayout elementami TableRow. Chocia
jest to zwyczajne podejcie, mona take umieci dowolny element android.widget.View
jako potomka tabeli. Na przykad w listingu 6.45 utworzono tabel, w ktrej pierwszym wierszem jest kontrolka EditText (rysunek 6.22).
Listing 6.45. Zastosowanie kontrolki EditText zamiast TableRow
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="0,1,2">
<EditText
android:text="Imi i nazwisko:"/>
<TableRow>
<TextView android:text="Edgar"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<TextView android:text="Allan"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<TextView android:text="Poe"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
</TableRow>
</TableLayout>

Interfejs uytkownika utworzony na listingu 6.45 zosta ukazany na rysunku 6.22. Zauwamy,
e kontrolka EditText zajmuje ca szeroko ekranu, chocia nawet nie zawarlimy odpowiedniego parametru w ukadzie graficznym XML. Wynika to z faktu, e wszystkie elementy

234 Android 3. Tworzenie aplikacji

Rysunek 6.22. Kontrolka EditText jako potomek w menederze TableLayout

podrzdne menedera TableLayout s zawsze rozcigane na dugo wiersza. Innymi sowy,


elementy potomne menedera TableLayout mog definiowa parametr android:layout_width
="wrap_content" (podobnie jak to zrobilimy w kontrolce EditText), nie wpynie to jednak
na rzeczywisty ukad graficzny musz przyjmowa warto fill_parent. Mona tu jednak
dostosowa argument android:layout_height.
Poniewa zawarto tabeli nie zawsze jest znana podczas etapu projektowania, meneder
TableLayout posiada kilka atrybutw umoliwiajcych kontrolowanie ukadu graficznego tabeli.
Na przykad na listingu 6.45 wida, e parametry waciwoci android:stretchColumns wynosz
odpowiednio 0, 1, 2. Dla menedera TableLayout oznacza to, e w zalenoci od treci kolumny nr
0, 1 oraz 2 zostan rozcignite. Gdyby na listingu 6.45 nie uyto argumentu stretchColumns,
efektem byoby zczenie trzech wyrazw EdgarAllanPoe. Pod wzgldem technicznym drugi
rzd zajmuje ca szeroko, ale trzy kontrolki TextView nie nachodz na niego.
W podobny sposb mona za pomoc argumentu android:shrinkColumns cisn zawarto
wybranych kolumn, gdy inne kolumny potrzebuj wicej miejsca. Mona take wprowadzi
waciwo android:collapseColumns, dziki ktrej wybrane kolumny staj si niewidoczne.
Naley pamita, e kolumny s numerowane, poczwszy od cyfry 0.
Meneder TableLayout zawiera take atrybut android:layout_span. Jest on uywany do
rozcignicia komrki na wiele kolumn. Atrybut ten przypomina waciwo colspan w jzyku HTML.
Nieraz pojawi si potrzeba utworzenia odstpw wewntrz zawartoci komrki lub kontrolki.
Przydatny jest wtedy atrybut android:padding oraz jemu podobne. Dziki niemu moliwe jest
kontrolowanie przestrzeni pomidzy zewntrznymi granicami widoku a jego treci (listing 6.46).
Listing 6.46. Zastosowanie waciwoci android:padding
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText android:text="raz"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:padding="40px" />
</LinearLayout>

Na listingu 6.46 zostay wyznaczone odstpy rwne 40px. W ten sposb 40 pikseli biaej przestrzeni oddziela tre kontrolki EditText od jej kracw. Na rysunku 6.23 zademonstrowano
t sam kontrolk EditText, ktrej nadano dwie rne wartoci odstpw. Interfejs po lewej
stronie nie ma zdefiniowanych adnych odstpw, a interfejs po prawej posiada dopisan linijk
android:padding="40px".

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

235

Rysunek 6.23. Zastosowanie odstpw

Atrybut android:padding tworzy odstpy we wszystkich kierunkach: w lewo, w prawo, w gr


i w d. Mona kontrolowa rozmiar odstpu dla kadej strony dziki atrybutom android:
leftPadding, android:rightPadding, android:topPadding oraz android:bottomPadding.
W Androidzie mona rwnie zdefiniowa atrybut android:layout_margin, bardzo przypominajcy android:padding. W rzeczywistoci rnica pomidzy atrybutami android:padding/
android:layout_margin jest taka jak w przypadku android:gravity/android:layout_
gravity. Oznacza to, e jeden atrybut jest przeznaczony dla widoku, a drugi dla pojemnika.
W kocu naley zwrci uwag, e warto odstpu jest zawsze wyraana jako wymiar okrelonego typu. Android obsuguje nastpujce typy wymiarw:
Piksele w skrcie px. Wymiar ten reprezentuje fizyczne piksele ekranu.
Cale w skrcie in. Jednostka ta reprezentuje rzeczywisty rozmiar obiektu
na wywietlaczu w calach.
Milimetry w skrcie mm. Jednostka ta reprezentuje rzeczywisty rozmiar obiektu
na wywietlaczu w milimetrach.
Punkty w skrcie pt. Jeden punkt jest rwny 1/72 cala.
Piksele niezalene od gstoci w skrcie dip albo dp. W tym przypadku ekran
o gstoci 160 pikseli na cal jest wykorzystywany jako ramka odniesienia, dla ktrej
wymiary obiektu s odwzorowywane na rzeczywistym ekranie. Na przykad ekran
o szerokoci 160 pikseli bdzie odwzorowywa 1 dip na 1 piksel.
Piksele niezalene od skali w skrcie sp. Wymiar uywany przewanie przy
pracy z czcionkami. S tu brane pod uwag ustawienia uytkownika oraz rozmiar
czcionki w celu okrelenia rzeczywistego wymiaru.
Warto zapamita, e wymienione tu rodzaje wymiarw nie s przeznaczone wycznie do definiowania odstpw kady obiekt w Androidzie akceptujcy wartoci wymiaru (na przykad
android:layout_width lub android:layout_height) bdzie w stanie odczyta te jednostki.

Meneder ukadu graficznego RelativeLayout


Kolejnym interesujcym menederem ukadu graficznego jest RelativeLayout. Jak sama nazwa
sugeruje, meneder ten implementuje zasady, dziki ktrym pozycja kontrolek znajdujcych
si w pojemniku jest zalena od pojemnika lub innej umieszczonej w nim kontrolki. Odpowiedni
przykad pokazano na listingu 6.47 oraz na rysunku 6.24.

236 Android 3. Tworzenie aplikacji


Listing 6.47. Zastosowanie menedera ukadu graficznego RelativeLayout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:id="@+id/userNameLbl"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Nazwa uytkownika: "
android:layout_alignParentTop="true" />
<EditText android:id="@+id/userNameText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/userNameLbl" />
<TextView android:id="@+id/pwdLbl"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/userNameText"
android:text="Haso: " />
<EditText android:id="@+id/pwdText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/pwdLbl" />
<TextView android:id="@+id/pwdCriteria"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/pwdText"
android:text="Kryteria hasa... " />
<TextView android:id="@+id/disclaimerLbl"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="Uywaj tego na wasn odpowiedzialno... " />
</RelativeLayout>

Jak wida, interfejs uytkownika przypomina prosty formularz logowania. Etykieta nazwy uytkownika zostaa przypita do grnej czci pojemnika, poniewa atrybutowi android:layout_
alignParentTop przypisano warto true. W podobny sposb pole wpisywania nazwy uytkownika znalazo si poniej etykiety, gdy wprowadzono atrybut android:layout_below.
Etykieta hasa jest umieszczona jeszcze niej, pod ni za wprowadzono pole wpisywania
hasa. Ostrzeenie znalazo si na samym dole pojemnika, gdy w tej kontrolce atrybutowi
android:layout_alignParentBottom nadano warto true.
Poza trzema wymienionymi atrybutami dostpne s jeszcze inne, takie jak layout_above,
layout_toRightOf, layout_toLeftOf, layout_centerInParent i inne. Praca z menederem
RelativeLayout jest przyjemna z powodu atwoci jego obsugi. Istotnie, dla kadego, kto
zacznie go uywa, stanie si on ulubionym menederem ukadu graficznego bez przerwy
bdzie si do niego wraca.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

237

Rysunek 6.24. Interfejs uytkownika utworzony za pomoc menedera ukadu graficznego


RelativeLayout

Meneder ukadu graficznego FrameLayout


Menedery ukadu graficznego omwione do tej pory wprowadzaj rne strategie kompozycji elementw. Innymi sowy, kady z nich w okrelony sposb wstawia i orientuje kontrolki
potomne na ekranie. Dziki menederom mona wstawi na jednym ekranie jednoczenie
wiele kontrolek, z ktrych kada zajmuje okrelony fragment pojemnika. W Androidzie dostpny jest take meneder ukadu graficznego, uywany przede wszystkim do wywietlania
pojedynczego skadnika FrameLayout. Ta klasa ukadu graficznego suy gwnie do dynamicznego wywietlania pojedynczego widoku, mona j jednak zapeni wieloma elementami,
z ktrych jeden bdzie widoczny, a pozostae nie. Na listingu 6.48 zademonstrowano zastosowanie menedera FrameLayout.
Listing 6.48. Zapenianie menedera FrameLayout
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/frmLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView
android:id="@+id/oneImgView" android:src="@drawable/one"
android:scaleType="fitCenter"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<ImageView
android:id="@+id/twoImgView" android:src="@drawable/two"

238 Android 3. Tworzenie aplikacji


android:scaleType="fitCenter"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone" />
</FrameLayout>
public class FrameLayoutActivity extends Activity{
private ImageView one = null;
private ImageView two = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listing6_48);
one = (ImageView)this.findViewById(R.id.oneImgView);
two = (ImageView)this.findViewById(R.id.twoImgView);
one.setOnClickListener(new OnClickListener(){
public void onClick(View view) {
two.setVisibility(View.VISIBLE);
view.setVisibility(View.GONE);
}});
two.setOnClickListener(new OnClickListener(){
public void onClick(View view) {
one.setVisibility(View.VISIBLE);
view.setVisibility(View.GONE);
}});
}
}

Listing 6.48 przedstawia plik ukadu graficznego oraz metod onCreate() aktywnoci. Celem
wiczenia jest wczytanie dwch widokw ImageView w menederze FrameLayout w taki sposb,
eby w danym momencie by widoczny tylko jeden z nich. Na poziomie interfejsu uytkownika,
kiedy uytkownik kliknie widoczny obrazek, aplikacja go schowa i wywietli drugi obiekt.
Przyjrzyjmy si bliej kodowi zamieszczonemu na listingu 6.48, poczwszy od ukadu graficznego. Mona zauway, e definiujemy meneder FrameLayout zawierajcy dwie kontrolki
ImageView (s one odpowiedzialne za waciwe wywietlanie obrazw). Zwrmy uwag, e
widoczno drugiego obiektu ImageView przyjmuje warto gone, dziki czemu staje si on
niewidoczny. Spjrzmy teraz na metod onCreate(). Rejestrujemy w niej elementy nasuchujce, reagujce na kliknicia widokw ImageView. W procedurze obsugi kliknicia programujemy ukrycie jednego obiektu ImageView wraz z jednoczesnym wywietleniem drugiego obiektu.
Jak ju wczeniej wspomnielimy, menedera FrameLayout zazwyczaj uywa si podczas dynamicznego konfigurowania treci widoku w pojedynczej kontrolce. Chocia jest to standardowa praktyka, pokazalimy rwnie, e kontrolka akceptuje take wiele obiektw potomnych.
Na listingu 6.48 do ukadu graficznego dodano dwie kontrolki, jednak w danym momencie

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

239

widoczna jest tylko jedna z nich. Meneder FrameLayout nie wymusza jednak takiego rozwizania. Jeeli do ukadu graficznego dodamy wiele kontrolek, Android po prostu utworzy ich stos,
w ktrym jedna kontrolka bdzie naoona na drug, ostatnia natomiast bdzie si znajdowa
na jego szczycie. W ten sposb mona stworzy interesujcy interfejs uytkownika. Na przykad na rysunku 6.25 pokazano meneder FrameLayout, w ktrym s widoczne dwa widoki
ImageView. Wida, e kontrolki s uoone na stosie, a ta znajdujca si na wierzchu czciowo
zasania obiekt umieszczony za ni.
Kolejny interesujcy fakt dotyczcy menedera FrameLayout jest taki, e po dodaniu do ukadu
graficznego wicej ni jednej kontrolki rozmiar tego ukadu jest definiowany jako rozmiar
najwikszego elementu w pojemniku. Na rysunku 6.25 element znajdujcy si na wierzchu jest
w rzeczywistoci znacznie mniejszy od elementu umieszczonego pod spodem, jednak poniewa
ukad graficzny jest dopasowany do najwikszego obiektu, obraz z pierwszego planu zostaje
rozcignity.

Rysunek 6.25. Meneder FrameLayout zawierajcy dwa widoki ImageView

Warto take pamita, e jeli umieci si w menederze FrameLayout wiele elementw, z ktrych cz zostanie zdefiniowana jako niewidoczne, mona rozway wykorzystanie metody
setMeasureAllChildren(true) na ukadzie FrameLayout. Skoro najwikszy element
podrzdny definiuje rozmiar caego ukadu graficznego, moe si pojawi problem, gdy takim
elementem okae si obiekt niewidoczny. To znaczy, e jeeli pojawi si na pierwszym planie, bdzie widoczna jedynie jego cz. Po wprowadzeniu metody setMeasureAllChildren() z wartoci true wszystkie elementy powinny by prawidowo wywietlane. Rwnowanym atrybutem
XML dla ukadu FrameLayout jest android:measureAllChildren="true".

Dostosowanie ukadu graficznego


do konfiguracji rnych urzdze
Jak doskonale wiemy, e Android zawiera wiele menederw ukadu graficznego pomagajcych w budowaniu interfejsw uytkownika. Jeeli kto ju wyprbowa omawiane przez nas
menedery, bdzie wiedzia, e mona je czy na wiele sposobw w celu uzyskania podanego
wygldu i sposobu funkcjonowania danego interfejsu. Jednak nawet za ich pomoc tworzenie
poprawnie dziaajcych interfejsw UI stanowi wyzwanie. Dotyczy to zwaszcza urzdze

240 Android 3. Tworzenie aplikacji


przenonych. Oczekiwania uytkownikw oraz producentw urzdze przenonych staj si
coraz bardziej wysublimowane, przez co poprzeczka stojca przed programist aplikacji zostaje
podniesiona jeszcze wyej.
Jednym z takich wyzwa jest tworzenie interfejsu uytkownika aplikacji, ktra bdzie wywietlona w rnych konfiguracjach wywietlacza. Trzeba sobie odpowiedzie na pytanie, jak wygldaby taki interfejs UI na wywietlaczu uoonym w orientacji poziomej, a jak w pionowej?
Czytelnicy, ktrzy jeszcze nie spotkali si z tym problemem, prawdopodobnie usilnie staraj si
teraz wyobrazi sobie rozwizanie tego dosy powszechnego scenariusza. Na szczcie Android
w interesujcy sposb pomaga poradzi sobie z tym problemem.
Dziaa to w nastpujcy sposb: w trakcie tworzenia ukadu graficznego Android na podstawie
konfiguracji urzdzenia znajduje oraz wczytuje ukady graficzne z okrelonych folderw.
Urzdzenie moe si znajdowa w jednej z trzech konfiguracji: pionowej (ang. portrait), poziomej (ang. landscape) lub umoliwiajcej wywietlanie kwadratowego obrazu (ang. square),
ktra jest najrzadziej spotykana. eby ukady graficzne byy waciwie wywietlane w tych rnych konfiguracjach, naley utworzy dla nich oddzielne foldery, z ktrych Android bdzie
wczytywa waciwe ukady graficzne. Jak wiadomo, domylnym folderem ukadu graficznego
jest res/layout. Aby obsuy konfiguracj pionow, naley utworzy folder res/layout-port; dla
trybu poziomego bdzie to res/layout-land, a dla kwadratowego res/layout-square.
W tym momencie nasuwa si pytanie: jeli zdefiniowano te trzy foldery, to czy jest potrzebny
jeszcze domylny katalog ukadu graficznego (res/layout)? Zasadniczo tak. Pamitajmy, e logika
Androida, polegajca na rozpoznawaniu zasobw, sprawdza najpierw katalog okrelony w konfiguracji. Jeeli nie zostan tam znalezione zasoby, system przechodzi do domylnego folderu
ukadu graficznego. Zatem mona umieci domylne definicje ukadu graficznego w folderze
res/layout, a ich odpowiednio dostosowane wersje w folderach konfiguracji.
Zauwamy, e rodowisko Android SDK nie posiada interfejsw API, ktre pozwalayby
w sposb programistyczny okreli rodzaj wczytywanej konfiguracji system sam wybiera
folder na podstawie wykrytej konfiguracji urzdzenia. Mona jednak w kodzie okreli orientacj
urzdzenia, na przykad w sposb przedstawiony poniej:
import android.content.pm.ActivityInfo;
...
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

W ten sposb urzdzenie zostaje zmuszone do wywietlenia aplikacji w orientacji poziomej.


Mona miao wyprbowa ten sposb we wczeniejszych projektach. Powyszy fragment kodu
naley doda do metody onCreate() danej aktywnoci, uruchomi j w emulatorze i ju mona podziwia odwrcon aplikacj na wywietlaczu.
Ukad graficzny nie jest jedynym zasobem korzystajcym z konfiguracji. Inne kwalifikatory
konfiguracji urzdzenia s rwnie brane pod uwag po znalezieniu odpowiedniego zasobu. Caa
zawarto folderu res moe posiada odpowiedniki w kadej konfiguracji. Aby na przykad dla
kadej konfiguracji przygotowa obiekty rysowane, naley utworzy katalogi drawable-port,
drawable-land oraz drawable-square. Jednak Android jest jeszcze potniejszy. W tabeli 6.3
wypisano pen list kwalifikatorw, ktre s wykorzystywane po znalezieniu zasobw.
Wicej informacji na temat tych kwalifikatorw mona znale na stronie Androida pod
adresem:
http://developer.android.com/guide/topics/resources/providing-resources.html#table2

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

241

Tabela 6.3. Kwalifikatory zasobw


Kwalifikator

Opis

Numery MCC i MNC

Identyfikatory kraju oraz operatora sieci.

Jzyk i region

Dwuliterowy kod jzyka pisany maymi literami, po dodaniu


parametru -r take dwuliterowy kod regionu pisany duymi literami.

Rozmiary ekranu

Daje oglne pojcie na temat rozmiaru ekranu; wartoci: small,


normal, large oraz xlarge.

Szersze/wysze ekrany

Zwizane z proporcjami obrazu; wartoci: long, notlong.

Orientacja ekranu

Wartoci: land, port oraz square.

Gsto pikseli na ekranie

Przyblione gstoci: ldpi (ok. 120), mdpi (ok. 160), hdpi (ok. 240)
oraz xhdpi (ok. 320). Android moe dopasowywa zasoby znalezione
w odpowiednich folderach, chyba e s one umieszczone w katalogu
zawierajcym kwalifikator nodpi.

Typ ekranu dotykowego

Wartoci: finger, notouch oraz stylus.

Klawiatura

Rodzaj klawiatury. Wartoci: keysexposed, keyshidden oraz


keyssoft.

Rodzaj tekstowych danych


wejciowych

Wartoci: nokeys, qwerty oraz 12key (numeryczne).

Sterowanie przy braku


klawiatury dotykowej

Wartoci: dpad, nonav, trackball oraz wheel.

Wersja rodowiska SDK

Wartoci: v4 (SDK 1.6), v7 (SDK 2.1) itd.

Powysze kwalifikatory mog by uywane w wielu kombinacjach, aby uzyska podane zachowanie. Nazwa katalogu zasobw moe nie zawiera adnej z wartoci kwalifikatorw lub
zawiera wiele takich wartoci oddzielonych mylnikami. Na przykad poniej pokazalimy
poprawn pod wzgldem technicznym (chocia niezalecan) nazw katalogu zasobw typu
drawable:
drawable-mcc310-en-rUS-large-long-port-mdpi-stylus-keyssoft-qwerty-dpad-v3
Mona jednak zapisa to rwnie w nastpujcy sposb:
drawable-en-rUS-land (obrazy dla wersji angielskiej ze Stanw Zjednoczonych,
w orientacji poziomej)
values-fr (cig znakw w jzyku francuskim)
Bez wzgldu na liczb kwalifikatorw wykorzystanych w zasobach aplikacji naley pamita, e
w kodzie cay czas naley odwoywa si do zasobw w postaci R.rodzaj_zasobu.nazwa, bez
kwalifikatorw. Jeli na przykad istnieje wiele rnych odmian pliku ukadu graficznego
main.xml w rnych kwalifikowanych katalogach zasobw, w kodzie nadal bdziemy si do
niego odwoywa za pomoc wyraenia R.layout.main. Android sam zajmuje si odnalezieniem waciwego pliku main.xml.

242 Android 3. Tworzenie aplikacji

Usuwanie bdw i optymalizacja ukadw


graficznych za pomoc narzdzia Hierarchy Viewer
W zestawie Android SDK znalazo si wiele narzdzi, ktre znacznie uatwiaj ycie projektanta
aplikacji. Poniewa zajmujemy si tematem tworzenia interfejsw uytkownika, warto zapozna si z narzdziem Hierarchy Viewer. Narzdzie to, pokazane na rysunku 6.26, pozwala na
usuwanie bdw w interfejsach UI z poziomu ukadu graficznego.

Rysunek 6.26. Widok ukadu graficznego w aplikacji Hierarchy Viewer

Jak wida na rysunku 6.26, narzdzie to ukazuje hierarchi widokw w formie drzewa. Koncepcja
dziaania narzdzia jest nastpujca: najpierw narzdzie wczytuje ukad graficzny, a nastpnie go
analizuje pod wzgldem okrelenia moliwych problemw lub prby zoptymalizowania ukadu
graficznego pod ktem minimalizacji liczby widokw (kwestia wydajnoci).
W celu wyszukania bdw w interfejsie uytkownika naley uruchomi emulator i wyszuka
interfejs UI, ktry zostanie sprawdzony. Nastpnie trzeba odnale narzdzie Hierarchy Viewer
w katalogu /tools rodowiska Android SDK. W przypadku systemu Windows bdzie si tam
znajdowa plik wsadowy hierarchyviewer.bat. Po jego uruchomieniu zostanie wywietlony ekran
urzdze (rysunek 6.27).
Okno Devices wywietla list uruchomionych urzdze (w naszym przypadku emulatorw). Po
rozwiniciu wza urzdzenia w prawym panelu ukae si lista ekranw dostpnych w tym
urzdzeniu. eby zobaczy hierarchi widokw dla danego ekranu, naley go zaznaczy (przewanie jest to pena nazwa aktywnoci, w ktrej przedrostkiem jest nazwa pakietu aplikacji),
a nastpnie klikn przycisk Load View Hierarchy.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

243

Rysunek 6.27. Ekran urzdze w aplikacji Hierarchy Viewer

W oknie View Hierarchy zostanie wywietlona hierarchia widokw w panelu po lewej stronie
(rysunek 6.26). Po zaznaczeniu elementu widoku w rodkowym panelu zostan wywietlone
jego waciwoci, a w obrazie szkieletowym po prawej stronie take relatywna wobec innych
widokw lokalizacja tego widoku. Zaznaczony widok bdzie podwietlony na czerwono. Majc
w ten sposb ukazan hierarchi widokw, moemy poszuka sposobu na zmniejszenie ich
liczby, co jest rwnoznaczne z przyspieszeniem dziaania aplikacji.
Na rysunku 6.27 mona dostrzec trzy przyciski w lewym dolnym rogu okna Hierarchy Viewer.
Przycisk po lewej wcza omwiony powyej widok drzewa. rodkowy przycisk odpowiada
za wywietlanie okna View Hierarchy. Przycisk po prawej wywietla biecy ukad graficzny
w widoku Pixel Perfect, jednak dopiero po zainicjalizowaniu tego widoku za pomoc przycisku
Inspect Screenshot, widocznego w grnej czci narzdzia. Widok ten jest bardzo interesujcy,
poniewa przedstawia ukad graficzny w siatce pikselowej (rysunek 6.28). Znajduje si tu kilka
ciekawych elementw. Po lewej stronie jest umieszczony widok eksploratora wszystkich skadnikw ekranu. Po klikniciu jednego z umieszczonych tu elementw zostanie on podwietlony
na czerwono w rodkowym panelu. Znajdujce si w nim celowniki pozwalaj wywietli
w prawym panelu przyblienie wybranego fragmentu ekranu (jest to lupa). Dostpna jest rwnie opcja powikszenia, pozwalajca na jeszcze wiksze przyblienie danego rejonu ekranu. Lupa
pokazuje rwnie dokadne wsprzdne (x, y) wybranego piksela oraz warto jego koloru.
Ostatni bardzo interesujc funkcj w tym oknie jest przycisk Load Overlay oraz suwak Overlay.
Istnieje moliwo zaadowania pliku obrazu pod wywietlanym ekranem w celu porwnania
go z tym obrazem (by moe stanowi makiet dla tworzonego ukadu graficznego) oraz zwikszania lub zmniejszania jego widocznoci za pomoc suwaka Overlay. Obraz ten pojawia si
w lewym dolnym rogu. Domylnie nie jest on pokazywany w widoku lupy, jednak mona to
zmieni poprzez zaznaczenie odpowiedniej opcji.

244 Android 3. Tworzenie aplikacji

Rysunek 6.28. Tryb Pixel Perfect narzdzia Hierarchy Viewer

Wraz z wydaniem wersji 2.3 Androida aplikacja Hierarchy Viewer staa si dostpna w rodowisku Eclipse. Pojawiy si w niej nowe perspektywy: Hierarchy Viewer oraz Pixel Perfect, kada
posiadajca zestaw widokw rozwijajcych ich funkcje. Aplikacja ta dziaa w zasadzie tak samo
jak jej samodzielna wersja, omwiona powyej. Osoby majce problem z jej zainstalowaniem
znajd instrukcj, jak j odszuka i wdroy, w rozdziale 2.
Dziki takim narzdziom programista posiada wszechstronn kontrol nad wygldem oraz
dziaaniem aplikacji.

Odnoniki
Poniej prezentujemy odnoniki do zagadnie, z ktrymi warto si dokadniej zapozna.
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw projektw
stworzonych na potrzeby ksiki. Waciwy plik znajdziesz w katalogu o nazwie
ProAndroid3_R06_Kontrolki. Zebrane s w nim wszystkie projekty z niniejszego
rozdziau, rozmieszczone w oddzielnych katalogach. Umiecilimy w nim rwnie
plik Czytaj.TXT, w ktrym zamiecilimy dokadn instrukcj importowania projektw
z plikw ZIP do rodowiska Eclipse.

Rozdzia 6 Budowanie interfejsw uytkownika oraz uywanie kontrolek

245

http://developer.android.com/reference/android/widget/LinearLayout.html#attr_andr
oid:gravity na tej stronie zostay omwione rnorodne wartoci parametru gravity,
stosowanego wraz z ukadem graficznym LinearLayout.
www.curious-creature.org/2010/08/15/scrollviews-handy-trick wpis Romain Guya
(z zespou Androida) wyjaniajcy, jak naley korzysta z kontrolki ScrollView.
http://developer.android.com/resources/articles/index.html na tej stronie zamieszczono
kilka technicznych artykuw pod wspln nazw Layout Tricks, ktrych przeczytanie
bardzo polecamy. Zajmuj si one zagadnieniem wydajnoci podczas projektowania
i programowania interfejsw uytkownika w Androidzie. Warto rwnie przejrze
pozostae artykuy dotyczce budowania interfejsw uytkownika.

Podsumowanie
W tym momencie Czytelnik powinien mie ju dobre pojcie na temat kontrolek dostpnych
w zestawie SDK. Nieobce powinny by take adaptery oraz menedery ukadu graficznego.
Znajc wymagania okrelonego typu ekranu, szybkie identyfikowanie kontrolek oraz menederw ukadw graficznych potrzebnych do skonstruowania wywietlanego ekranu nie powinno by teraz trudne.
W nastpnym rozdziale bdziemy si dalej zajmowa interfejsem uytkownika naszym celem
bd menu.

246 Android 3. Tworzenie aplikacji

R OZDZIA

7
Praca z menu

Zestaw Android SDK pozwala na dokadn obsug menu. W tym rozdziale


wyjanimy, jak korzysta z kilku rodzajw menu obsugiwanych przez Androida:
standardowych menu, podmenu, menu kontekstowych, menu w postaci ikon,
menu drugorzdnych oraz menu alternatywnych. W wersji 3.0 Androida zosta
wprowadzony pasek zada, ktry znakomicie integruje si z listami menu. Pasek
zada oraz jego interakcja z menu zostay omwione w rozdziale 30.
Menu, oprcz tego, e s obiektami rodowiska Java, w Androidzie s traktowane
jako zasoby. Podobnie jak w przypadku pozostaych rodzajw zasobw, mona je
wczytywa z plikw XML. Dla kadego wczytanego elementu menu Android tworzy
identyfikator zasobw. W tym rozdziale szczegowo zajmujemy si takimi zasobami menu w formie XML. Pokaemy take, w jaki sposb wykorzystywa automatycznie wygenerowane identyfikatory zasobw dla kadego rodzaju elementw menu.

Menu w Androidzie
Osoby pracujce w takim rodowisku jak Swing obsugiwane przez jzyk Java,
Windows Presentation Foundation (WPF) w systemie Windows lub w jakimkolwiek innym szkielecie interfejsu uytkownika, z pewnoci zetkny si z menu.
Najwaniejsz klas obsugujc menu w Androidzie jest klasa android.view.Menu.
Kada aktywno w Androidzie jest powizana z tego typu obiektem menu,
w ktrym mona zawrze wiele elementw menu oraz podmenu.
Elementy menu s reprezentowane przez klas android.view.MenuItem, a podmenu przez android.view.SubMenu. Zwizki pomidzy nimi zostay naszkicowane na
rysunku 7.1. cile rzecz biorc, nie jest to diagram klas, lecz diagram strukturalny,
zaprojektowany po to, aby pomc w dostrzeeniu powiza pomidzy rnymi
klasami i funkcjami dotyczcymi menu.

248 Android 3. Tworzenie aplikacji

Rysunek 7.1. Struktura klas zwizanych z menu w Androidzie

Na rysunku 7.1 wida, e klasa Menu skada si z podzbioru elementw.


Element menu posiada take nazw (tytu), swj niepowtarzalny identyfikator oraz identyfikator
kolejnoci na licie (w zestawie SDK nosi on po prostu nazw kolejnoci ang. order), a take
identyfikator (lub numer). Takie identyfikatory kolejnoci su do uporzdkowania elementw wewntrz menu. Jeli na przykad jeden z elementw posiada warto identyfikatora
kolejnoci rwn 4, a inny element ma przyporzdkowan warto 6, pierwszy z obiektw
bdzie widnia ponad drugim.
Pewne zakresy wartoci s zarezerwowane dla okrelonych rodzajw menu. Kolejno elementw menu drugorzdnych, ktre s uznawane za mniej istotne od pozostaych, rozpoczyna
si od wartoci 0x30000 i jest definiowana przez sta Menu.CATEGORY_SECONDARY. Inne rodzaje
kategorii menu na przykad menu systemowe, menu alternatywne czy menu kontenerowe
posiadaj odmienne zakresy wartoci kolejnoci.
Wartoci elementw menu systemowego rozpoczynaj si od 0x20000 i s definiowane przez
sta Menu.CATEGORY_SYSTEM. Wartoci elementw menu alternatywnego rozpoczynaj si od
0x40000 i s definiowane przez sta Menu.CATEGORY_ALTERNATIVE. Wartoci elementw menu
kontenerowego rozpoczynaj si od 0x10000 i s definiowane przez sta Menu.CATEGORY_
CONTAINER. Przygldajc si zakresom wartoci tych staych, mona stwierdzi, w jakiej
kolejnoci elementy menu pojawiaj si na ekranie (rodzaje elementw menu omwimy
w podrozdziale Praca z innymi rodzajami menu).
Moemy grupowa elementy menu poprzez przydzielenie kademu z nich identyfikatora grupy,
ktry jest jednym z atrybutw takiego elementu. Wszystkie obiekty posiadajce ten sam identyfikator grupy s uznawane za elementy jednego zbioru.
Na rysunku 7.1 pokazano rwnie dwie wywoywane metody, dziki ktrym mona tworzy
i obsugiwa elementy menu: onCreateOptionsMenu oraz onOptionsItemSelected. Zajmiemy
si nimi nieco dalej.

Rozdzia 7 Praca z menu

249

Tworzenie menu
W rodowisku Android SDK nie ma potrzeby tworzenia obiektu menu od podstaw. Poniewa
aktywno jest powizana z pojedynczym menu, jest ono tworzone dla tej aktywnoci i przekazywane do metody wywoawczej onCreateOptionsMenu klasy tej aktywnoci (jak wskazuje
nazwa metody, menu w Androidzie zwane s take menu opcji). Metoda ta umoliwia zapenienie przekazywanego jej menu zestawem elementw menu (listing 7.1).
Listing 7.1. Sygnatura metody onCreateOptionsMenu
@Override
public boolean onCreateOptionsMenu(Menu menu)
{

// zapenia elementami menu


...
...return true;
}

Po zapenieniu menu elementami metoda powinna odesa warto true, co oznacza, e menu
stao si widoczne. Jeeli przekazan wartoci bdzie false, menu bdzie niewidoczne. Kod
przedstawiony na listingu 7.2 pokazuje, w jaki sposb doda trzy elementy do menu za pomoc
identyfikatora grupy oraz identyfikatorw elementw menu i identyfikatora kolejnoci o wartociach wzrastajcych.
Listing 7.2. Dodawanie elementw do menu
@Override
public boolean onCreateOptionsMenu(Menu menu)
{

// wywouje klas bazow, zawierajc listy menu systemowych


super.onCreateOptionsMenu(menu);
menu.add(0

// Grupa
// identyfikator elementu
,0
// kolejno
,"Dodaj"); // tytu
,1

menu.add(0,2,1,"element2");
menu.add(0,3,2,"Wyczy");

// Wane jest, eby zostaa zwrcona warto true, co spowoduje wywietlenie menu
return true;
}

Naley take wywoa implementacj klasy podstawowej tej metody, aby system mia moliwo zapenienia menu elementami menu systemowego. eby elementy menu systemowego
byy oddzielone od pozostaych elementw, Android dodaje je, poczwszy od wartoci kolejnoci
rwnej 0x20000 (wspomnielimy wczeniej, e staa Menu.CATEGORY_SYSTEM definiuje identyfikator kolejnoci dla tego typu elementw. Jak na razie w adnej wersji Androida nie zostay
dodane nowe menu systemowe).

250 Android 3. Tworzenie aplikacji


Pierwszym parametrem wymaganym do dodania elementu jest identyfikator grupy (liczba
cakowita). Drugim jest identyfikator elementu menu, odsyany do wywoywanej funkcji po
wybraniu tego elementu. Trzeci argument reprezentuje identyfikator kolejnoci.
Ostatnim argumentem jest nazwa lub tytu elementu menu. Mona skorzysta z zasobu typu
string umieszczonego w pliku staych R.java, zamiast wpisywa tekst. Identyfikatory grupy,
elementu oraz kolejnoci s cakowicie opcjonalne; jeeli nie ma potrzeby ich definiowania,
mona wprowadzi argument Menu.NONE.

Praca z grupami menu


Pokaemy teraz, w jaki sposb mona pracowa z grupami menu. Listing 7.3 przedstawia sposb
dodania dwch grup menu: Grupy 1 oraz Grupy 2.
Listing 7.3. Zastosowanie identyfikatorw grup do utworzenia grup menu
@Override
public boolean onCreateOptionsMenu(Menu menu)
{

// Grupa 1
int group1 = 1;
menu.add(group1,1,1,"g1.item1");
menu.add(group1,2,2,"g1.item2");

// Grupa 2
int group2 = 2;
menu.add(group2,3,3,"g2.item1");
menu.add(group2,4,4,"g2.item2");
return true;

// wane, eby zostaa zwrcona warto true

Zwrmy uwag, e identyfikatory elementw oraz kolejnoci s niezalene dla kadej grupy.
Jaki jest wic poytek z grupy? W klasie android.view.Menu dostpny jest zbir metod, korzystajcych z identyfikatorw grupy. Za ich pomoc mona kontrolowa elementy menu w danej grupie:
removeGroup(id)
setGroupCheckable(id, checkable, exclusive)
setGroupEnabled(id,boolean enabled)
setGroupVisible(id,visible)

Metoda removeGroup usuwa wszystkie elementy z grupy o podanym identyfikatorze. Mona


wcza lub wycza elementy menu w danej grupie za pomoc metody setGroupEnabled.
W podobny sposb, stosujc metod setGroupVisible, kontrolujemy widoczno grupy elementw menu.
Metoda setGroupCheckable jest dosy interesujca. Dziki niej pojawia si znak zaznaczenia
obok elementu wybranego przez uytkownika. Jeeli metoda ta zostanie zastosowana wobec
caej grupy, wszystkie jej elementy uzyskaj t waciwo. W przypadku ustanowienia w tej
metodzie flagi wycznoci moliwe stanie si zaznaczenie wycznie jednego elementu w grupie.
Pozostae elementy grupy bd niezaznaczone.

Rozdzia 7 Praca z menu

251

Wiemy ju, w jaki sposb zapeni gwne menu aktywnoci elementami oraz pogrupowa je
zgodnie z ich przeznaczeniem. Teraz pokaemy, jak ustanowi reakcj systemu na wybr elementu menu.

Odpowied na wybr elementw menu


Istnieje wiele sposobw okrelenia reakcji na kliknicie elementu menu w Androidzie. Mona
wykorzysta metod onOptionsItemSelected klasy aktywnoci, wprowadzi samodzielne
obiekty nasuchujce lub zastosowa intencje. W kolejnych podpunktach omwimy kad
z wymienionych metod.

Odpowied na kliknicie za pomoc metody onOptionsItemSelected


Po klikniciu elementu menu Android wywouje metod onOptionsItemSelected w klasie
Activity (listing 7.4).
Listing 7.4. Sygnatura oraz tre metody onOptionsItemSelected
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch(item.getItemId()) {
...
}

// dla elementw kliknitych


return true;

// dla pozostaych elementw


...return super.onOptionsItemSelected(item);
}

Podstaw dziaajcego tu algorytmu jest sprawdzenie identyfikatora elementu menu poprzez


metod getItemId() klasy MenuItem oraz wykonanie odpowiedniej czynnoci. Jeeli metoda
onOptionsItemSelected() przetwarza element menu, przekazuje warto true. Zdarzenie
z menu nie bdzie przenoszone dalej. Jeeli wywoa elementu menu nie obsuy metoda
onOptionsItemSelected(), wywouje ona metod nadrzdn poprzez super.onOptionsItem
Selected. Domylna implementacja metody onOptionsItemSelected() przekazuje warto
false, co powoduje zwyke przetwarzanie zdarzenia. Taka forma przetwarzania obejmuje alternatywne metody wywoania odpowiedzi na kliknicie elementu menu.

Odpowied na kliknicie za pomoc obiektw nasuchujcych


Odpowiedzi na kliknicie definiuje si przewanie za pomoc przesonicia metody onOption
jest to technika zalecana z powodu poprawienia wydajnoci. Jednak element
menu pozwala rwnie na zarejestrowanie obiektu nasuchujcego, zapewniajcego wywoania zwrotne.
-ItemSelcted;

Jest to proces dwuetapowy. Pierwszy etap polega na zaimplementowaniu interfejsu OnMenu


ClickListener. Nastpnie przekazuje si wystpienie tej implementacji do elementu menu.
Po klikniciu elementu zostanie wywoana metoda onMenuItemClick() interfejsu onMenuClick
Listener (listing 7.5).

252 Android 3. Tworzenie aplikacji


Listing 7.5. Zastosowanie obiektu nasuchujcego jako wywoania zwrotnego w przypadku
kliknicia elementu menu
// Etap 1
public class MyResponse implements OnMenuClickListener
{

// Jaka zmienna lokalna, na ktrej mona pracowa


//
// Jakie konstruktory
@override
boolean onMenuItemClick(MenuItem item)
{

// Wykonuje zadanie
return true;
}
}

// Etap 2
MyResponse myResponse = new MyResponse(...);
menuItem.setOnMenuItemClickListener(myResponse);
...

Metoda onMenuItemClick zostaje wywoana po wywietleniu elementu menu. Kod zostaje wykonany natychmiast po klikniciu elementu, jeszcze przed wywoaniem metody onOptions
ItemSelected. Jeeli metoda onMenuItemClick przekae warto true, nie zostan wykonane nastpne wywoania zwrotne w tym take metoda onOptionsItemSelected. Oznacza
to, e kod obiektu nasuchujcego ma pierwszestwo przed metod onOptionsItemSelected.

Wykorzystanie intencji do wywoania odpowiedzi


na kliknicie elementu menu
Istnieje take moliwo powizania elementu menu z intencj poprzez wykorzystanie metody
setIntent(intent) klasy MenuItem. Domylnie element menu nie jest powizany z adn intencj. Jednak jeli intencja jest powizana z takim elementem i nic innego go nie przetwarza, to
domylnym zachowaniem staje si przywoanie intencji za pomoc metody startActivity
(intent). Jeli ten sposb ma zadziaa, wszystkie procedury obsugujce zwaszcza metoda
onOptionsItemSelected powinny wywoywa nadrzdn klas metody onOptionsItem
Selected() dla elementw nieprzetwarzanych. Ewentualnie mona na ten sposb spojrze
nastpujco: system daje metodzie onOptionsItemSelected pierwszestwo w przetworzeniu
elementu listy menu (nie liczc oczywicie obiektu nasuchujcego). Zakadamy tutaj, e aden
obiekt nasuchujcy nie zosta bezporednio powizany z tym elementem menu, ale jeli jest
inaczej, obiekt nasuchujcy przesoni ca reszt.
Jeeli metoda onOptionsItemSelected nie zostanie przesonita, to podstawowa klasa szkieletu
Androida wykona czynnoci potrzebne do przywoania intencji wobec elementu menu. Jeeli
jednak metoda ta zostanie przesonita, ale nie interesuje nas dany element menu, naley wywoa metod nadrzdn, ktra z kolei zajmie si wywoaniem intencji. Podsumowujc: albo
nie naley przesania metody onOptionsItemSelected, albo naley j przesoni i przywoa
metod nadrzdn dla elementw menu, ktre nie s przetwarzane.

Rozdzia 7 Praca z menu

253

Utworzenie rodowiska testowego


do sprawdzania menu
Jak na razie wszystko jest proste i zrozumiae. Czytelnicy nauczyli si, w jaki sposb tworzy menu
oraz jak definiowa dla nich odpowiedzi za pomoc rnych rodzajw wywoa. Teraz pokaemy przykadow aktywno testujc interfejsy API, ktre do tej pory przedstawilimy.
Na kocu rozdziau zamiecilimy adres URL, z ktrego mona pobra odpowiedni
projekt, gotowy do zaimportowania w rodowisku Eclipse.

Celem tego wiczenia jest utworzenie prostej aktywnoci, w ktrej znajdzie si widok tekstowy.
Widok ten bdzie peni rol testera. Przy kadym menu bdziemy wypisywa nazw oraz identyfikator elementu menu w tym widoku tekstowym. Efekt kocowy bdzie wyglda tak jak
na rysunku 7.2.

Rysunek 7.2. Przykadowa aplikacja menu

Na rysunku 7.2 widoczne s dwa interesujce nas elementy: menu oraz widok tekstowy. Menu
pojawia si u spodu ekranu. Nie bdzie jednak widoczne po uruchomieniu aplikacji; konieczne
bdzie kliknicie przycisku Menu na emulatorze lub urzdzeniu, eby menu zostao wywietlone.
Drugim zajmujcym nas elementem jest widok tekstu na grze ekranu, w ktrym wywietlane s
wiadomoci dotyczce bdw. Podczas klikania elementw dostpnych w menu ich nazwy bd
wywietlane w widoku tekstowym. Po klikniciu elementu Wyczy program usunie zawarto
widoku tekstowego.
Na rysunku 7.2 nie jest przedstawiony pocztkowy stan aplikacji. Stanowi on ilustracj
omawianych typw menu w tym rozdziale.

254 Android 3. Tworzenie aplikacji


eby zaimplementowa rodowisko testowe, naley wykona nastpujce czynnoci:
1. Utwrz plik XML ukadu graficznego, w ktrym zostanie umieszczony widok tekstowy.
2. Utwrz klas Activity, ktra bdzie przechowywa ukad graficzny zdefiniowany
w punkcie 1.
3. Skonfiguruj menu.
4. Dodaj standardowe elementy do menu.
5. Dodaj elementy drugorzdne do menu.
6. Utwrz odpowiedzi na kliknicie dla tych elementw.
7. Zmodyfikuj plik AndroidManifest.xml w taki sposb, eby bya wywietlana
waciwa nazwa aplikacji.
Kady z tych etapw zostanie teraz szczegowo omwiony. Zaprezentujemy rwnie kod potrzebny do utworzenia rodowiska testowego.

Utworzenie ukadu graficznego w pliku XML


Pierwszy etap polega na utworzeniu prostego pliku XML ukadu graficznego, zawierajcego widok
tekstowy (listing 7.6). Plik ten moe by wczytywany do aktywnoci podczas jej uruchamiania.
Listing 7.6. Plik XML ukadu graficznego, zastosowany w rodowisku testowym
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/textViewId"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Notatnik testowy"
/>
</LinearLayout>

Utworzenie aktywnoci
W drugim etapie tworzymy aktywno, co jest rwnie cakiem proste. Zaoywszy, e plik ukadu graficznego z pierwszego etapu jest dostpny w katalogu /res/layout/main.xml, mona
skorzysta z jego identyfikatora zasobw do zapenienia widokw aktywnoci (listing 7.7).
Listing 7.7. Klasa aktywnoci menu w rodowisku testowym
public class SampleMenusActivity extends Activity {

// Naley to zainicjalizowa w metodzie onCreateOptions


Menu myMenu = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Rozdzia 7 Praca z menu

255

setContentView(R.layout.main);
}
}

W celu zachowania przejrzystoci nie zawarlimy tutaj instrukcji importu. W rodowisku


Eclipse mona to zrobi automatycznie poprzez wybranie opcji Source/Organize Imports w menu
kontekstowym edytora. Rwnie skuteczny okazuje si skrt Ctrl+Shift+O.

Konfiguracja menu
Gdy ju mamy widok oraz aktywno, moemy przej do trzeciego etapu: przesonicia metody
onCreateOptionsMenu i skonfigurowania menu za pomoc kodu (listing 7.8).
Listing 7.8. Konfigurowanie menu za pomoc kodu
@Override
public boolean onCreateOptionsMenu(Menu menu)
{

// naley wywoa metod nadrzdn w celu doczenia menu systemowych


super.onCreateOptionsMenu(menu);
this.myMenu = menu;

// dodaje kilka standardowych menu


addRegularMenuItems(menu);

// dodaje kilka drugorzdnych menu


add5SecondaryMenuItems(menu);

// W celu uwidocznienia menu musi zosta zwrcona warto true


// Przy wartoci false menu nie bdzie widoczne
return true;
}

Kod z listingu 7.8 wywouje najpierw nadrzdn metod onCreateOptionsMenu, dziki czemu
uzyskuje ona moliwo dodania systemowych menu.
W dotychczasowych wersjach rodowiska Android SDK metoda onCreateOptionsMenu
nie dodaje nowych elementw menu. Jednak w kolejnych edycjach moe si to zmieni,
wic warto wywoywa metod nadrzdn.

Programujemy nastpnie obiekt Menu, gdy bdzie on pniej modyfikowany w celach demonstracyjnych. Nastpnym etapem jest dodanie kilku standardowych elementw menu oraz kilku
elementw drugorzdnych.

Dodawanie elementw menu standardowego


Nadszed czas na etap czwarty: dodanie kilku standardowych elementw do menu. Kod funkcji
addRegularMenuItems przedstawiono na listingu 7.9.

256 Android 3. Tworzenie aplikacji


Listing 7.9. Funkcja addRegularMenuItems
private void addRegularMenuItems(Menu menu)
{
int base=Menu.FIRST; // warto wynosi 1
menu.add(base,base,base,"Dodaj");
menu.add(base,base+1,base+1,"element2");
menu.add(base,base+2,base+2,"Wyczy");
menu.add(base,base+3,base+3,"ukryj drugorzdny");
menu.add(base,base+4,base+4,"poka drugorzdny");
menu.add(base,base+5,base+5,"wcz drugorzdny");
menu.add(base,base+6,base+6,"wycz drugorzdny");
menu.add(base,base+7,base+7,"zaznacz drugorzdny");
menu.add(base,base+8,base+8,"odznacz drugorzdny");
}

Klasa Menu definiuje kilka przydatnych staych, wrd nich sta Menu.FIRST. Mona j wykorzysta jako podstaw numeracji identyfikatorw menu oraz innych sekwencji liczbowych zwizanych z menu. Zauwamy, w jaki sposb moemy powiza identyfikator grupy z wartoci base
i zwiksza jedynie wartoci identyfikatorw kolejnoci oraz identyfikatorw poszczeglnych
elementw. Dodatkowo w kodzie w celach demonstracyjnych umieszczono kilka niestandardowych elementw menu, takich jak ukryj drugorzdny, wcz drugorzdny i par innych.

Dodawanie elementw menu drugorzdnego


Dodajmy teraz kilka elementw menu drugorzdnego, aby wykona pity etap (listing 7.10).
Jak wczeniej wspomnielimy, kolejno elementw menu drugorzdnego rozpoczyna si od
wartoci 0x30000 i jest zdefiniowana przez sta Menu.CATEGORY_SECONDARY. Poniewa warto
ta jest wysza od analogicznej wartoci elementw menu standardowego, elementy te bd si
znajdoway poniej elementw standardowych w menu. Zauwamy, e jedynie kolejno rozmieszczenia odrnia elementy standardowe od drugorzdnych. We wszystkich innych aspektach
elementy te niczym si od siebie nie rni.
Listing 7.10. Dodawanie elementw menu drugorzdnego
private void add5SecondaryMenuItems(Menu menu)
{

// Elementy drugorzdne s wywietlane tak samo jak inne elementy


int base=Menu.CATEGORY_SECONDARY;
menu.add(base,base+1,base+1,"drugorz.
menu.add(base,base+2,base+2,"drugorz.
menu.add(base,base+3,base+3,"drugorz.
menu.add(base,base+3,base+3,"drugorz.
menu.add(base,base+4,base+4,"drugorz.
}

element
element
element
element
element

1");
2");
3");
4");
5");

Rozdzia 7 Praca z menu

257

Odpowied na kliknicie elementu menu


Po skonfigurowaniu menu przechodzimy do szstego etapu: przypisywania odpowiedzi na
kliknicie. Po klikniciu elementu Android wywouje metod onOptionsItemSelected klasy
Activity poprzez przesanie odniesienia do tego kliknitego elementu. Nastpnie metoda
getItemId() klasy MenuItem rozpoznaje wybrany element.
Nierzadko stosuje si instrukcje switch lub kombinacje if oraz else, ktrych celem jest wywoanie rnych funkcji w odpowiedzi na kliknicie elementu. Listing 7.11 przedstawia standardowy
proces odpowiadania na kliknicie za pomoc wywoywanej metody onOptionsItemSelected
(nieco lepszy sposb wykonania tej samej czynnoci zosta zaprezentowany w podrozdziale
Wczytywanie menu poprzez pliki XML, gdzie bd stosowane symboliczne nazwy dla identyfikatorw elementw menu).
Listing 7.11. Odpowied na kliknicie elementu menu
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == 1) {
appendText("\nwitaj");
}
else if (item.getItemId() == 2) {
appendText("\nelement2");
}
else if (item.getItemId() == 3) {
emptyText();
}
else if (item.getItemId() == 4) {

// ukryj drugorzdny
this.appendMenuItemText(item);
this.myMenu.setGroupVisible(Menu.CATEGORY_SECONDARY,false);

}
else if (item.getItemId() == 5) {

// poka drugorzdny
this.appendMenuItemText(item);
this.myMenu.setGroupVisible(Menu.CATEGORY_SECONDARY,true);

}
else if (item.getItemId() == 6) {

// wcz drugorzdny
this.appendMenuItemText(item);
this.myMenu.setGroupEnabled(Menu.CATEGORY_SECONDARY,true);

}
else if (item.getItemId() == 7) {

// wycz drugorzdny
this.appendMenuItemText(item);
this.myMenu.setGroupEnabled(Menu.CATEGORY_SECONDARY,false);

}
else if (item.getItemId() == 8) {

// zaznacz drugorzdny
this.appendMenuItemText(item);
myMenu.setGroupCheckable(Menu.CATEGORY_SECONDARY,true,false);

}
else if (item.getItemId() == 9) {

// usu zaznaczenie drugorzdnego


this.appendMenuItemText(item);
myMenu.setGroupCheckable(Menu.CATEGORY_SECONDARY,false,false);

258 Android 3. Tworzenie aplikacji


}
else {
this.appendMenuItemText(item);
}

// powinien zwrci warto true, jeli element jest przetwarzany


return true;
}

Kod przedstawiony na listingu 7.11 prezentuje rwnie przeprowadzanie operacji na poziomie


grupy menu; wywoania tych metod zostay zaznaczone pogrubieniem. Szczegy kliknitego
elementu s wywietlane w widoku TextView. Na listingu 7.12 zostay wypisane funkcje pozwalajce umieszcza te informacje w kontrolce TextView. Zwrmy uwag na dodatkow
metod klasy MenuItem, pozwalajc wywietli tytu elementu.
Listing 7.12. Funkcje umoliwiajce wypisywanie danych w kontrolce testowej TextView
// Dany cig znakw tekstowych dodany do kontrolki TextView
private void appendText(String text) {
TextView tv = (TextView)this.findViewById(R.id.textViewId);
tv.setText(tv.getText() + text);
}

// Dany element menu wywietla swj tytu w kontrolce TextView


private void appendMenuItemText(MenuItem menuItem) {
String title = menuItem.getTitle().toString();
TextView tv = (TextView)this.findViewById(R.id.textViewId);
tv.setText(tv.getText() + "\n" + title);
}

// Wyczyszczenie zawartoci kontrolki TextView


private void emptyText() {
TextView tv = (TextView)this.findViewById(R.id.textViewId);
tv.setText("");
}

Modyfikowanie pliku AndroidManifest.xml


Ostatnim etapem tworzenia rodowiska testowego jest aktualizacja pliku AndroidManifest.xml.
Ten generowany automatycznie podczas tworzenia nowego projektu plik jest umieszczony
w katalogu gwnym projektu.
Wewntrz tego pliku jest przeprowadzany proces rejestrowania klasy Activity (na przykad
SampleMenusActivity) oraz definiowany tytu aktywnoci. Jak wida na rysunku 7.2, nasza
aktywno nosi nazw Przykadowa aplikacja menu. Wiersz odpowiedzialny za tytu zosta
na listingu 7.13 pogrubiony.
Listing 7.13. Plik AndroidManifest.xml przygotowany do testowania
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your-package-name-goes-here "
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="Przykadowe menu">

Rozdzia 7 Praca z menu

259

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

Za pomoc umieszczonych w tym podrozdziale fragmentw kodu mona w szybki sposb


utworzy rodowisko testowe pozwalajce na sprawdzenie moliwoci menu. Pokazalimy, jak
utworzy prost aktywno uruchamian wraz z widokiem tekstowym, a take w jaki sposb
utworzy menu i przypisa jego elementom reakcj na kliknicie. Wikszo menu jest zaprojektowana na podstawie tego prostego, lecz funkcjonalnego wzorca. Rysunek 7.2 pokazuje,
jakiego rodzaju interfejsu UI naley si spodziewa po zakoczeniu wiczenia. Przypominamy
jednak, e efekt kocowy moe si rni od przedstawionego na rysunku (najczciej jednak
nie bdzie si rni), poniewa nie zademonstrowalimy sposobu, w jaki naley dodawa ikony
do menu. Nawet po wstawieniu ikon interfejs rodowiska testowego moe si nieznacznie rni
od naszej wersji, poniewa Czytelnik moe zastosowa inne obrazy.

Praca z innymi rodzajami menu


Do tej pory zajmowalimy si prostszymi, chocia funkcjonalnymi rodzajami menu. W trakcie
korzystania ze rodowiska SDK mona zauway, e Android obsuguje rwnie menu w formie
ikon, podmenu, menu kontekstowe oraz menu alternatywne. Ten ostatni typ wystpuje wycznie
w Androidzie. W kolejnych punktach zostan przedstawione niestandardowe rodzaje menu.

Rozszerzone menu
Na rysunku 7.2 w prawym dolnym rogu ekranu widnieje element menu zatytuowany Wicej.
W adnym fragmencie kodu nie umiecilimy tego elementu, zatem skd on si tu wzi?
Jeeli aplikacja posiada wicej elementw menu, ni moe zaprezentowa na ekranie, Android
wywietla w menu element Wicej, dziki ktremu uytkownik moe przej do pozostaych
elementw. Takie rozszerzone menu pojawia si automatycznie, gdy trzeba wywietli zbyt du
liczb elementw na maej przestrzeni. Rozszerzone menu maj jednak ograniczenie: nie mog
przetwarza ikon. Po klikniciu przycisku Wicej zostanie wywietlone menu bez ikon.

Praca z menu w postaci ikon


Skoro ju wspomnielimy o menu zawierajcych ikony, przyjrzyjmy si im uwaniej. W repertuarze menu obsugiwany jest nie tylko tekst, lecz rwnie obrazy lub ikony. Do reprezentowania elementw menu mona uywa samych ikon, jak i ikon wraz z tekstem. Podczas stosowania ikon pojawia si jednak kilka ogranicze. Po pierwsze, nie ma moliwoci stosowania ikon
w rozszerzonych menu, jak ju zostao wspomniane w poprzednim punkcie. Po drugie, elementy menu zawierajce ikony nie obsuguj funkcji ich zaznaczania. Po trzecie, jeeli tekst
w elemencie menu jest za dugi, zostanie obcity o pewn liczb znakw w zalenoci od rozmiaru wywietlacza (ograniczenie to dotyczy take elementw menu zawierajcych sam tekst).

260 Android 3. Tworzenie aplikacji


Utworzenie elementu menu w formie ikony jest bardzo proste. Najpierw buduje si standardowy
element menu, a nastpnie w celu wybrania obrazu stosuje si metod setIcon w klasie MenuItem.
Konieczne jest wprowadzenie identyfikatora zasobu obrazu, trzeba wic umieci obraz lub ikon
w katalogu /res/drawable. Jeli na przykad plik nosi nazw balony, jego identyfikator bdzie
nastpujcy: R.drawable.balony.
Poniej umieszczono przykad:
// Dodaje element menu i zapamituje go, eby nastpnie mc doda do niego ikon.
MenuItem item8 = menu.add(base,base+8,base+8,"Odznacz drugorzdny");
item8.setIcon(R.drawable.balony);

Podczas dodawania elementw do menu rzadko kiedy trzeba pilnowa, eby lokalna zmienna bya
przekazywana przez metod menu.add. Jednak w tym przypadku otrzymany obiekt musi by zapamitany w celu dodania ikony do obiektu menu. Z powyszego przykadu wida take, e
typem zwracanym przez metod menu.add jest MenuItem.
Ikona bdzie widoczna tak dugo, jak dugo wywietlany bdzie obiekt menu w gwnym oknie
aplikacji. Jeeli obiekt ten bdzie wywietlany w rozszerzonym menu, ikona zostanie pominita
i widoczny stanie si sam tekst. Pokazany na rysunku 7.2 element menu, wywietlajcy ikon
przedstawiajc balony, jest przykadem omwionego rodzaju obiektu.

Praca z podmenu
Przyjrzyjmy si teraz podmenu w Androidzie. Na rysunku 7.1 zostay naszkicowane zwizki
strukturalne pomidzy obiektem klasy SubMenu a obiektami klas Menu i MenuItem. W obiekcie
Menu moe si znajdowa wiele obiektw klasy SubMenu. Kady obiekt SubMenu jest dodawany
do obiektu Menu poprzez wywoanie metody Menu.addSubMenu (listing 7.14). Elementy s
dodawane do listy podmenu tak samo jak w przypadku zwykego menu. Wynika to z faktu,
e obiekt SubMenu wywodzi si z obiektu klasy Menu. Nie mona jednak umieszcza podmenu w obiekcie SubMenu.
Listing 7.14. Dodawanie podmenu
private void addSubMenu(Menu menu)
{

// Elementy drugorzdne s pokazywane tak jak pozostae obiekty


int base=Menu.FIRST + 100;
SubMenu sm = menu.addSubMenu(base,base+1,Menu.NONE,"podmenu");
sm.add(base,base+2,base+2,"podelement1");
sm.add(base,base+3,base+3, "podelement2");
sm.add(base,base+4,base+4, "podelement3");

// Ikony elementw podmenu nie s obsugiwane


item1.setIcon(R.drawable.icon48x48_2);

// Ten poniej jest poprawny, jednak


sm.setIcon(R.drawable.icon48x48_1);

// spowoduje wywietlenie wyjtku wykonawczego


//sm.addSubMenu("sprbuj tego");
}

Rozdzia 7 Praca z menu

261

Poniewa obiekt SubMenu jest podklas obiektu Menu, obsuguje metod addSubMenu.
Kompilator nie wywietli bdu przy prbie doczenia podmenu do innego podmenu,
pojawi si jednak wyjtek wykonawczy.

W dokumentacji zestawu Android SDK widnieje take informacja, e podmenu nie obsuguj
ikon elementw menu. Gdy do elementu menu zostanie dodana ikona, a nastpnie zostanie on
przeniesiony do podmenu, ikona ta zostanie zignorowana, nawet jeli nie pojawi si aden bd
kompilacji lub bd wykonawczy. Jednak samo podmenu moe posiada ikon.

Zabezpieczanie menu systemowych


Wikszo aplikacji systemu Windows posiada takie menu, jak Plik, Edycja, Widok, Otwrz,
Zamknij oraz Wyjcie. S to tak zwane menu systemowe. Zestaw Android SDK sugeruje wstawianie podobnego zestawu menu do kadego menu opcji. Jednak adna z aktualnych wersji
rodowiska Android SDK nie docza tych menu w procesie tworzenia menu. Mona sobie
wyobrazi, e systemowe menu zostan zaimplementowane w przyszych wersjach rodowiska
programistycznego. Wedug dokumentacji programici powinni w odpowiedni sposb przygotowywa kod, eby mona byo wstawi menu systemowe, gdy bd dostpne. W tym celu
naley wywoa metod onCreateOptionsMenu w klasie nadrzdnej, dziki czemu moliwe stanie
si dodawanie menu systemowych do grupy definiowanej przez sta CATEGORY_SYSTEM.

Praca z menu kontekstowymi


Uytkownicy aplikacji biurowych z pewnoci natknli si na menu kontekstowe. Na przykad
w programach systemu Windows dostp do takiego menu uzyskuje si poprzez kliknicie
prawym przyciskiem dowolnego elementu interfejsu uytkownika. W Androidzie zosta zaadaptowany ten sam pomys, korzystajcy z dziaania zwanego dugim klikniciem. W technice tej przycisk urzdzenia wskazujcego jest przytrzymywany nieco duej ni w przypadku
zwykego kliknicia.
W takich handheldach jak telefony komrkowe kliknicia myszy zostay zaimplementowane na
najrniejsze sposoby, w zalenoci od rodzaju sterowania. Jeeli telefon jest zaopatrzony
w kko sterujce kursorem, jego wcinicie jest odpowiednikiem kliknicia myszy. W urzdzeniach posiadajcych panel dotykowy jego nacinicie lub stuknicie peni rol kliknicia.
Natomiast w przypadku urzdze posiadajcych klawisze sterujce kursora oraz przycisk wykonania akcji nacinicie tego przycisku jest rwnoznaczne klikniciu przyciskiem myszy.
Niezalenie od sposobu zaimplementowania myszy w urzdzeniu, dusze przytrzymanie
elementu odpowiedzialnego za kliknicie spowoduje wykonanie dugiego kliknicia.
Pod wzgldem struktury menu kontekstowe rni si od omawianego wczeniej standardowego
menu opcji (rysunek 7.3). Menu kontekstowe charakteryzuj pewne niuanse, ktrych brak
w menu standardowym.
Na rysunku 7.3 wida, e menu kontekstowe reprezentowane jest przez klas ContextMenu
w architekturze menu Androida. Podobnie jak klasa Menu, tak i ContextMenu moe obejmowa
wiele elementw menu. Aby doda elementy do menu kontekstowego, wykorzystuje si te
same metody klasy Menu. Najwiksz rnic pomidzy klasami Menu a ContextMenu jest
rodzaj obiektu bdcego wacicielem danego rodzaju menu. Standardowe menu przynaley do
aktywnoci, podczas gdy menu kontekstowe do widoku. Naleao si tego spodziewa, poniewa dugie kliknicia, uruchamiajce menu kontekstowe, s stosowane wobec kliknitego

262 Android 3. Tworzenie aplikacji

Rysunek 7.3. Aktywnoci, widoki i menu kontekstowe

widoku. Zatem aktywno moe zawiera tylko jedno menu opcji, ale wiele menu kontekstowych. Poniewa aktywno moe obejmowa wiele widokw, a kady z nich moe posiada
wasne menu kontekstowe, maksymalna liczba menu kontekstowych w aktywnoci jest
rwna liczbie zawartych w niej widokw.
Chocia wacicielem menu kontekstowego jest widok, metoda potrzebna do zapenienia takiego
menu znajduje si w klasie Activity. Nosi ona nazw activity.onCreateContextMenu()
i z dziaania przypomina metod activity.onCreateOptionsMenu(). Ta wywoywana metoda
przenosi ze sob rwnie widok, w ktrym menu kontekstowe bdzie zapenione.
Istnieje jeszcze jeden godny uwagi problem z menu kontekstowymi. Chocia metoda onCreate
OptionsMenu() jest automatycznie wywoywana dla kadej aktywnoci, nie dotyczy to metody
onCreateContextMenu(). Widok w aktywnoci nie musi posiada menu kontekstowego. Na
przykad mog by obecne trzy widoki w aktywnoci, lecz moe istnie potrzeba wczenia
menu kontekstowego tylko dla jednego z nich. Jeeli dany widok ma posiada menu kontekstowe,
musi on zosta zarejestrowany wraz z aktywnoci do penienia roli waciciela tego menu. Dokonuje si tego poprzez metod activity.registerForContextMenu(view), omwion
w podpunkcie Rejestrowanie widoku dla menu kontekstowego.
Zwrmy teraz uwag na przedstawion na rysunku 7.3 klas ContextMenuInfo. Obiekt tego typu
jest przekazywany do metody onCreateContextMenu. Jest to jeden ze sposobw przekazywania
przez widok dodatkowych informacji do tej metody. eby widok mg tego dokona, musi
przesoni metod getContextViewInfo() i zwrci pochodn klas ContextMenuInfo wraz

Rozdzia 7 Praca z menu

263

z danymi reprezentujcymi dodatkowe informacje. eby w peni zrozumie t interakcj,


mona zajrze do kodu rdowego klasy android.view.View.
Zgodnie z dokumentacj rodowiska Android SDK menu kontekstowe nie obsuguj
skrtw, ikon ani podmenu.

Skoro znamy ju ogln struktur menu kontekstowych, przyjrzyjmy si przykadowemu kodowi pokazujcemu, w jaki sposb krok po kroku zaimplementowa menu kontekstowe:
1. Zarejestruj widok dla danego menu kontekstowego w metodzie onCreate() aktywnoci.
2. Zapenij menu kontekstowe za pomoc metody onCreateContextMenu(). Musisz
dokoczy pierwszy etap, zanim ta metoda zostanie wywoana przez system Android.
3. Zdefiniuj odpowiedzi na kliknicia poszczeglnych elementw menu kontekstowego.

Rejestrowanie widoku dla menu kontekstowego


Pierwszym etapem w implementacji menu kontekstowego jest zarejestrowanie widoku dla tego
menu w metodzie onCreate() aktywnoci. Po utworzeniu rodowiska testowego, omwionego
we wczeniejszej czci rozdziau, mona zarejestrowa widok TextView dla menu kontekstowego w tym rodowisku za pomoc kodu widocznego na listingu 7.15. Najpierw naley
znale widok TextView, a nastpnie wywoa w aktywnoci metod registerForContextMenu,
jako argument wstawiajc ten widok TextView. W ten sposb widok zostanie skonfigurowany
dla menu kontekstowych.
Listing 7.15. Rejestrowanie widoku TextView dla menu kontekstowego
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView tv = (TextView)this.findViewById(R.id.textViewId);
registerForContextMenu(tv);
}

Zapenianie menu kontekstowego


Po zarejestrowaniu przykadowego widoku TextView dla menu kontekstowych Android wywoa metod onCreateContextMenu(), w ktrej argumentem bdzie ten widok. To wanie tutaj
mona zapeni menu kontekstowe odpowiednimi elementami. Dziki wywoanej metodzie
onCreateContextMenu() dostpne staj si trzy potrzebne argumenty.
Pierwszym argumentem jest domylnie utworzona klasa ContextMenu, drugi argument to widok
(na przykad TextView), ktry wygenerowa wywoywanie zwrotne, a trzeci argument stanowi
klasa ContextMenuInfo, ktr pokrtce omwilimy podczas analizowania rysunku 7.3. W wielu
prostych przypadkach mona po prostu zignorowa trzeci argument, jednak niektre widoki
mog przenosi dziki niemu dodatkowe informacje. W takich przypadkach trzeba bdzie
umieci klas ContextMenuInfo w podklasie, a nastpnie zastosowa dodatkowe metody,
umoliwiajce odczyt danych.

264 Android 3. Tworzenie aplikacji


Przykadami klas wywodzcych si z klasy ContextMenuInfo s AdapterContextMenuInfo oraz
ExpandableContextMenuInfo. Widoki, ktre s powizane w Androidzie z bazodanowymi
kursorami, wykorzystuj klas AdapterContextMenuInfo do przekazywania identyfikatora
krotki do widoku, w ktrym menu kontekstowe bdzie wywietlane. W pewnym sensie mona
stosowa t klas do dalszego zwikszania przejrzystoci obiektu kryjcego si pod klikniciem
myszy, nawet w danym widoku.
Na listingu 7.16 zostaa zaprezentowana metoda onCreateContextMenu().
Listing 7.16. Metoda onCreateContextMenu()
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
{
menu.setHeaderTitle("Przykadowe menu kontekstowe");
menu.add(200, 200, 200, "element1");
}

Tworzenie odpowiedzi na kliknicie elementu menu kontekstowego


Trzecim etapem implementacji menu kontekstowego jest zdefiniowanie odpowiedzi na kliknicie jego elementw. Mechanizm tworzenia odpowiedzi w menu kontekstowym jest analogiczny do przeprowadzania tej czynnoci w menu opcji. Dostpna jest wywoywana metoda
podobna do onOptionsItemSelected(), nazwana onContextItemSelected(). Obydwie s
dostpne w klasie Activity. Listing 7.17 przedstawia zastosowanie metody onContextItem
Selected().
Listing 7.17. Tworzenie odpowiedzi dla menu kontekstowego
@Override
public boolean onContextItemSelected(MenuItem item)
{
if (item.getitemId() = jakis-identyfikator-elementu-menu)
{

// przetwarza ten element menu


return true;
}
... inne przetwarzanie wyjtku
}

Praca z menu alternatywnymi


Do tej pory nauczylimy si tworzy i obsugiwa menu, podmenu oraz menu kontekstowe.
W Androidzie zaprezentowano now koncepcj menu alternatywne pozwalajc elementom takiego menu stanowi cz standardowych menu, podmenu oraz menu kontekstowych. Menu alternatywne pozwalaj wielu aplikacjom w Androidzie na wzajemne uytkowanie. Takie menu alternatywne stanowi cz midzyaplikacyjnego systemu komunikacyjnego
lub struktury uytkowania.

Rozdzia 7 Praca z menu

265

W szczeglnoci menu alternatywne pozwalaj na zamieszczanie menu jednej aplikacji wewntrz drugiej. Po wybraniu menu alternatywnego docelowa aplikacja lub aktywno uruchamia
si za porednictwem adresu URL w celu przekazania danych wymaganych przez dajc
aktywno. Nastpnie wywoana aktywno wykorzysta adres URL danych, ktre zostay przekazane za pomoc intencji. eby dobrze poj dziaanie menu alternatywnych, trzeba najpierw
zrozumie pojcia dostawcw treci, identyfikatorw URI treci, typw MIME treci oraz
intencji (rozdziay 4. i 5.).
Oglna zasada dziaania jest nastpujca: wyobramy sobie, e tworzymy ekran, ktry ma za
zadanie wywietla dane. Najprawdopodobniej ekran ten bdzie aktywnoci. W tej aktywnoci
bdzie dostpne menu opcji pozwalajcych na rne sposoby modyfikowania tych danych.
Zamy take na chwil, e pracujemy nad dokumentem lub notatk, definiowan przez
identyfikator URI oraz odpowiadajcy mu typ MIME. Jako programici chcemy, eby urzdzenie
posiadao wicej aplikacji umoliwiajcych edytowanie lub wywietlanie tych danych. Chcemy
sprawi, eby menu tych programw byy wywietlane jako cz menu tworzonego dla naszej
aktywnoci.
eby przyczy elementy menu alternatywnego do naszego menu, naley wykona nastpujce czynnoci podczas konfigurowania menu w metodzie onCreateOptionsMenu:
1. Utwrz intencj, ktrej identyfikator URI danych jest ustanowiony dla pokazywanego
w biecym momencie identyfikatora URI danych.
2. Przydziel t intencj do kategorii CATEGORY_ALTERNATIVE.
3. Wyszukaj aktywnoci, ktre pozwalaj na obsug typu danych definiowanych przez
identyfikator URI.
4. Intencje wywoujce te aktywnoci dodaj jako elementy menu.
Wymienione etapy mwi nam wiele na temat natury aplikacji w Androidzie, wic zastanowimy
si nad kadym z nich. Jak ju wiemy, przyczanie elementw menu alternatywnego do standardowego menu jest przeprowadzane w metodzie onCreateOptionsMenu:
@Override public boolean onCreateOptionsMenu(Menu menu)
{
}

Zastanwmy si, jaki kod tworzy t funkcj. Najpierw musimy pozna identyfikator URI danych, na ktrych zamierzamy pracowa w danej aktywnoci. Mona go uzyska w nastpujcy
sposb:
this.getIntent().getData()

Technika ta dziaa, poniewa klasa Activity posiada metod getIntent(), przekazujc identyfikator URI danych, dla ktrych zostaa przywoana aktywno. Tak aktywnoci moe
by gwna aktywno wywoana przez menu gwne; w takim przypadku moe nie posiada
intencji i metoda getIntent() zwrci warto null. Piszc kod, naley zabezpieczy si przed
podobn sytuacj.
Teraz naszym celem jest odnalezienie innych programw, ktre potrafi pracowa z tego rodzaju danymi. Wyszukiwanie przeprowadzamy, wstawiajc intencj w miejsce argumentu.
Poniej przedstawiamy kod pozwalajcy na skonstruowanie odpowiedniej intencji:
Intent criteriaIntent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);

266 Android 3. Tworzenie aplikacji


Po skonstruowaniu intencji dodamy take kategori interesujcych nas dziaa. Gwoli cisoci,
interesuj nas jedynie te aktywnoci, ktr mog by wywoane jako cz menu alternatywnego.
Jestemy ju gotowi do zaprogramowania obiektu Menu, aby wyszuka pasujce aktywnoci i doda
je jako opcje w menu (listing 7.18).
Listing 7.18. Zapenianie menu elementami menu alternatywnego
// Wyszukuje pasujce aktywnoci i wypenia nimi menu.
menu.addIntentOptions(
Menu.CATEGORY_ALTERNATIVE,

// Grupa
// Unikatowe identyfikatory, ktre chcemy doczy.
Menu.CATEGORY_ALTERNATIVE, // Kolejno
getComponentName(),
// Nazwa klasy wywietlajcej menu Name
// --tutaj, to jest ta klasa.
null,
// Bez szczegw.
criteriaIntent,
// Uprzednio utworzona intencja, ktra
// opisuje nasze wymagania.
0,
// Bez flag.
null);
// Zwracane elementy menu
Menu.CATEGORY_ALTERNATIVE,

Zanim omwimy kad linijk kodu, wyjanimy, co rozumiemy pod pojciem pasujce
aktywnoci. Pasujca aktywno to taka aktywno, ktra potrafi przetworzy przekazany jej
identyfikator URI. Informacje dotyczce obsugiwanych identyfikatorw URI s zazwyczaj
rejestrowane w plikach manifestach aktywnoci za pomoc identyfikatorw URI, dziaa i kategorii. W Androidzie znajduje si mechanizm pozwalajcy uywa obiektu Intent do wyszukiwania pasujcych aktywnoci, jeli ma si dane te atrybuty.
Przyjrzyjmy si teraz uwaniej listingowi 7.18. Metoda addIntentOptions w klasie Menu
wyszukuje aktywnoci pasujce do identyfikatora URI intencji oraz atrybutw kategorii. Nastpnie metoda dodaje te aktywnoci do waciwej grupy w menu, korzystajc z identyfikatorw
elementw menu oraz identyfikatorw kolejnoci. Tym aspektem dziaania metody zajmuj si
trzy pierwsze atrybuty. Na listingu 7.18 grup, od ktrej zaczniemy dodawanie nowych elementw menu, jest Menu.CATEGORY_ALTERNATIVE. Ta sama staa jest uywana jako warto
bazowa dla identyfikatorw elementw oraz kolejnoci.
Kolejny argument wskazuje na w peni kwalifikowan nazw skadnika aktywnoci, ktrej
czci jest nasze menu. W kodzie jest zastosowana pomocnicza metoda getComponentName(),
wywodzca si z klasy Activity. Nazwa skadnika (ang. component name) stanowi po prostu nazw pakietu oraz klasy i jest ona wymagana, poniewa za kadym razem, gdy jest dodawany nowy element menu, element ten bdzie musia wywoywa docelow aktywno.
eby tego dokona, system wymaga rdowej aktywnoci, ktra uruchomia aktywno
docelow. Nastpnym argumentem jest tablica intencji, ktra powinna by stosowana jako filtr
wobec zwracanych intencji. W naszym przykadzie wprowadzilimy warto null.
Kolejny argument wskazuje obiekt criteriaIntent, ktry dopiero co skonstruowalimy.
Jest to kryterium wyszukiwania, ktrego chcemy uy. Nastpujcy po nim argument jest
flag typu Menu.FLAG_APPEND_TO_GROUP wskazujc na to, czy elementy menu maj by
dodawane do istniejcej grupy menu, czy te maj by podmieniane. Domylna warto wynosi
0, co wskazuje na to, e elementy grupy menu maj by podmieniane.

Rozdzia 7 Praca z menu

267

Ostatnim argumentem na listingu 7.18 jest tablica dodanych elementw menu. Mona korzysta
z takiego odniesienia do dodanych elementw, w przypadku gdy trzeba je w jaki sposb zmodyfikowa ju po ich dodaniu.
Wszystko brzmi prosto i piknie. Pozostaje jednak kilka pyta bez odpowiedzi. Na przykad: jakie bd nazwy dodanych elementw? Dokumentacja Androida jest w tym temacie wyjtkowo
uboga, zatem poszperalimy troch w kodzie rdowym, eby dowiedzie si, jak ta funkcja
w rzeczywistoci dziaa poza wzrokiem uytkownika (w rozdziale 1. opisalimy, w jaki sposb
uzyska dostp do kodu rdowego Androida).
Okazuje si, e klasa Menu jest jedynie interfejsem, wic nie widzielimy jej kodu rdowego.
Klas implementujc interfejs Menu jest MenuBuilder. Na listingu 7.19 umiecilimy kod podobnej metody, addIntentOptions, z klasy MenuBuilder (wstawilimy ten kod wycznie
w celach pogldowych; nie bdziemy go szczegowo objania).
Listing 7.19. Metoda MenuBuilder.addIntentOptions
public int addIntentOptions(int group, int id, int categoryOrder,
ComponentName caller,
Intent[] specifics,
Intent intent, int flags,
MenuItem[] outSpecificItems)
{
PackageManager pm = mContext.getPackageManager();
final List<ResolveInfo> lri =
pm.queryIntentActivityOptions(caller, specifics, intent, 0);
final int N = lri != null ? lri.size() : 0;
if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
removeGroup(group);
}
for (int i=0; i<N; i++) {
final ResolveInfo ri = lri.get(i);
Intent rintent = new Intent(
ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
rintent.setComponent(new ComponentName(
ri.activityInfo.applicationInfo.packageName,
ri.activityInfo.name));
final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm));
item.setIntent(rintent);
if (outSpecificItems != null && ri.specificIndex >= 0) {
outSpecificItems[ri.specificIndex] = item;
}
}
return N;
}

Zauwamy, e jeden wiersz na listingu 7.19 zosta pogrubiony; ta cz kodu jest odpowiedzialna
za konstruowanie elementu menu. Zadanie okrelenia tytuu menu zostaje przekazane klasie
ResolveInfo. W kodzie rdowym tej klasy wida, e filtr deklarujcy t intencj powinien
posiada powizany z ni tytu. Poniej znajduje si przykad:

268 Android 3. Tworzenie aplikacji


<intent-filter android:label="Tytu menu ">
...
<category android:name="android.intent.category.ALTERNATE" />
<data android:mimeType="jaki typ danych" />
</intent-filter>

Warto label filtru intencji staje si nazw menu. Mona sprawdzi zachowanie tej klasy na
podstawie przykadowej aplikacji Notepad.

Praca z menu w odpowiedzi na zmian danych


Do tej pory zajmowalimy si menu statycznymi; zostaj one skonfigurowane na pocztku i nie
zmieniaj si dynamicznie w zalenoci od zawartoci ekranu. eby utworzy menu dynamiczne,
naley zastosowa dostpn w Androidzie metod onPrepareOptionsMenu. Przypomina ona
metod onCreateOptionsMenu z wyjtkiem faktu, e jest wywoywana za kadym razem, gdy
zostaje przywoane menu. Jest ona stosowana na przykad, gdy niektre menu lub grupy menu
maj by wyczone na podstawie danych wywietlanych na ekranie. Warto o tym pamita
podczas projektowania menu.
Musimy zaj si jeszcze jednym istotnym aspektem menu, zanim przejdziemy do okien dialogowych. Android umoliwia tworzenie menu za pomoc plikw XML. Nastpny podrozdzia
powicono wanie zapoznaniu si z obsug takich menu majcych posta plikw XML.

Wczytywanie menu poprzez pliki XML


A do tej chwili tworzylimy nasze menu za pomoc kodu Java. Nie jest to najwygodniejszy
sposb, poniewa kade menu wymaga kilku identyfikatorw oraz staych zdefiniowanych dla
tych identyfikatorw. Niewtpliwie po pewnym czasie staje si to nuce.
Zamiast tego mona zdefiniowa menu w plikach XML; Android pozwala na to, poniewa menu
s uznawane za zasoby. Tworzenie menu za pomoc plikw XML ma kilka zalet, takich jak
moliwo nadania nazwy menu, automatyczne tworzenie kolejnoci menu oraz przyznawanie
identyfikatorw i tak dalej. Mona take wprowadzi obsug lokalizacji wobec tekstu zawartego
w menu.
eby zaprojektowa menu oparte na pliku XML, naley wykona nastpujce czynnoci:
1. Zdefiniuj plik XML ze znacznikami menu.
2. Umie plik w podkatalogu /res/menu. Nazwa pliku moe by dowolna. Nie ma ogranicze
co do liczby plikw. Android wygeneruje automatycznie identyfikator tego pliku.
3. Wykorzystaj identyfikator zasobw tego menu, aby wczyta plik XML do menu.
4. Zdefiniuj odpowiedzi na kliknicie za pomoc identyfikatora zasobw
wygenerowanego dla kadego elementu menu.
W poniszych punktach omwimy kady z tych etapw oraz zaprezentujemy fragmenty kodu.

Struktura pliku XML zasobw menu


Najpierw przyjrzymy si plikowi XML zawierajcemu definicje menu (listing 7.20). Wszystkie
pliki menu rozpoczynaj si od znacznika menu, po ktrym nastpuje seria znacznikw group.
Kady znacznik group odpowiada grupie elementw menu, omwionej na pocztku rozdziau.

Rozdzia 7 Praca z menu

269

Identyfikator grupy mona okreli za pomoc wyraenia @+id. W kadej grupie menu bd
umieszczone elementy menu, ktrych identyfikatory bd powizane z nazwami symbolicznymi.
W dokumentacji rodowiska Android SDK mona znale wszystkie argumenty dla tych
znacznikw jzyka XML.
Listing 7.20. Plik XML zawierajcy definicje menu
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa korzysta z domylnej kategorii. -->


<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/menu_testPick"
android:orderInCategory="5"
android:title="Cz testowa" />
<item android:id="@+id/menu_testGetContent"
android:orderInCategory="5"
android:title="Test pobierania treci" />
<item android:id="@+id/menu_clear"
android:orderInCategory="10"
android:title="Wyczy" />
<item android:id="@+id/menu_dial"
android:orderInCategory="7"
android:title="Dzwo" />
<item android:id="@+id/menu_test"
android:orderInCategory="4"
android:title="@+string/test" />
<item android:id="@+id/menu_show_browser"
android:orderInCategory="5"
android:title="Wywietl przegldark" />
</group>
</menu>

Plik XML menu, ktrego kod wida na listingu 7.20, posiada jedn grup menu. Na podstawie
definicji identyfikatora zasobu @+id/menuGroup_main tej grupie zostanie automatycznie
przydzielony identyfikator zasobw menuGroup_main w pliku R.java. W analogiczny sposb
wszystkie potomne elementy menu uzyskuj wasne identyfikatory, tworzone na bazie definicji
symbolicznych identyfikatorw zasobw z tego pliku XML.

Zapenianie plikw XML zasobw menu


Zamy, e nasz plik XML nosi nazw moje_menu.xml. Naley umieci ten plik w podkatalogu /res/menu. Dziki temu automatycznie zostanie wygenerowany identyfikator zasobw
R.menu.moje_menu.
Spjrzmy teraz, w jaki sposb moemy wykorzysta ten identyfikator do zapenienia menu opcji.
W Androidzie jest dostpna klasa android.view.MenuInflater suca do umieszczania
obiektw Menu z plikw XML. Klasa MenuInflater posuy nam do zapenienia menu za
pomoc identyfikatora R.menu.moje_menu:
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();

// z aktywnoci

270 Android 3. Tworzenie aplikacji


inflater.inflate(R.menu.moje_menu, menu);

// Musi by zwrcona warto true, eby menu byo widoczne


return true;
}

W powyszym kodzie najpierw pobieramy klas MenuInflater z klasy Activity, a nastpnie


kaemy jej przesa zawarto pliku XML bezporednio do menu.

Tworzenie odpowiedzi
dla elementw menu opartych na pliku XML
Nie mielimy jeszcze do czynienia z wyjtkow zalet tej metody staje si ona widoczna dopiero wtedy, gdy przychodzi czas na tworzenie odpowiedzi dla elementw menu. Odpowied dla
elementw umieszczonych w pliku XML tworzy si podobnie jak w przypadku programowania
w jzyku Java, istnieje jednak maa rnica. Tak jak wczeniej, elementami menu zajmuje
si wywoywana metoda onOptionsItemSelected. Tym razem bdziemy musieli skorzysta
z pomocy zasobw Androida (rozdzia 3. zosta powicony midzy innymi zasobom). W punkcie Struktura pliku XML zasobw menu wspomnielimy, e Android generuje nie tylko
identyfikator zasobu dla pliku XML, lecz rwnie niezbdne identyfikatory elementw, dziki
ktrym s one rozrniane. Pod wzgldem tworzenia odpowiedzi dla elementw menu jest
to olbrzymia zaleta, poniewa nie trzeba jawnie tworzy identyfikatorw tych elementw
i zarzdza nimi.
eby w peni zrozumie implikacje, w przypadku menu napisanych w jzyku XML nie trzeba
definiowa staych dla tych identyfikatorw oraz nie naley si martwi o ich unikatowo,
poniewa t kwesti zajmuje si proces generowania identyfikatorw zasobw. Wida to
w poniszym kodzie:
private void onOptionsItemSelected (MenuItem item)
{
this.appendMenuItemText(item);
if (item.getItemId() == R.id.menu_clear)
{
this.emptyText();
}
else if (item.getItemId() == R.id.menu_dial)
{

// co robi
}
else if (item.getItemId() == R.id.menu_testPick)
{

// co robi
}
else if (item.getItemId() == R.id.menu_testGetContent)
{

// co robi
}
else if (item.getItemId() == R.id.menu_show_browser)
{

// co robi
}
itd.
}

Rozdzia 7 Praca z menu

271

Zauwamy, w jaki sposb nazwy elementw menu z pliku zasobw XML automatycznie wygeneroway identyfikatory elementw w przestrzeni nazw R.id.

Krtkie wprowadzenie
do dodatkowych znacznikw menu w pliku XML
Podczas konstruowania plikw XML naley zna dostpne znaczniki. Informacje na ich temat
mona szybko zdoby, przegldajc wersje demonstracyjne interfejsw API, dostpne w zestawie Android SDK. Wrd nich znajduje si zbir menu, ktre pomagaj zrozumie wszystkie
aspekty programowania w Androidzie. W podkatalogu /res/menu znajduje si wiele przykadowych plikw XML zasobw menu. Omwimy pokrtce niektre kluczowe znaczniki.

Znacznik kategorii grupy


W pliku XML mona okreli kategori grupy za pomoc znacznika menuCategory:
<group android:id="@+id/some-group-id"
android:menuCategory="secondary">

Znacznik zaznaczania
Do kontrolowania zaznaczania na poziomie grupy mona zastosowa znacznik
Behavior:

checkable

<group android:id="@+id/noncheckable_group"
android:checkableBehavior="none">

Znacznik checked suy do kontrolowania zaznaczania na poziomie pojedynczych elementw:


<item android:id=".."
android:title="..."
android:checked="true" />

Tagi do symulowania podmenu


Podmenu jest reprezentowane jako element wewntrz menu:
<item android:title="Wszystko poza grup">
<menu>
<item...>
</menu>
</item>

Znacznik ikony menu


Dziki znacznikowi icon mona powiza obraz z elementem menu:
<item android:id=".. "
android:icon="@drawable/jakis-plik" />

272 Android 3. Tworzenie aplikacji

Znacznik wczania i wyczania menu


Mona wcza i wycza element menu za pomoc znacznika enabled:
<item android:id=".. "
android:enabled="true"
android:icon="@drawable/jakis-plik" />

Skrty dla elementu menu


Mona ustanawia skrty dla elementw menu poprzez znacznik alphabeticShortcut:
<item android:id="... "
android:alphabeticShortcut="a"
...
</item>

Widoczno menu
Widoczno elementu menu jest kontrolowana za pomoc flagi visible:
<item android:id="... "
android:visible="true"
...
</item>

Odnoniki
W trakcie nauki korzystania z klas menu Androida przydatny moe si okaza poniszy adres
URL. Mona tam znale projekty rodowiska Eclipse, powizane z niniejszym rozdziaem.
ftp://ftp.helion.pl/przyklady/and3ta.zip z tego adresu moemy pobra projekt testowy,
ukazujcy koncepcje omwione w tym rozdziale. Waciwy plik znajdziesz w katalogu
o nazwie ProAndroid3_R07_Menu.

Podsumowanie
W niniejszym rozdziale zaprezentowano sposb korzystania z rnorodnych rodzajw menu
dostpnych w Androidzie: menu standardowych, kontekstowych, alternatywnych oraz opartych
na jzyku XML. W niektrych rozdziaach, na przykad w 8. (Praca z oknami dialogowymi),
16. (Analiza animacji dwuwymiarowej) czy 20. (Programowanie grafiki trjwymiarowej za
pomoc biblioteki OpenGL), przedstawimy sposb wykorzystania menu XML do przetestowania
omawianych w nich funkcji. Natomiast wprowadzone w wersji 3.0 Androida paski zada oraz
ich interakcja z menu s przedmiotami rozwaa w rozdziale 30.

R OZDZIA

8
Praca z oknami dialogowymi

System Android zapewnia rozbudowan obsug okien dialogowych. Wrd jawnie


obsugiwanych typw okien dialogowych wyrniamy okna alertw, listy kompletacyjne, okna dialogowe pojedynczego wyboru, okna wielokrotnego wyboru, okna
informujce o postpie dziaania oraz obiekty TimePicker i DatePicker (lista ta moe si rni w zalenoci od wersji Androida). Istnieje rwnie moliwo stosowania niestandardowych okien dialogowych w razie potrzeby. Zasadniczym
celem tego rozdziau jest omwienie nie samych okien dialogowych Androida,
lecz raczej architektury kryjcej si pod tym pojciem. W wersji 3.0 systemu dodano okna dialogowe oparte na fragmentach. Ten aspekt zosta omwiony w rozdziale 29. Spodziewamy si, e wraz z upywem czasu okna dialogowe oparte na
fragmentach bd w coraz wikszym stopniu zastpowa omawiane tu tradycyjne
okna. Jednak te klasyczne okna dialogowe nie s jeszcze przestarzae i cigle stanowi standard w urzdzeniach obsugujcych system Android.
Okna dialogowe w Androidzie s asynchroniczne, dziki czemu zapewnia si wiksz elastyczno. Jednak osoby przyzwyczajone do pracy w rodowiskach programistycznych obsugujcych okna dialogowe synchroniczne (na przykad Microsoft
Windows) mog uzna okna dialogowe asynchroniczne za nieco nieintuicyjne.
Dziki omwieniu podstaw tworzenia oraz stosowania okien dialogowych w Androidzie wprowadzimy pewn intuicyjn abstrakcj, uatwiajc prac z asynchronicznymi oknami dialogowymi. Nastpnie za pomoc tej abstrakcji zaimplementujemy kilka przykadowych okien dialogowych. W znajdujcym si na kocu
rozdziau podrozdziale Odnoniki zamiecilimy rwnie adres URL strony
zawierajcej przygotowany projekt, utworzony z myl o oknach dialogowych.
Warto pobra ten projekt w celu poeksperymentowania z objanianymi tu koncepcjami oraz kodem.

274 Android 3. Tworzenie aplikacji

Korzystanie z okien dialogowych w Androidzie


Osoby pracujce w rodowiskach obsugujcych synchroniczne okna dialogowe (zwaszcza
modalne) musz zmieni tok mylenia podczas projektowania okien dialogowych w Androidzie.
W tym systemie okna dialogowe s asynchroniczne, a do tego rwnie zarzdzane. Oznacza
to, e s wielokrotnie wykorzystywane pomidzy wywoaniami, by moe w celu zwikszenia wydajnoci.

Projektowanie okien alertw


Dyskusj rozpoczniemy od okien alertw. Zasadniczo okna alertw wywietlaj proste informacje dotyczce poprawnoci wpisywanych danych lub czasami ostrzee przed bdami (prawdziwymi lub rzekomymi). Przeledmy poniszy przykad, czsto spotykany na stronach HTML:
if (validate(pole1) == false)
{

// wskazuje poprzez okno alertu, e format nie jest poprawny


showAlert("Dane w pole1 s wpisane w niewaciwym formacie.");

// przechodzi do tego pola


//...i kontynuuje
}

Tego typu program najlepiej utworzy w programie JavaScript za pomoc funkcji alert, ktra
powoduje wywietlenie prostego, synchronicznego okna dialogowego, zawierajcego informacj
oraz przycisk OK. Po klikniciu przycisku OK program dziaa dalej. To okno dialogowe jest
uznawane za modalne oraz synchroniczne, poniewa uzyskamy dostp do nastpnej linijki kodu
dopiero po wykonaniu funkcji alert.
Okno typu alert przydaje si podczas sprawdzania bdw. Jednak w Androidzie takie bezporednie funkcje lub okna dialogowe nie s dostpne. Zamiast nich jest obsugiwany konstruktor
okien alertw, aplikacja oglnego przeznaczenia suca do tworzenia oraz uywania okien
alertw. Mona zatem samemu stworzy okno alertu, korzystajc z klasy android.app.
AlertDialog.Builder. Dziki tej klasie mona konstruowa okna dialogowe pozwalajce
uytkownikowi wykonywa nastpujce czynnoci:
odczytywa wiadomoci oraz odpowiada Tak lub Nie;
wybiera element z listy;
wybiera wiele elementw z listy;
obserwowa postp dziaania aplikacji;
wybra jedn z opcji;
odpowiedzie na zacht, zanim program wznowi dziaanie.
Pokaemy, w jaki sposb skonstruowa jedno z wymienionych okien dialogowych oraz jak wywoa je z elementu menu. Algorytm odnoszcy si do wszystkich wspomnianych powyej okien
dialogowych skada si z nastpujcych etapw:
1. Utwrz obiekt klasy Builder.
2. Ustaw takie parametry wywietlania, jak liczba przyciskw, lista elementw i tak dalej.
3. Ustanw metody wywoywania zwrotnego dla przyciskw.

Rozdzia 8 Praca z oknami dialogowymi

275

4. Zaprogramuj obiekt Builder, eby skonstruowa okno dialogowe. Typ utworzonego


okna dialogowego zaley od informacji zawartych w tym obiekcie.
5. Zastosuj metod dialog.show() do wywietlenia okna dialogowego.
Na listingu 8.1 zosta przedstawiony kod, w ktrym zaimplementowano wymienione etapy.
Listing 8.1. Budowanie oraz wywietlanie okna alertu
public class Alerts
{
public static void showAlert(String message, Context ctx)
{

// Tworzenie konstruktora
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Okno alertu");

// Dodanie przyciskw i obiektu nasuchujcego


EmptyOnClickListener pl = new EmptyOnClickListener();
builder.setPositiveButton("OK", pl);

// Utworzenie okna dialogowego


AlertDialog ad = builder.create();

// Wywietlenie
ad.show();
}
}
public class EmptyOnClickListener
implements android.content.DialogInterface.OnClickListener {
public void onClick(DialogInterface v, int buttonId)
{
}
}

Kod z listingu 8.1 mona wywoa w odpowiedniej aktywnoci testowej (na przykad w przygotowanym przez nas projekcie) poprzez utworzenie elementu menu i przypisanie mu nastpujcej odpowiedzi:
if (item.getItemId() == R.id.menu_simple_alert)
{
Alerts.showAlert("Przykad prostego okna alertu", this);
}

Efekt kocowy moe wyglda tak jak na rysunku 8.1 (w zalenoci od aktywnoci testowej).
Kod tego alertu nie jest skomplikowany (jest on zawarty na listingu 8.1 oraz w nastpujcym po
nim wycinku kodu). Nawet fragment dotyczcy obiektu nasuchujcego jest atwy do zrozumienia. W istocie nacinicie przycisku nic nie powoduje.

276 Android 3. Tworzenie aplikacji

Rysunek 8.1. Proste okno alertu

Warto jednak zauway, e obiekt nasuchujcy przekaza odniesienie do interfejsu Dialog


To odniesienie wskazuje rzeczywiste okno dialogowe, wobec ktrego nastpuje
wywoanie zwrotne. Interfejs ten obsuguje pewn liczb staych wykorzystywanych przez klasy
okien dialogowych, pewn liczb interfejsw wywoa zwrotnych oraz dwie kluczowe metody.
S to klasy:

Interface.

cancel()
dismiss()

Zazwyczaj nie musimy ich wywoywa, poniewa w razie koniecznoci s one automatycznie
przywoywane podczas kliknicia przycisku. Jeeli chcemy reagowa na wywoania tych metod,
moemy zarejestrowa odpowiadajce im wywoania zwrotne. W dokumentacji zestawu SDK
dotyczcej interfejsu DialogInterface znajdziemy pen list dostpnych metod zwrotnych.
Na listingu 8.1 utworzylimy po prostu pusty obiekt nasuchujcy, zarejestrowany dla przycisku
OK. Jedyn now czynnoci jest brak zastosowania polecenia new do utworzenia okna dialogowego. Zamiast tego skonfigurowalimy parametry i utworzylimy konstruktor, ktry zbudowa okno alertu.

Projektowanie okna dialogowego zachty


Po udanym utworzeniu prostego okna alertu przejdmy do nieco bardziej skomplikowanego
rodzaju okna dialogowego: do okna dialogowego zachty. Wedle definicji obowizujcej dla
jzyka JavaScript okno dialogowe zachty wywietla wskazwk lub pytanie i oczekuje od uytkownika wpisania danych w polu edycji. Wpisany cig znakw jest odsyany programowi,
ktry wznawia dziaanie. Jest to znakomity przykad do przeledzenia, poniewa moemy
zaobserwowa zastosowanie wielu funkcji pochodzcych z klasy Builder. Mona te zapozna
si z natur synchronicznoci, asynchronicznoci, modalnoci oraz niemodalnoci okien dialogowych w Androidzie.

Rozdzia 8 Praca z oknami dialogowymi

277

Poniej przedstawilimy sposb utworzenia okna dialogowego zachty:


1. Utwrz widok ukadu graficznego okna dialogowego zachty.
2. Wczytaj ukad graficzny do klasy View.
3. Skonstruuj obiekt klasy Builder.
4. Ustanw widok w obiekcie Builder.
5. Skonfiguruj przyciski wraz z ich wywoaniami zwrotnymi, eby przyjmoway
wprowadzany tekst.
6. Utwrz okno dialogowe za pomoc konstruktora alertw.
7. Wywietl okno dialogowe.
Zaprezentujemy teraz kod dla kadego z opisanych etapw.

Plik XML ukadu graficznego dla okna dialogowego zachty


Aby zbudowa okno dialogowe zachty, trzeba najpierw wpisa tekst zachty w widoku TextView,
po ktrym ustawia si pole edycji. Uytkownik moe w nim wpisa odpowied na zacht.
Listing 8.2 przedstawia zawarto pliku XML ukadu graficznego okna dialogowego zachty. Jeeli plik ten zostanie nazwany prompt_layout.xml, naley go umieci w podkatalogu
/res/layout, aby Android utworzy dla niego identyfikator zasobw R.layout.prompt_layout.
Listing 8.2. Plik prompt_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/promptmessage"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:text="Tutaj naley wpisa tekst"
android:gravity="left"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/editText_prompt"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:scrollHorizontally="true"
android:autoText="false"
android:capitalize="none"
android:gravity="fill_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>

278 Android 3. Tworzenie aplikacji

Konfigurowanie konstruktora alertw za pomoc widoku uytkownika


Poczmy etapy 2., 3. oraz 4. naszej instrukcji: zaadowanie widoku XML oraz skonfigurowanie
go w konstruktorze alertw, aby utworzy okno dialogowe zachty. Android zawiera klas
android.view.LayoutInflater pozwalajc na utworzenie widoku View z pliku XML definicji
ukadu graficznego. Teraz pokaemy, jak wykorzysta wystpienie metody LayoutInflater do
zapenienia widoku naszego okna dialogowego na podstawie pliku XML ukadu graficznego
(listing 8.3).
Listing 8.3. Umieszczanie ukadu graficznego w oknie dialogowym
LayoutInflater li = LayoutInflater.from(activity);

// zmienna activity stanowi odniesienie do naszej aktywnoci lub kontekstu


View view = li.inflate(R.layout.prompt_layout, null);

// przywouje konstruktor i ustanawia widok


AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Zachta");
builder.setView(view);

Na listingu 8.3 pokazano, jak uzyska obiekt klasy LayoutInflater za pomoc statycznej metody
LayoutInflater.from(ctx), a nastpnie uy go do przeksztacenia kodu XML do widoku
View. Po tym naley wprowadzi do konstruktora alertw tytu oraz utworzony przed momentem widok.

Konfigurowanie przyciskw i obiektw nasuchujcych


Przechodzimy do etapu 5.: konfigurowania przyciskw. Musimy umieci przyciski OK oraz
Anuluj, eby uytkownik mg odpowiedzie na zacht. Jeeli uytkownik kliknie przycisk
Anuluj, program nie bdzie odczytywa informacji z okna dialogowego zachty. Po wybraniu
przycisku OK program pobierze warto z pola tekstowego i przeniesie j do aktywnoci.
eby skonfigurowa te przyciski, potrzebny bdzie obiekt nasuchujcy, odpowiadajcy na
wywoania zwrotne. Kod dla obiektu nasuchujcego zostanie przedstawiony w podrozdziale
Obiekt nasuchujcy okna dialogowego zachty, teraz za zajmijmy si konfiguracj przyciskw. Odpowiedni kod, stanowicy rozwinicie listingu 8.3, znajduje si na listingu 8.4.
Listing 8.4. Konfigurowanie przyciskw OK i Anuluj
// dodaje przyciski oraz obiekt nasuchujcy
PromptListener pl = new PromptListener(view);
builder.setPositiveButton("OK", pl);
builder.setNegativeButton("Anuluj", pl);

Piszc kod na listingu 8.4, zaoylimy, e nazw klasy obiektu nasuchujcego jest Prompt
Listener. Ten obiekt nasuchujcy zosta zarejestrowany dla kadego przycisku. Klasa
PromptListener pobiera skonstruowany za pomoc kodu z listingu 8.3 ukad graficzny. Jeeli
przyjrzymy si tej klasie nieco uwaniej, dostrzeemy, e zmienna view jest stosowana do
identyfikowania kontrolek tekstowych oraz odczytywania danych wprowadzanych przez
uytkownika.

Rozdzia 8 Praca z oknami dialogowymi

279

Utworzenie i wywietlenie okna dialogowego zachty


W kocu docieramy do etapw 6. i 7.: utworzenia oraz wywietlenia okna dialogowego zachty.
Dziki konstruktorowi okien dialogowych nie powinno by z tym najmniejszych problemw
(listing 8.5).
Listing 8.5. Konstruktor okien alertw tworzy okno dialogowe
// tworzy okno dialogowe
AlertDialog ad = builder.create();
ad.show();

// zwraca zacht
return pl.getPromptReply();

W ostatniej linii wykorzystujemy obiekt nasuchujcy do odesania odpowiedzi na zacht.


Teraz, zgodnie z obietnic, przedstawimy kod klasy PromptListener.

Obiekt nasuchujcy okna dialogowego zachty


Okno dialogowe zachty oddziauje z aktywnoci poprzez wywoanie klasy PromptListener
obiektu nasuchujcego. Klasa ta posiada jedn metod, nazwan onClick, a przekazany tej
metodzie identyfikator przycisku rozpoznaje, ktry przycisk zosta wcinity. Reszta kodu
jest atwa do zrozumienia (listing 8.6). Kiedy uytkownik wpisze tekst i kliknie przycisk OK,
warto z pola tekstowego zostanie przeniesiona do pola promptReply. W przeciwnym wypadku jego warto pozostanie null. Zwrmy uwag na sposb wykorzystania identyfikatora
kontrolki EditText (editText_prompt) utworzonej na listingu 8.2.
Listing 8.6. Klasa PromptListener obiektu nasuchujcego
public class PromptListener
implements android.content.DialogInterface.OnClickListener
{

// Zmienna lokalna zwracajca warto wpisan w zachcie


private String promptReply = null;

// Przechowuje zmienn dla widoku, aby odczyta warto zachty


View promptDialogView = null;

// Przyjmuje widok w konstruktorze


public PromptListener(View inDialogView) {
promptDialogView = inDialogView;
}

// Wywouje metod z okien dialogowych


public void onClick(DialogInterface v, int buttonId) {
if (buttonId == DialogInterface.BUTTON_POSITIVE) {

// Przycisk OK
promptReply = getPromptText();
}
else {

// Przycisk Anuluj
}
}

promptReply = null;

280 Android 3. Tworzenie aplikacji


// Metoda dostpowa do zawartoci pola edycji
private String getPromptText() {
EditText et = (EditText)
promptDialogView.findViewById(R.id.editText_prompt);
return et.getText().toString();
}
public String getPromptReply() { return promptReply; }
}

Przedstawienie kompletnego kodu


Po omwieniu kadego fragmentu kodu, dziki ktremu mona wywietli okno dialogowe
zachty, zaprezentujemy go w caoci, aby Czytelnik mg go przetestowa (listing 8.7).
Pominlimy klas PromptListener, poniewa jej kod zosta przedstawiony oddzielnie na
listingu 8.6.
Listing 8.7. Przykadowy kod okna dialogowego zachty
public class Alerts
{
public static String prompt(String message, Context ctx)
{

// wczytuje jaki widok


LayoutInflater li = LayoutInflater.from(ctx);
View view = li.inflate(R.layout.prompt_layout, null);

// generuje konstruktor i ustanawia widok


AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Zachta");
builder.setView(view);

// dodaje przyciski i obiekt nasuchujcy


PromptListener pl = new PromptListener(view);
builder.setPositiveButton("OK", pl);
builder.setNegativeButton("Anuluj", pl);

// generuje okno dialogowe


AlertDialog ad = builder.create();

// wywietla
ad.show();
return pl.getPromptReply();
}
}

Kod z listingu 8.7 mona wywoa poprzez utworzenie elementu menu w odpowiednim
rodowisku testowym oraz napisanie nastpujcej odpowiedzi dla tego elementu:
if (item.getItemId() == R.id.nasz_identyfikator_elementu_menu)
{
String reply = Alerts.showPrompt("Tutaj wpisujesz tekst", this);
}

Rozdzia 8 Praca z oknami dialogowymi

281

Rezultat powinien przypomina ekran z rysunku 8.2.

Rysunek 8.2. Proste okno dialogowe zachty

Jednak po przetestowaniu tego kodu Czytelnik zauway, e okno dialogowe zachty zawsze
odsya warto null, nawet po wpisaniu tekstu w polu edycji. Wynika to z faktu, e metoda
show() wywouje okno dialogowe w sposb asynchroniczny:
ad.show();

// dialog.show

return pl.getPromptReply();

// listener.getPromptReply()

Oznacza to, e wywoanie metody getPromptReply() (listing 8.6) nastpuje, zanim uytkownik
zdy wpisa tekst w polu edycji i klikn przycisku OK. Taki bd logiczny zblia nas do zrozumienia sedna natury okien dialogowych systemu Android.

Natura okien dialogowych w Androidzie


Jak ju wspomnielimy, wywietlanie okien dialogowych w Androidzie jest procesem asynchronicznym. Po wywietleniu okna dialogowego odpowiedzialny za to gwny wtek wraca do
programu i kontynuuje przetwarzanie reszty kodu. Nie oznacza to wcale, e okno dialogowe nie jest modalne. W istocie nadal takie jest. Kliknicia mysz dotycz wycznie okna
dialogowego, podczas gdy nadrzdna aktywno wraca do ptli komunikatw.
W niektrych systemach okienkowych modalne okna dialogowe zachowuj si nieco inaczej.
Program wywoujcy pozostaje zablokowany, dopki uytkownik nie udzieli odpowiedzi poprzez okno dialogowe (blokada programu moe by czysto wirtualna). W systemach Windows
wtek wysyajcy komunikat rozpoczyna przesyanie danych do okna dialogowego, a wstrzymuje je dla okna gwnego. Po zamkniciu okna dialogowego wtek wraca do gwnego okna.
W tym przypadku wywoanie jest synchroniczne.

282 Android 3. Tworzenie aplikacji


W przypadku urzdzenia typu handheld, w ktrym niespodziewane zdarzenia wystpuj czciej
i gwny wtek musi na nie reagowa, takie podejcie moe si okaza nieskuteczne. eby
zagwarantowa wystarczajco krtki czas odpowiedzi, Android natychmiast zwraca gwny
wtek do ptli komunikatw.
W konsekwencji takiego zaoenia nie ma moliwoci zaimplementowania prostego okna dialogowego, w ktrym program oczekuje odpowiedzi i wstrzymuje dziaanie do czasu jej uzyskania.
W istocie model programowania okien dialogowych musi wyglda inaczej pod ktem definiowania wywoa zwrotnych.

Przeprojektowanie okna dialogowego zachty


Przyjrzyjmy si ponownie problematycznemu fragmentowi kodu z poprzedniej implantacji
okna dialogowego zachty:
if (item.getItemId() == R.id.identyfikator_naszego_menu)
{
String reply = Alerts.showPrompt("Tutaj wpisujesz tekst", this);
}

Udowodnilimy, e warto cigu znakw w zmiennej reply bdzie zawsze wynosia null,
poniewa okno dialogowe zachty, zainicjalizowane przez metod Alerts.showPrompt(),
nie ma moliwoci odesania wpisanej wartoci do tego samego wtku. Jedynym sposobem,
dziki ktremu mona tego dokona, jest bezporednie zaimplementowanie wywoywanej
metody w aktywnoci, bez wykorzystywania klasy PromptListener. W tym celu naley zaimplementowa metod onClickListener w klasie Activity:
public class SampleActivity extends Activity
implements android.content.DialogInterface.OnClickListener
{

// ...jaki inny kod


if (item.getItemId() == R.id.identyfikator_naszego_menu)
{
Alerts.showPrompt("Tutaj wpisujesz tekst", this);
}

// ...
public void onClick(DialogInterface v, int buttonId)
{

// tutaj naley wprowadzi jaki kod odpowiedzialny za odczytanie wartoci cigu


// znakw z okna dialogowego
}

Jak wida, dziki metodzie onClick mona poprawnie odczytywa zmienne z wywoanego okna
dialogowego, poniewa zanim zostanie ona wywoana, uytkownik zdy zamkn okno
dialogowe.
Taka forma korzystania z okien dialogowych jest cakowicie poprawna. Jednak twrcy Androida
zapewnili dodatkowy mechanizm optymalizacji wydajnoci w postaci zarzdzanych okien dialogowych obiektw wielokrotnie wykorzystywanych przez rne wywoania. Nadal jednak trzeba
stosowa wywoywania zwrotne. Tak naprawd caa wiedza zdobyta podczas implementowania
okna dialogowego zachty przyda si podczas pracy z zarzdzanymi oknami dialogowymi i uatwi
zrozumienie ich dziaania. Takie zarzdzane okna dialogowe pozwalaj rwnie Androidowi na
kontrolowanie stanu okien dialogowych pomidzy ich wywoaniami, dopki stan widoku aktywnoci pozostaje niezmieniony.

Rozdzia 8 Praca z oknami dialogowymi

283

Praca z zarzdzanymi oknami dialogowymi


Android wykorzystuje protok zarzdzania oknem dialogowym, dziki czemu ponownie wykorzystuje uprzednio utworzone okna dialogowe, zamiast tworzy ich nowe wystpienia. W tym
podrozdziale omwimy szczegy protokou zarzdzania oknami dialogowymi oraz pokaemy,
w jaki sposb zaimplementowa alert w postaci zarzdzanego okna dialogowego. Jednak naszym
zdaniem protok zarzdzania oknami dialogowymi powoduje, e praca z tymi oknami jest
dosy mudna. Utworzymy niewielk struktur z wyrywkowych fragmentw tego protokou,
aby uatwi prac z zarzdzanymi oknami dialogowymi.

Protok zarzdzanych okien dialogowych


Podstawowym zadaniem protokou zarzdzanych okien dialogowych jest ponowne wykorzystanie okna dialogowego podczas jego kolejnych wywoa. Przypomina to stosowanie pul
obiektw w rodowisku Java. Protok zarzdzanych okien dialogowych skada si z nastpujcych etapw:
1. Przypisz niepowtarzalny identyfikator dla kadego tworzonego i wykorzystywanego
okna dialogowego. Zakadamy, e etykiet jednego z okien dialogowych jest 1.
2. Zaprogramuj wywietlenie okna dialogowego z etykiet 1.
3. Android sprawdza, czy bieca aktywno zawiera ju okno dialogowe oznaczone
jako 1. Jeeli tak jest, okno dialogowe zostaje wywietlone bez koniecznoci ponownego
tworzenia. W celach porzdkowych Android wywouje funkcj onPrepareDialog()
przed wywietleniem okna dialogowego.
4. Jeeli okno dialogowe z etykiet 1 nie istnieje, Android wywouje metod
onCreateDialog() poprzez przekazanie identyfikatora okna dialogowego
(w tym przypadku 1).
5. Teraz przeso metod onCreateDialog(). W tym celu utwrz okno dialogowe
za pomoc konstruktora alertw, a nastpnie je wywoaj. Jednak najpierw musisz
okreli na podstawie identyfikatora, ktre okno dialogowe bdzie tworzone. W tym
celu stosuje si instrukcj switch.
6. Android wywietla okno dialogowe.
7. Okno dialogowe wywouje odpowiednie funkcje po klikniciu przyciskw.
Zastosujmy teraz protok do ponownego zaimplementowania okna dialogowego, ktre nie jest
zarzdzane, w formie zarzdzanego alertu.

Przeksztacenie niezarzdzanego okna dialogowego


na zarzdzane okno dialogowe
W celu ponownego zaimplementowania okna alertu bdziemy postpowa zgodnie z etapami
wypisanymi w poprzednim podrozdziale. Rozpocznijmy od zdefiniowania niepowtarzalnego
identyfikatora tego okna dialogowego w kontekcie danej aktywnoci:
// unikatowy identyfikator okna dialogowego
private static final int DIALOG_ALERT_ID = 1;

Wystarczajco proste. Wanie utworzylimy identyfikator okna dialogowego, sucy do sterowania wywoaniami zwrotnymi. Identyfikator ten pozwala nam na wykonanie poniszej czynnoci w odpowiedzi na kliknicie elementu menu:

284 Android 3. Tworzenie aplikacji


jakasaktywnosc.showDialog(this.DIALOG_ALERT_ID);

Dostpna w zestawie Android SDK metoda showDialog uruchamia proces wywoania metody
onCreateDialog() klasy naszej aktywnoci. Android jest wystarczajco sprytny, eby nie
wywoywa wielokrotnie metody onCreateDialog(). Po jej wywoaniu musimy utworzy okno
dialogowe i odesa je Androidowi. Metoda onCreateDialog() jest pniej przechowywana wewntrz systemu na wypadek potrzeby ponownego uycia. Poniej przedstawilimy przykadowy
kod, sucy do utworzenia okna dialogowego na podstawie unikalnego identyfikatora:
public class SomeActivity extends Activity {
...
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_ALERT_ID:
return createAlertDialog();
}
return null;
}
private Dialog createAlertDialog()
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Okno alertu");
builder.setMessage("jaki komunikat");
EmptyOnClickListener emptyListener = new EmptyOnClickListener();
builder.setPositiveButton("OK", emptyListener );
AlertDialog ad = builder.create();
return ad;
}

Warto zwrci uwag, w jaki sposb metoda onCreateDialog() rozpoznaje przychodzcy


identyfikator w celu okrelenia waciwego okna dialogowego. Sama funkcja onCreate
Dialog() jest przechowywana w oddzielnej funkcji i przetwarza rwnolegle omwiony wczeniej proces tworzenia okna alertu. W tym kodzie rwnie wykorzystano t sam funkcj
EmptyOnClickListener, ktra bya stosowana podczas korzystania z alertu.
Poniewa okno dialogowe jest tworzone tylko raz, potrzebny jest mechanizm pozwalajcy
na zmian elementw okna dialogowego podczas jego kolejnych wywietle. Do tego celu
suy metoda onPrepareDialog():
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id) {
case DIALOG_ALERT_ID:
prepareAlertDialog(dialog);
}
}
private void prepareAlertDialog(Dialog d) {
AlertDialog ad = (AlertDialog)d;

// tutaj naley co zmieni w oknie dialogowym


}

Rozdzia 8 Praca z oknami dialogowymi

285

Jeeli powyszy kod zostanie wstawiony, metoda showDialog(1) bdzie dziaa. Nawet jeeli ta
metoda bdzie przywoywana wiele razy, metoda onCreateMethod zostanie wywoana tylko raz.
Taki sam protok mona zastosowa wobec okna dialogowego zachty.
Utworzenie odpowiedzi na wywoanie okna dialogowego to kawa roboty, jednak korzystanie
z protokou zarzdzanych okien dialogowych jest jeszcze bardziej pracochonne. Po dokadnym
przestudiowaniu tego protokou stwierdzilimy, e wyabstrahujemy go i przeksztacimy tak,
eby spenia dwa zadania:
spowodowa, aby identyfikacja okna dialogowego oraz jego tworzenie odbyway si
poza klas aktywnoci obiektw;
umieci proces tworzenia okna dialogowego oraz jego odpowiedzi
w wyspecjalizowanej klasie okna dialogowego.
W nastpnym podrozdziale omwimy proces projektowania takiej struktury, a nastpnie zastosujemy j do ponownego utworzenia okna alertu i okna dialogowego zachty.

Uproszczenie protokou zarzdzanych okien dialogowych


Wikszo Czytelnikw pewnie zdya zauway, e praca z zarzdzanymi oknami dialogowymi moe by nieco chaotyczna, a dodatkowo moe wprowadzi nieporzdek do gwnego
kodu. Gdyby przeksztaci protok zarzdzanych okien dialogowych w prostsz form,
wygldaby on nastpujco:
1. Utwrz instancj wymaganego okna dialogowego, stosujc instrukcj new. Zachowaj t
instancj jako lokaln zmienn. Nazwijmy j dialog1.
2. Wywietl okno dialogowe za pomoc metody dialog1.show().
3. Zaimplementuj w aktywnoci jedn metod nazwan dialogFinished().
4. W metodzie dialogFinished() odczytaj atrybuty zmiennej dialog1, takie jak
dialog1.getValue1().
Zgodnie z tym schematem wywietlenie zarzdzanego okna alertu bdzie moliwe za pomoc
nastpujcego kodu:
//...class MyActivity ...
{

// nowe okno dialogowe


ManagedAlertDialog mad = new ManagedAlertDialog("komunikat", ..., .. );

//...jaka metoda menu


if (item.getItemId() == R.id.identyfikator_naszego_menu)
{

// wywietla okno dialogowe


mad.show();
}

//...
// w razie potrzeby uzyskuje dostp do wntrza okna dialogowego mad
dialogFinished()
{

//...
// uywa wartoci z okna dialogowego
mad.getA();

286 Android 3. Tworzenie aplikacji


mad.getB();
}
}

Uwaamy, e jest to o wiele prostszy model korzystania z okien dialogowych. Wyranie wida
zalety tego rozwizania:

Nie ma potrzeby nadawania ani zapamitywania wasnych identyfikatorw.

Nie trzeba wstawia do gwnego kodu fragmentw sucych do tworzenia


okien dialogowych.

Mona bezporednio wykorzystywa obiekty pochodzce z okna dialogowego w celu odczytania wartoci.

Jaki jest mechanizm dziaania tego abstrakcyjnego rozwizania? W pierwszym etapie przenosimy procesy tworzenia i przygotowywania okna dialogowego do klasy rozpoznajcej podstawowe
okno dialogowe. Interfejs ten nosi nazw IDialogProtocol. W tym interfejsie bezporednio
zaimplementowano metod show(). Takie okna dialogowe s zbierane oraz przechowywane
w rejestrze bazowej klasy aktywnoci, a ich identyfikatory peni funkcje kluczy. Bazowa klasa na
podstawie identyfikatorw okien dialogowych rozdziela wywoania onCreate, onPrepare oraz
onClick i kieruje je do klasy okna dialogowego. Omawiana architektura zostaa szczegowo
naszkicowana na rysunku 8.3.

Rysunek 8.3. Struktura prostego zarzdzanego okna dialogowego

Rozdzia 8 Praca z oknami dialogowymi

287

Na listingu 8.8 zaprezentowano zastosowanie tej struktury. W dalszej czci rozdziau pokazalimy kod rdowy najwaniejszych klas (w tym GenericPromptDialog i GenericManagedAlertDialog), jednak nie zamiecilimy w ksice penej klasy sterownika. Na listingu
8.8 przedstawilimy jedynie jego najwaniejsze aspekty. W podrozdziale Odnoniki mona znale adres URL do projektu, w ktrym klasa ta pozwala na przetestowanie uprzednio
omwionych okien dialogowych.
Listing 8.8. Wersja skrcona protokou zarzdzanych okien dialogowych
public class MainActivity extends ManagedDialogsActivity
{

// okno dialogowe 1
private GenericManagedAlertDialog gmad =
new GenericManagedAlertDialog(this,1,"InitialValue");

// okno dialogowe 2
private GenericPromptDialog gmpd =
new GenericPromptDialog(this,2,"InitialValue");

// elementy menu uruchamiajce okna dialogowe


if (item.getItemId() == R.id.identyfikator1_naszego_menu)
{
gmad.show();
}
else if (item.getItemId() == R.id.identyfikator2_naszego_menu)
{
gmpd.show();
}

// zajmuje si kwesti wywoa


public void dialogFinished(ManagedActivityDialog dialog, int buttonId)
{
if (dialog.getDialogId() == gmpd.getDialogId())
{

// zakadajc, e gmpd zawiera metod dostpow do cigu znakw odpowiedzi


String replyString = gmpd.getReplyString();
}
}
}

Aby ta struktura zadziaaa, naley najpierw rozszerzy klas ManagedDialogsActivity.


Nastpnie tworzy si obiekty potrzebnych okien dialogowych, kady pochodzcy z klasy
ManagedActivityDialog. W razie implementacji odpowiedzi na kliknicie elementu menu
wystarczy zastosowa metod show() wobec tych okien dialogowych. Okna dialogowe same pobieraj parametry niezbdne do ich utworzenia i wywietlenia. Chocia przekazujemy identyfikator okna dialogowego, nie bdziemy musieli ju go pamita. Jeeli kto chce, moe je
nawet cakiem pomin.
Teraz zajmiemy si kad klas przedstawion na rysunku 8.3. Ponisze fragmenty kodu
rdowego s rwnie dostpne do pobrania ze strony przykadowych projektw. Jeeli Czytelnik zechce skompilowa ten kod, warto pobra ten projekt. Jeli jednak kto postanowi
inaczej, w ksice znajdzie wikszo wymaganego kodu rdowego, ale bdzie musia samodzielnie wypeni kilka luk.

288 Android 3. Tworzenie aplikacji

IDialogProtocol
Interfejs IDialogProtocol definiuje pojcie zarzdzanego okna dialogowego. Zadaniem zarzdzanego okna dialogowego jest tworzenie okna dialogowego i przygotowanie go do kadego
wywietlenia. Rozsdne jest take przekazanie funkcji show() do samego okna dialogowego.
Okno dialogowe musi rozpoznawa take kliknicia przycisku oraz umoliwia wywoywanie odpowiedniej funkcji nadrzdnej po jego zamkniciu. Poniszy kod przedstawia te dziaania
w formie zestawu funkcji:
public interface IDialogProtocol
{
public Dialog create();
public void prepare(Dialog dialog);
public int getDialogId();
public void show();
public void onClickHook(int buttonId);
}

ManagedActivityDialog
Abstrakcyjna klasa ManagedActivityDialog zapewnia wspln implementacj wszystkim klasom okien dialogowych, wymagajcym wprowadzenia interfejsu IDialogProtocol. Pozwala
bazowym klasom na przesonicie funkcji create oraz prepare, ale umoliwia zaimplementowanie wszystkich pozostaych metod klasy IDialogProtocol. Klasa ManagedActivityDialog
informuje take nadrzdn aktywno o zakoczeniu dziaania okna dialogowego po zarejestrowaniu zdarzenia kliknicia. Wykorzystuje wzorce szablonw uchwytw i pozwala klasom
pochodnym na korzystanie z wyspecjalizowanej metody uchwytu onClickHook. Klasa ta jest rwnie odpowiedzialna za przekierowanie metody show() do nadrzdnej aktywnoci, a tym samym
implementacja tej metody staje si bardziej naturalna. Klasa ManagedActivityDialog powinna
by stosowana jako klasa bazowa dla wszystkich nowych okien dialogowych (listing 8.9).
Listing 8.9. Klasa ManagedActivityDialog
public abstract class ManagedActivityDialog implements IDialogProtocol
,android.content.DialogInterface.OnClickListener
{
private ManagedDialogsActivity mActivity;
private int mDialogId;
public ManagedActivityDialog(ManagedDialogsActivity a, int dialogId)
{
mActivity = a;
mDialogId = dialogId;
}
public int getDialogId()
{
return mDialogId;
}
public void show()
{
mActivity.showDialog(mDialogId);
}
public void onClick(DialogInterface v, int buttonId)
{
onClickHook(buttonId);

Rozdzia 8 Praca z oknami dialogowymi

289

this.mActivity.dialogFinished(this, buttonId);
}
}

DialogRegistry
Klasa DialogRegistry peni dwie funkcje. Tworzy map powiza pomidzy identyfikatorami
okien dialogowych a ich rzeczywistymi (wbudowanymi) wystpieniami. Kieruje take oglne
wywoania metod onCreate i onPrepare do okrelonych okien dialogowych poprzez mapowanie tych identyfikatorw na obiekty. Klasa ManagedDialogsActivity wykorzystuje klas
DialogRegistry jako magazyn rejestrujcy nowe okna dialogowe (listing 8.10).
Listing 8.10. Klasa DialogRegistry
public class DialogRegistry
{
SparseArray<IDialogProtocol> idsToDialogs
= new SparseArray();
public void registerDialog(IDialogProtocol dialog)
{
idsToDialogs.put(dialog.getDialogId(),dialog);
}
public Dialog create(int id)
{
IDialogProtocol dp = idsToDialogs.get(id);
if (dp == null) return null;
return dp.create();
}
public void prepare(Dialog dialog, int id)
{
IDialogProtocol dp = idsToDialogs.get(id);
if (dp == null)
{
throw new RuntimeException("Identyfikator okna dialogowego jest
niezarejestrowany:" + id);
}
dp.prepare(dialog);
}
}

ManagedDialogsActivity
Klasa ManagedDialogsActivity jest traktowana jako klasa bazowa dla aktywnoci obsugujcych zarzdzane okna dialogowe. Utrzymuje jedno wystpienie klasy DialogRegistry, dziki
czemu na bieco ledzi zarzdzane okna dialogowe identyfikowane przez interfejs Idialog
Protocol. Umoliwia pochodnym aktywnociom rejestrowanie ich okien dialogowych poprzez funkcj registerDialogs(). Jak wida na rysunku 8.3, jest rwnie odpowiedzialna za
przenoszenie semantyk create i prepare do waciwej instancji okna dialogowego poprzez wyszukanie jej w rejestrze okien dialogowych. Klasa ta zapewnia take metod zwrotn dialogFinished
dla kadego okna dialogowego znajdujcego si w rejestrze (listing 8.11).

290 Android 3. Tworzenie aplikacji


Listing 8.11. Klasa ManagedDialogsActivity
public class ManagedDialogsActivity extends Activity
implements IDialogFinishedCallBack
{

// Rejestr dla zarzdzanych okien dialogowych


private DialogRegistry dr = new DialogRegistry();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.registerDialogs();
}
protected void registerDialogs()
{

// Nic nie robi


// Klasy pochodne przesaniaj t metod,
// eby mogy zarejestrowa swoje okna dialogowe
// Przykad:
// registerDialog(this.DIALOG_ALERT_ID_3, gmad);
}
public void registerDialog(IDialogProtocol dialog)
{
this.dr.registerDialog(dialog);
}
@Override
protected Dialog onCreateDialog(int id) {
return this.dr.create(id);
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
this.dr.prepare(dialog, id);
}
public void dialogFinished(ManagedActivityDialog dialog, int buttonId)
{

// Nic nie robi


// Klasy pochodne przesaniaj
}
}

IDialogFinishedCallBack
Interfejs IDialogFinishedCallBack umoliwia klasie ManagedActivityDialog przekazywanie
aktywnoci nadrzdnej informacji, e uytkownik zakoczy prac z oknem dialogowym i mona
wywoa wobec okna dialogowego odpowiednie metody, eby odczyta parametry. Zazwyczaj
klasa ManagedDialogsActivity implementuje ten interfejs i zachowuje si jak klasa nadrzdna
wobec klasy ManagedActivityDialog (listing 8.12).

Rozdzia 8 Praca z oknami dialogowymi

291

Listing 8.12. Interfejs IDialogFinishedCallBack


public interface IDialogFinishedCallBack
{
public static int OK_BUTTON = -1;
public static int CANCEL_BUTTON = -2;
public void dialogFinished(ManagedActivityDialog dialog, int buttonId);
}

GenericManagedAlertDialog
Klasa GenericManagedAlertDialog jest implementacj okna alertu. Rozszerza ona klas ManagedActivityDialog. Odpowiada za utworzenie rzeczywistego okna alertu za pomoc konstruktora okien alertw. Przenosi rwnie wszystkie potrzebne jej informacje w formie zmiennych lokalnych. Poniewa klasa GenericManagedAlertDialog implementuje proste okno alertu,
nie wpywa w aden sposb na metod onClickHook. Podczas korzystania z tej klasy naley
przede wszystkim zwrci uwag, e przechowuje ona wszystkie powizane z ni informacje
w jednym miejscu (listing 8.13). Oznacza to, e gwny kod aktywnoci pozostaje sterylnie czysty.
Listing 8.13. Klasa GenericManagedAlertDialog
public class GenericManagedAlertDialog extends ManagedActivityDialog
{
private String alertMessage = null;
private Context ctx = null;
public GenericManagedAlertDialog(ManagedDialogsActivity inActivity,
int dialogId,
String initialMessage)
{
super(inActivity,dialogId);
alertMessage = initialMessage;
ctx = inActivity;
}
public Dialog create()
{
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Okno alertu");
builder.setMessage(alertMessage);
builder.setPositiveButton("OK", this );
AlertDialog ad = builder.create();
return ad;
}
public void prepare(Dialog dialog)
{
AlertDialog ad = (AlertDialog)dialog;
ad.setMessage(alertMessage);
}
public void setAlertMessage(String inAlertMessage)
{
alertMessage = inAlertMessage;
}
public void onClickHook(int buttonId)
{

292 Android 3. Tworzenie aplikacji


// nic nie robi
// adnych zmiennych lokalnych do skonfigurowania
}
}

GenericPromptDialog
Klasa GenericPromptDialog przechowuje wszystkie dane niezbdne dla okna dialogowego
zachty poprzez rozszerzenie klasy ManagedActivityDialog oraz dostarczenie niezbdnych
metod create i prepare (listing 8.14). Zachowuje ona take tekst wpisany przez uytkownika
w oknie dialogowym zachty jako zmienn lokaln, aby nadrzdna aktywno moga go odczyta dziki metodzie dialogFinished.
Listing 8.14. Klasa GenericPromptDialog
public class GenericPromptDialog extends ManagedActivityDialog
{
private String mPromptMessage = null;
private View promptView = null;
String promptValue = null;
private Context ctx = null;
public GenericPromptDialog(ManagedDialogsActivity inActivity,
int dialogId,
String promptMessage)
{
super(inActivity,dialogId);
mPromptMessage = promptMessage;
ctx = inActivity;
}
public Dialog create()
{
LayoutInflater li = LayoutInflater.from(ctx);
promptView = li.inflate(R.layout.promptdialog, null);
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("zachta");
builder.setView(promptView);
builder.setPositiveButton("OK", this);
builder.setNegativeButton("Anuluj", this);
AlertDialog ad = builder.create();
return ad;
}
public void prepare(Dialog dialog)
{

// na razie puste
}
public void onClickHook(int buttonId)
{
if (buttonId == DialogInterface.BUTTON1)
{

// przycisk OK
String promptValue = getEnteredText();
}
}

Rozdzia 8 Praca z oknami dialogowymi

293

private String getEnteredText()


{
EditText et =
(EditText)
promptView.findViewById(R.id.editText_prompt);
String enteredText = et.getText().toString();
Log.d("xx",enteredText);
return enteredText;
}
}

Zaprezentowana tu struktura musi zosta nieco zmodyfikowana w przypadku przebudowy


aktywnoci zalenej od konfiguracji urzdzenia. Gwna zmiana polega na odtworzeniu obiektw okien dialogowych za pomoc metod zapisywania oraz odczytywania instancji. Poniewa
omawiane typy okien dialogowych zostan zastpione przez okna dialogowe oparte na fragmentach (omwione w rozdziale 29.), postanowilimy nie wprowadza w kodzie modyfikacji
wymaganych do przechowywania okien dialogowych w zalenoci od konfiguracji urzdzenia.

Praca z klas Toast


Rozpoczlimy ten rozdzia od stwierdzenia, e okna alertu s czsto stosowane w procesie debugowania kodu JavaScript na bdnie dziaajcych stronach. Jeeli musimy wprowadzi podobne rozwizanie, powodujce sporadyczne wywietlanie informacji o bdach, moemy w tym
celu wykorzysta obiekt Toast.
Obiekt Toast przypomina okno alertu, ktre wywietla informacj przez okrelony czas, po czym
znika. Mona wic powiedzie, e mamy tu do czynienia z chwilowo wystpujcym oknem alertu.
Na listingu 8.15 zaprezentowalimy przykad wywietlenia komunikatu za pomoc obiektu
Toast.
Listing 8.15. Zastosowanie klasy Toast w procesie debugowania
//Tworzy funkcj opakowujc komunikat w obiekt klasy Toast
//Wywietla obiekt Toast
public void reportToast(String message)
{
String s = tag + ":" + message;
Toast mToast = Toast.makeText(activity, s, Toast.LENGTH_SHORT);
mToast.show();
Log.d(tag,message);
}

//Moemy w razie potrzeby przywoywa wielokrotnie


//powysz funkcj, tak jak wida poniej
private void testToast()
{
reportToast("Komunikat1");
reportToast("Komunikat2");
reportToast("Komunikat3");
}

294 Android 3. Tworzenie aplikacji


Widoczna na listingu 8.14 metoda makeText() moe pobiera nie tylko obiekt aktywnoci,
ale nawet obiekt kontekstu, na przykad przekazywany odbiorcy komunikatw lub usudze.
W ten sposb zastosowanie obiektu Toast wykracza poza granice aktywnoci.

Odnoniki

http://developer.android.com/guide/topics/ui/dialogs.html znakomita dokumentacja


zestawu Android SDK stanowica wprowadzenie do pracy z oknami dialogowymi
w Androidzie. Znajdziemy tu wyjanienie sposobu stosowania zarzdzanych okien
dialogowych oraz rnorodne przykady dostpnych okien dialogowych.
http://developer.android.com/reference/android/content/DialogInterface.html
lista staych zdefiniowanych dla dialogw.
http://developer.android.com/reference/android/app/Dialog.html omwiony zbir
metod dostpnych w obiekcie klasy Dialog.
http://developer.android.com/reference/android/app/AlertDialog.Builder.html
dokumentacja interfejsw API dotyczca klasy AlertDialog.
http://developer.android.com/reference/android/app/ProgressDialog.html
dokumentacja dotyczca klasy ProgressDialog.
http://developer.android.com/reference/android/app/DatePickerDialog.html
dokumentacja dotyczca klasy DatePicker.
http://developer.android.com/reference/android/app/TimePickerDialog.html
dokumentacja dotyczca klasy TimePicker.
http://developer.android.com/resources/tutorials/views/hello-datepicker.html
samouczek pozwalajcy na zrozumienie dziaania klasy DatePicker.
http://developer.android.com/resources/tutorials/views/hello-timepicker.html
samouczek uatwiajcy prac z klas TimePicker.
ftp://ftp.helion.pl/przyklady/and3ta.zip z tego adresu moemy pobra testowy projekt,
utworzony na podstawie niniejszego rozdziau. Waciwy plik znajdziesz w katalogu
o nazwie ProAndroid3_R08_OknaDialogowe. Znajdziemy tu rwnie przykadowe
aplikacje korzystajce z okien dialogowych TimePicker i DatePicker.

Podsumowanie
W tym rozdziale zwrcilimy uwag na nowego rodzaju wyzwania zwizane z korzystaniem
z okien dialogowych w Androidzie. Pokazalimy skutki uywania asynchronicznych okien dialogowych oraz zaprezentowalimy, w jaki sposb mona sobie uatwi korzystanie z zarzdzanych okien dialogowych. Dodatkowo w rozdziale 29. omwilimy dziaanie okien dialogowych
opartych na fragmentach, elementach wprowadzonych w wersji 3.0 Androida. Poniewa interfejs
API dialogw jest wprowadzany rwnie do starszych wersji Androida, rozpoczcie uywania
okien dialogowych tego typu jest dobrym pomysem.

R OZDZIA

9
Praca z preferencjami
i zachowywanie stanw

W Androidzie podobnie jak w przypadku wielu innych rodowisk SDK


istnieje obsuga preferencji. System moe ledzi zarwno preferencje uytkownika
aplikacji, jak i preferencje samej aplikacji. Dobrym przykadem jest aplikacja
Microsoft Outlook jej uytkownik moe okreli preferencje wywietlania wiadomoci e-mail w okrelony sposb, ale i sama aplikacja posiada pewne domylne
ustawienia, ktre mog by konfigurowane przez poszczeglnych uytkownikw.
Istnieje jednak rnica midzy aplikacj tak jak Microsoft Outlook a aplikacj
Androida nawet jeli Android teoretycznie ledzi preferencje uytkownikw
i aplikacji, to nie wprowadza pomidzy nimi rozrnienia. Wynika to z faktu, e
zazwyczaj aplikacje Androida dziaaj w urzdzeniu, ktre nie jest wspdzielone
przez kilku uytkownikw; ludzie nie wspuytkuj telefonw komrkowych.
Zatem w Androidzie wprowadzono termin preferencji aplikacji, odnoszcy si
zarwno do preferencji uytkownika, jak i do domylnych preferencji aplikacji.
Osoby widzce po raz pierwszy obsug preferencji w Androidzie s zazwyczaj
bardzo pozytywnie zaskoczone. Android zapewnia wydajn i elastyczn struktur
obsugi preferencji. Dostpne s proste interfejsy API, pozwalajce na ukrywanie
i przechowywanie preferencji, a take przygotowane interfejsy uytkownika, za
pomoc ktrych uytkownik moe zmienia ustawienia preferencji. Dziki ogromowi potencjau tkwicego w strukturze preferencji Androida moemy j rwnie
wykorzystywa do bardziej oglnego zadania przechowywania stanu aplikacji,
na przykad w taki sposb, aby aplikacja po zamkniciu i ponownym uruchomieniu otwieraa si dokadnie w punkcie przerwania pracy przez uytkownika.
Wszystkie wymienione funkcje omwimy w dalszej czci rozdziau.

296 Android 3. Tworzenie aplikacji

Badanie struktury preferencji


Zanim zajmiemy si omwieniem struktury preferencji w Androidzie, ustalmy scenariusz,
w ktrym wymagane bdzie wykorzystanie preferencji, a nastpnie zastanwmy si, jak bymy si zabrali za ich implementacj. Zamy, e piszemy aplikacj posiadajc funkcj wyszukiwania rozkadu lotw. Przypumy jeszcze, e domylnie aplikacja wyszukuje loty wedug
kryterium ceny biletu, ale uytkownik moe wprowadzi preferencj sortowania wynikw
pod wzgldem liczby przesiadek lub pod wzgldem konkretnej linii lotniczej. Jak moemy
tego dokona?

Klasa ListPreference
Oczywicie najpierw musimy dostarczy interfejs UI, umoliwiajcy przegldanie listy dostpnych opcji. Lista taka bdzie zawieraa przyciski opcji, a domylny (lub biecy) wybr bdzie ju zaznaczony. Rozwizanie tego problemu dotyczcego struktury preferencji w Androidzie
wymaga bardzo niewielkiego nakadu pracy. Najpierw utworzymy plik XML, w ktrym zostan
opisane preferencje, a nastpnie wykorzystamy gotow klas aktywnoci, ktra moe wywietla
i przechowywa preferencje. Szczegy zostay ukazane na listingu 9.1.
Na kocu rozdziau umiecilimy odnonik, za pomoc ktrego mona pobiera
projekty utworzone specjalnie dla tego rozdziau. Projekty te mona importowa
bezporednio do rodowiska Eclipse.
Listing 9.1. Plik XML preferencji lotw i powizana z nim klasa aktywnoci
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/xml/flightoptions.xml -->


<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="flight_option_preference"
android:title="@string/prefTitle"
android:summary="@string/prefSummary">
<ListPreference
android:key="@string/selected_flight_sort_option"
android:title="@string/listTitle"
android:summary="@string/listSummary"
android:entries="@array/flight_sort_options"
android:entryValues="@array/flight_sort_options_values"
android:dialogTitle="@string/dialogTitle"
android:defaultValue="@string/flight_sort_option_default_value" />
</PreferenceScreen>

package com.androidbook.preferences.sample;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class FlightPreferenceActivity extends PreferenceActivity
{

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

297

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.flightoptions);
}
}

Listing 9.1 zawiera fragment kodu XML, ktry umoliwia ustawienie preferencji lotw. Na
listingu tym znalaza si rwnie klasa aktywnoci, wczytujca plik XML preferencji. Zacznijmy
od kodu XML. Android posiada pen struktur obsugi preferencji. Oznacza to, e dziki tej
strukturze moemy definiowa wasne preferencje, wywietla je uytkownikowi i zapamitywa
jego decyzje w magazynie danych. Preferencje definiujemy w katalogu /res/xml. Aby wywietli
uytkownikowi preferencje, tworzymy klas aktywnoci, ktra rozszerza predefiniowan klas
Androida android.preference.PreferenceActivity, i dodajemy zasb do zbioru zasobw
aktywnoci za pomoc metody addPreferencesFromResource(). Wspomniana wczeniej struktura zapewnia obsug pozostaych funkcjonalnoci (zapisywanie i przechowywanie preferencji).
W naszym scenariuszu z rozkadami lotw tworzymy plik flightoptions.xml i umieszczamy go
w /res/xml/flightoptions.xml. Generujemy nastpnie klas aktywnoci FlightPreference
Activity, rozszerzajc klas android.preference.PreferenceActivity. W dalszej
kolejnoci wywoujemy metod addPreferencesFromResource() i przekazujemy jej zasb
R.xml.flightoptions. Zauwamy, e kod XML zasobw ustawie wskazuje kilka zasobw
typu string. Aby kompilacja zostaa przeprowadzona pomylnie, musimy doda do projektu
kilka cigw znakw. Wkrtce pokaemy, jak tego dokona. Na razie przyjrzyjmy si utworzonemu na listingu 9.1 interfejsowi uytkownika (rysunek 9.1).

Rysunek 9.1. Interfejs UI preferencji lotw

Na rysunku 9.1 pokazano dwa widoki. Widok z lewej strony jest nazywany ekranem preferencji,
a interfejs z prawej strony nosi nazw listy preferencji. Po klikniciu pola Opcje lotu pojawia si
widok Wybierz opcje lotu w formie okna dialogowego modalnego, zawierajcego przycisk opcji
dla kadego ustawienia. Uytkownik wybiera opcj, ktra natychmiast zostaje zapisana, a widok
zostaje zamknity. Po powrocie do ekranu opcji widok odzwierciedla dokonany wybr.
Kod na listingu 9.1 definiuje klas PreferenceScreen, a nastpnie tworzy klas podrzdn
ListPreference. Klasa PreferenceScreen posiada trzy waciwoci: key, title i summary.

298 Android 3. Tworzenie aplikacji


Waciwo key jest cigiem znakw, za pomoc ktrego mona si odnosi programistycznie
do elementu (podobnie jak w przypadku waciwoci android:id); title definiuje nazw ekranu (Opcje lotu); a dziki waciwoci summary opisujemy przeznaczenie ekranu, umieszczone
mniejsz czcionk pod nazw ekranu (w naszym przypadku jest to Ustaw opcje wyszukiwania).
Dla listy preferencji definiujemy waciwoci key, title i summary, a take atrybuty entries,
entryValues, dialogTitle i defaultValue. Podsumowalimy te atrybuty w tabeli 9.1.
Tabela 9.1. Niektre atrybuty klasy android.preference.ListPreference
Atrybut

Opis

android:key

Nazwa lub klucz opcji (na przykad selected_flight_sort_option).

android:title

Tytu opcji.

android:summary

Krtki opis opcji.

android:entries

Nazwy elementw listy, ktre mog by wybierane w ramach opcji.

android:entryValues

Definiuje klucz lub warto kadego elementu. Kady element posiada


tekst i warto. Tekst jest definiowany w atrybucie entries, a wartoci
w entryValues.

android:dialogTitle

Nazwa okna dialogowego atrybut jest uywany w przypadku


przedstawiania widoku w postaci okna dialogowego modalnego.

android:defaultValue

Domylna warto opcji na licie elementw.

Aby nasz przykadowy kod zadziaa, musimy doda lub zmodyfikowa pliki, tak jak pokazano
na listingu 9.2.
Listing 9.2. Konfigurowanie reszty projektu w naszym przykadzie
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/values/arrays.xml -->


<resources>
<string-array name="flight_sort_options">
<item>Cakowity koszt</item>
<item>Liczba przesiadek</item>
<item>Linia lotnicza</item>
</string-array>
<string-array name="flight_sort_options_values">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources>
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/values/strings.xml -->


<resources>
<string name="app_name">Demonstracja preferencji</string>
<string name="prefTitle">Moje preferencje</string>
<string name="prefSummary">Ustaw preferencje opcji lotu</string>
<string name="flight_sort_option_default_value">1</string>
<string name="dialogTitle">Wybierz opcje lotu</string>
<string name="listSummary">Ustaw opcje wyszukiwania</string>

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

299

<string name="listTitle">Opcje lotu</string>


<string name="selected_flight_sort_option">selected_flight_sort_option</string>
<string name="menu_prefs_title">Ustawienia</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/menu/mainmenu.xml -->


<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_prefs"
android:title="@string/menu_prefs_title"
/>
</menu>

<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:text="" android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>

// Jest to plik MainActivity.java


import
import
import
import
import
import
import
import

android.app.Activity;
android.content.Intent;
android.content.SharedPreferences;
android.os.Bundle;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.widget.TextView;

public class MainActivity extends Activity {


private TextView tv = null;
private Resources resources;

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
resources = this.getResources();
tv = (TextView)findViewById(R.id.text1);
setOptionText();

300 Android 3. Tworzenie aplikacji


}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.mainmenu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected (MenuItem item)
{
if (item.getItemId() == R.id.menu_prefs)
{

// Uruchamia j na ekranie preferencji.


Intent intent = new Intent()
.setClass(this,
com.androidbook.preferences.sample.FlightPreferenceActivity.class);
this.startActivityForResult(intent, 0);
}
return true;
}
@Override
public void onActivityResult(int reqCode, int resCode, Intent data)
{
super.onActivityResult(reqCode, resCode, data);
setOptionText();
}
private void setOptionText()
{
SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(this);

// Jest to alternatywny sposb uzyskania dostpu do wspdzielonych zasobw:


// SharedPreferences prefs = getSharedPreferences(
// "com.androidbook.preferences.sample_preferences", 0);
String option = prefs.getString(
resources.getString(R.string.selected_flight_sort_option),
resources.getString(R.string.flight_sort_option_default_value));
String[] optionText = resources.getStringArray(R.array.flight_sort_options);
tv.setText("warto opcji wynosi " + option + " (" +
optionText[Integer.parseInt(option)] + ")");
}
}
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik AndroidManifest.xml -->


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.preferences.sample"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MainActivity"

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

301

android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".FlightPreferenceActivity"
android:label="@string/prefTitle">
<intent-filter>
<action android:name=
"com.androidbook.preferences.sample.intent.action.FlightPreferences" />
<category android:name="android.intent.category.PREFERENCE" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="4" />
</manifest>

Po wprowadzeniu zmian i uruchomieniu aplikacji ujrzymy prosty komunikat tekstowy Warto


opcji wynosi 1 (Liczba przesiadek). Kliknijmy przycisk Menu, a nastpnie Settings, aby przej do
aktywnoci PreferenceActivity. Po zakoczeniu wystarczy klikn strzak cofania, aby natychmiast ujrze tekst informujcy o wprowadzonych zmianach.
Pierwszym dodanym plikiem jest /res/values/arrays.xml. W pliku tym znajduj si dwie tablice
cigw znakw, ktre s nam potrzebne do wprowadzenia moliwoci wyboru opcji. Pierwsza
tablica przechowuje wywietlany tekst, w drugiej natomiast s umieszczone wartoci, ktre
otrzymamy podczas wywoania metody, oraz warto przechowywana w pliku XML preferencji.
W celach demonstracyjnych wprowadzilimy wartoci indeksw tablic 0, 1 i 2 dla obiektu
flight_sort_options_values. Moemy wprowadzi kad warto usprawniajc dziaanie
aplikacji. Gdyby nasza opcja bya natury numerycznej (na przykad pocztkowa warto
odliczania w liczniku), moglibymy wykorzysta wartoci 60, 120, 300 i tak dalej. Wartoci
nie musz by numeryczne, dopki maj sens dla projektanta; uytkownik ich nie widzi,
chyba e zostan wyeksponowane. Widoczny jest jedynie tekst zawarty w pierwszej tablicy
flight_sort_options.
Jak ju stwierdzilimy wczeniej, struktura Androida zapewnia take przechowywanie preferencji.
Na przykad po wybraniu przez uytkownika opcji sortowania Android przechowuje wybr
w pliku XML, umiejscowionym w katalogu aplikacji /data (rysunek 9.2).

Rysunek 9.2. cieka do zachowanych preferencji aplikacji

302 Android 3. Tworzenie aplikacji


Rzeczywista cieka pliku wyglda nastpujco: /data/data/[NAZWA_PAKIETU]/shared_
prefs/[NAZWA_PAKIETU]_preferences.xml. Na listingu 9.3 widoczny jest plik com.androidbook.
preferences.sample_preferences.xml z naszego przykadu.
Listing 9.3. Przykadowe zachowane preferencje
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="selected_flight_sort_option">1</string>
</map>

Widzimy, e w przypadku listy preferencji warto wybranego elementu jest przechowywana za


pomoc atrybutu key listy. Zwrmy rwnie uwag, e przechowywana jest warto elementu
nie tekst. Naley tu zwrci Czytelnikowi uwag: poniewa w pliku XML preferencji jest przechowywana jedynie warto, a nie tekst, to jeeli bdziemy kiedykolwiek uaktualnia aplikacj
i zmienia tekst opcji lub dodawa elementy do tablic cigw znakw, wszystkie wartoci
przechowywane w tym pliku powinny pozosta powizane z odpowiednim tekstem po aktualizacji. Plik XML preferencji jest zachowywany podczas aktualizowania aplikacji. Innymi
sowy, jeeli przed uaktualnieniem aplikacji w tym pliku znajdowaa si warto 1 oznaczajca
Liczba przesiadek, to po aktualizacji powinna ona oznacza dokadnie to samo.
Nastpnym utworzonym przez nas plikiem jest /res/values/strings.xml. Dodalimy kilka cigw
znakw do tytuw, podsumowa i elementw menu. Umieszczono tu dwa cigi znakw, na ktre
szczeglnie warto zwrci uwag. Pierwszy z nich to flight_sort_option_default_value.
W naszym przykadzie przypisalimy warto 1 argumentowi Liczba przesiadek. Zazwyczaj
warto wybra domyln warto dla kadej opcji. Jeeli nie ustawiono wartoci domylnej
i uytkownik nie wybra adnej opcji, metody przeka warto null dla tej opcji. W takim
przypadku kod musi by w stanie przetwarza puste wartoci. Drugim interesujcym nas
cigiem znakw jest selected_flight_sort_option. Gwoli cisoci, uytkownik nie bdzie
widzia tego cigu znakw, nie musimy wic umieszcza go w pliku strings.xml w celu zapewnienia alternatywnego tekstu w innych jzykach. Poniewa jednak ten cig znakw stanowi
klucz uywany przez metody do odczytania wartoci, poprzez utworzenie z niego identyfikatora moemy si upewni w czasie procesu kompilacji, e nie popenilimy adnej literwki
w nazwie klucza.
Trzecim dodanym przez nas plikiem jest /res/menu/mainmenu.xml. Zakadamy, e chcemy
uzyska dostp 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 gwny interfejs uytkownika naszej aplikacji.
Dotychczas omawialimy sposb obsugiwania preferencji za pomoc specjalnej klasy aktywnoci PreferenceActivity. Chcemy jednak, aby uytkownik mg korzysta z preferencji
w gwnej aktywnoci, a nie w PreferenceActivity. Musimy wic uzyska w jaki sposb
dostp do preferencji z poziomu innej aktywnoci. W naszym przykadzie ukadem graficznym jest prosta kontrolka TextView, wywietlajca biec warto preferencji lotu.
Nastpnie umieszczono kod rdowy aktywnoci MainActivity. Jest to podstawowa aktywno, ktra pobiera odniesienie do preferencji i zajmuje si obsug widoku TextView, a nastpnie wywouje metod odczytujc biec warto opcji, dziki czemu moliwe jest wstawienie
jej do widoku. Konfigurujemy menu oraz jego wywoywanie zwrotne. Wewntrz wywoania
zwrotnego menu uruchamiamy obiekt Intent wobec aktywnoci FlightPreferenceActivity.

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

303

Uruchomienie intencji wobec preferencji jest najlepszym sposobem na wywietlenie ekranu


preferencji. Aby j uruchomi, moemy wykorzysta opcje menu lub przycisk. W nastpnych przykadach nie bdziemy powtarza tego kodu, ale mona w nich wykorzystywa t
sam technik, pod warunkiem e wprowadzimy odpowiedni nazw aktywnoci. Po przekazaniu intencji Intent preferencji wywoujemy metod setOptionText() suc do
aktualizacji kontrolki TextView.
Istniej dwa sposoby postpowania z preferencjami:
W powyszym przykadzie przedstawilimy prostszy sposb, polegajcy na
wywoaniu metody PreferenceManager.getDefaultSharedPreferences(this).
Argument this jest kontekstem pozwalajcym na odnalezienie domylnie
wspdzielonych preferencji, natomiast sama metoda wykorzystuje nazw pakietu
tego kontekstu do okrelenia nazwy oraz lokalizacji pliku preferencji. Jest to zreszt
ta sama preferencja co utworzona przez aktywno PreferenceActivity, poniewa
posiada t sam nazw pakietu.
Drugim sposobem jest wywoanie metody getSharedPreferences() i przekazanie
argumentw nazwy pliku oraz trybu. Rozwizanie to jest widoczne na listingu 9.2,
jednak ten fragment kodu jest nieaktywny, gdy zosta objty znakami komentarza.
Warto zauway, e wymieniono jedynie podstawowy czon nazwy pliku, pominito
natomiast ciek do niego oraz jego rozszerzenie. Argument trybu pozwala nam na
okrelanie uprawnie do naszego pliku XML. W poprzednim przykadzie argument
trybu by zbdny, poniewa plik XML zosta utworzony wycznie w obrbie aktywnoci
PreferenceActivity, co sprawia, e domylne uprawnienia otrzymuj warto
MODE_PRIVATE (np. zero). Argumenty trybu zostan omwione w podrozdziale
dotyczcym zachowywania stanu.
Nastpnie z poziomu metody setOptionText(), uwzgldniajc odniesienie do preferencji, wywoujemy odpowiednie metody suce do odczytywania wartoci. W naszym przypadku
wywoujemy metod getString(), poniewa z preferencji uzyskujemy warto typu string.
Pierwszym argumentem jest cig znakw, reprezentujcy klucz opcji. Stwierdzilimy wczeniej,
e stosowanie identyfikatorw chroni programist przed popenieniem literwki podczas kompilacji. W miejsce pierwszego argumentu moglibymy rwnie po prostu wstawi cig znakw
selected_flight_sort_option, gdy zaley nam na utworzeniu jak najmniejszej i jak najszybszej aplikacji. Drugi argument suy do okrelenia wartoci domylnej, w przypadku gdy nie
mona jej znale w pliku XML preferencji. Gdy aplikacja zostaje uruchomiona po raz pierwszy,
nie istnieje jeszcze plik preferencji, zatem bez okrelenia wartoci drugiego argumentu zawsze
za pierwszym razem bdzie odsyana pusta warto. Dzieje si tak, nawet jeli zdefiniowano
domyln warto w specyfikacji ListPreference, umieszczonej w pliku flightoptions.xml.
W naszym przykadzie ustanowilimy w pliku domyln warto w pliku XML za pomoc
identyfikatora zasobu, wic kod w metodzie setOptionText() moe posuy do odczytania
tego identyfikatora w celu otrzymania domylnej wartoci. Zwrmy uwag, e gdybymy nie
utworzyli identyfikatora domylnej wartoci, jej odczyt z obiektu ListPreference byby
znacznie utrudniony. Dziki temu, e identyfikator zasobu jest wykorzystywany zarwno
przez plik XML, jak i kod Java, warto domyln moemy zmienia tylko w jednym miejscu
(mamy na myli plik strings.xml).
Poza prezentowaniem wartoci preferencji wywietlamy take jej tre. W naszym przykadzie korzystamy ze skrtu, poniewa uylimy indeksu tablicy dla wartoci w obiekcie
flight_sort_options_values. Dziki prostej konwersji wartoci na typ int wiemy, ktry
cig znakw ma zosta odczytany z argumentu flight_sort_options. Gdybymy uyli dla

304 Android 3. Tworzenie aplikacji


argumentu flight_sort_options_values innego zestawu wartoci, musielibymy okreli
indeks elementu stanowicego nasz preferencj, a nastpnie wykorzysta ten indeks do odczytania tekstu tej preferencji z tablicy flight_sort_options.
Ostatnim plikiem uytym w naszym przykadzie jest AndroidManifest.xml. Teraz w aplikacji
istniej dwie aktywnoci, a zatem potrzebujemy dwch znacznikw aktywnoci. Pierwszy z nich
okrela standardow aktywno kategorii LAUNCHER. Drugi znacznik jest przeznaczony dla
aktywnoci PreferenceActivity, a wic ustanawiamy nazw dziaania zgodnie z konwencj dla intencji oraz kategori PREFERENCE. Prawdopodobnie nie chcemy, aby aktywno
PreferenceActivity pojawiaa si wraz z pozostaymi aplikacjami na ekranie startowym
Androida, dlatego nie wyznaczylimy jej do kategorii LAUNCHER. W przypadku dodawania kolejnych ekranw preferencji naley wprowadza podobne zmiany w pliku AndroidManifest.xml.
Zademonstrowalimy jeden sposb odczytywania domylnej wartoci preferencji za pomoc
kodu. Istnieje alternatywne rozwizanie, nieco bardziej eleganckie. Moglibymy w metodzie
onCreate() wykona nastpujc czynno:
PreferenceManager.setDefaultValues(this, R.xml.flightoptions, false);

Nastpnie mona odczyta warto opcji wewntrz metody setOptionText():


String option = prefs.getString(
resources.getString(R.string.selected_flight_sort_option), null);

W pierwszym wywoaniu wykorzystano plik flightoptions.xml do znalezienia domylnych


wartoci oraz wygenerowania za ich pomoc pliku XML preferencji. Jeeli w pamici obecne
jest ju wystpienie interfejsu SharedPreferences, zostanie ono rwnie zaktualizowane.
Drugie wywoanie odnajdzie teraz warto opcji selected_flight_sort_option, poniewa
najpierw zostay zaadowane domylne wartoci.
Jeeli zajrzymy do folderu shared_prefs po pierwszym uruchomieniu aplikacji, zauwaymy, e
zosta utworzony plik XML preferencji, nawet jeli ekran preferencji nie zosta wywietlony.
Bdzie widoczny rwnie inny plik _has_set_default_values.xml. Aplikacja otrzymuje w ten
sposb informacj, e utworzono i wypeniono domylnymi wartociami plik XML preferencji.
Trzeci argument metody setDefaultValues() false, wskazuje, e domylne wartoci
maj zosta wstawione do pliku XML preferencji tylko wtedy, jeli wczeniej ich tam nie byo.
Jeeli ustawilibymy warto true, plik ten zawsze byby nadpisywany wartociami domylnymi.
Android zapamituje t informacj na cay czas istnienia nowego pliku XML. Jeeli uytkownik
wprowadzi nowe wartoci preferencji przy ustawionej wartoci false tego trzeciego argumentu,
nie zostan one przywrcone do wartoci domylnych podczas kolejnego uruchomienia aplikacji.
Zauwamy jeszcze, e nie musimy wprowadza domylnej wartoci do wywoania metody
getString(), poniewa powinnimy j zawsze uzyskiwa z pliku XML preferencji.
Aby uzyska odniesienie do preferencji z wntrza aktywnoci rozszerzajcej klas

Preference

Activity, moemy dokona tego w nastpujcy sposb:


SharedPreferences prefs = getPreferenceManager().getDefaultSharedPreferences(this);

Pokazalimy, w jaki sposb mona wykorzysta widok ListPreference; teraz przyjrzymy si


innym elementom interfejsu uytkownika, stosowanym w strukturze preferencji w Androidzie.
Przeanalizujmy mianowicie widoki CheckBoxPreference, EditTextPreference i Ringtone
Preference.

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

305

Widok CheckBoxPreference
Pokazalimy, e preferencja ListPreference wywietla list jako element swego interfejsu
uytkownika. W analogiczny sposb preferencja CheckBoxPreference wywietla widet pola
wyboru w postaci skadnika swego interfejsu UI.
Zamy, e w ramach rozbudowania naszej aplikacji wyszukujcej loty chcemy uytkownikowi umoliwi wybranie kolumn przed wywietleniem zestawu wynikw. Preferencja Check
BoxPreference wywietla spis dostpnych kolumn i umoliwia uytkownikowi wybranie
podanych kolumn poprzez zaznaczenie odpowiedniego pola wyboru. Interfejs uytkownika dla tego przykadu jest przedstawiony na rysunku 9.3, a zawarto pliku XML preferencji zostaa umieszczona na listingu 9.4.

Rysunek 9.3. Interfejs UI preferencji pola wyboru


Listing 9.4. Zastosowanie widoku CheckBoxPreference
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/xml/chkbox.xml -->


<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="flight_columns_pref"
android:title="Preferencje wyszukiwania lotu"
android:summary="Wybierz kolumny wywietlane w wynikach wyszukiwania">
<CheckBoxPreference
android:key="show_airline_column_pref"
android:title="Linia lotnicza"
android:summary="Wywietla kolumn Linia lotnicza" />
<CheckBoxPreference
android:key="show_departure_column_pref"
android:title="Odlot"
android:summary="Wywietla kolumn Odlot" />
<CheckBoxPreference
android:key="show_arrival_column_pref"
android:title="Przylot"
android:summary="Wywietla kolumn Przylot" />
<CheckBoxPreference
android:key="show_total_travel_time_column_pref"

306 Android 3. Tworzenie aplikacji


android:title="Cakowity czas podry"
android:summary="Wywietla kolumn Cakowity czas podry" />
<CheckBoxPreference
android:key="show_price_column_pref"
android:title="Cena"
android:summary="Wywietla kolumn Cena" />
</PreferenceScreen>

// CheckBoxPreferenceActivity.java
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class CheckBoxPreferenceActivity extends PreferenceActivity
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.chkbox);
}
}

Na listingu 9.4 zosta ukazany plik XML preferencji, chkbox.xml, oraz prosta klasa aktywnoci wczytujca ten plik za pomoc metody addPreferencesFromResource(). Jak wida, w interfejsie uytkownika znalazo si pi pl wyboru, kade reprezentowane przez wze
CheckBoxPreference w pliku XML. Kade z tych pl posiada rwnie warto key, ktra jak
mona byo si spodziewa jest ostatecznie wykorzystywana do przechowywania stanu
elementu interfejsu uytkownika w trakcie procesu zapisywania wprowadzonych zmian
preferencji. Dziki widokowi CheckBoxPreference zostaje ona zapisana po zmianie stanu
preferencji. Innymi sowy, jeeli uytkownik zaznacza kontrolk preferencji lub usuwa jej
zaznaczenie, jej stan zostaje zapisany. Listing 9.5 przedstawia magazyn danych preferencji
dla naszego przykadu.
Listing 9.5. Magazyn danych dla preferencji w postaci pola wyboru
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<boolean name="show_total_travel_time_column_pref" value="false" />
<boolean name="show_price_column_pref" value="true" />
<boolean name="show_arrival_column_pref" value="false" />
<boolean name="show_airline_column_pref" value="true" />
<boolean name="show_departure_column_pref" value="false" />
</map>

Ponownie wida, e kada preferencja zostaje zachowana poprzez atrybut key. Typem danych
widoku CheckBoxPreference jest boolean, przyjmujcy warto true lub false: warto true
wskazuje zaznaczenie preferencji, warto false ma przeciwne znaczenie. Aby odczyta
warto z pola wyboru preferencji, musielibymy uzyska dostp do wspdzielonej preferencji,
a nastpnie wywoa metod getBoolean() i przekaza jej atrybut key preferencji:
boolean option = prefs.getBoolean("show_price_column_pref", false);

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

307

Inn poyteczn funkcj widoku CheckBoxPreference jest moliwo zmiany treci podsumowania w zalenoci od tego, czy pole wyboru jest zaznaczone, czy nie. W tym przypadku
stosowane s atrybuty jzyka XML summaryOn i summaryOff. Przyjrzyjmy si teraz kontrolce
EditTextPreference.

Widok EditTextPreference
W Androidzie jest rwnie dostpna preferencja umoliwiajca pisanie tekstu, zwana EditText
Uytkownik, zamiast zaznaczy wybran opcj, moe wpisa wasn tre. Zamy, e posiadamy aplikacj suc do generowania kodu Java. Jednym z ustawie preferencji tej aplikacji moe by nazwa domylnego pakietu, wyznaczonego dla generowanych
klas. Chcemy wywietli uytkownikowi pole tekstowe i pozwoli mu na wpisanie nazwy
takiego pakietu. Na rysunku 9.4 zosta zaprezentowany interfejs uytkownika takiej aplikacji, za na listingu 9.6 pokazalimy kod XML.

Preference.

Rysunek 9.4. Zastosowanie widoku EditTextPreference


Listing 9.6. Przykadowy widok EditTextPreference
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/xml/packagepref.xml -->


<PreferenceScreen
xmlns:android=http://schemas.android.com/apk/res/android
android:key="package_name_screen"
android:title="Nazwa pakietu"
android:summary="Wprowad nazw pakietu">
<EditTextPreference
android:key="package_name_preference"
android:title="Wprowad nazw pakietu"
android:summary="Ustanawia nazw pakietu dla wygenerowanego kodu"
android:dialogTitle="Nazwa pakietu" />
</PreferenceScreen>

// EditTextPreferenceActivity.java

308 Android 3. Tworzenie aplikacji


import android.os.Bundle;
import android.preference.PreferenceActivity;
public class EditTextPreferenceActivity extends PreferenceActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.packagepref);
}
}

Widzimy, e na listingu 9.6 zdefiniowano klas PreferenceScreen, zawierajc widok EditText


Preference. W wygenerowanym interfejsie uytkownika klasa PreferenceScreen jest widoczna po lewej stronie, a widok EditTextPreference po prawej (rysunek 9.4). Po klikniciu
przez uytkownika opcji Wprowad nazw pakietu pojawi si okno dialogowe, w ktrym
mona wpisa odpowiedni nazw. Kliknicie przycisku OK spowoduje zapisanie ustawienia
w magazynie preferencji.
Podobnie jak ma to miejsce w przypadku innych preferencji, moemy odczyta widok
z poziomu klasy aktywnoci za pomoc atrybutu key tej preferencji. Po
wczytaniu preferencji EditTextPreference moemy konfigurowa widok EditText poprzez
wywoanie metody getEditText() jeli na przykad chcemy wprowadzi sprawdzanie
wartoci wpisanej przez uytkownika, jej przetwarzanie wstpne lub przetwarzanie kocowe.
Aby odczyta tre widoku EditTextPreference, wykorzystujemy po prostu metod getText().
EditTextPreference

Przyjrzyjmy si teraz widokowi RingtonePreference struktury preferencji.

Widok RingtonePreference
Widok RingtonePreference suy do obsugi dzwonkw. Jest on wstawiany do aplikacji,
w przypadku gdy uytkownik ma otrzyma moliwo wyboru dzwonka w formie preferencji.
Rysunek 9.5 ilustruje przykadowy interfejs UI widoku RingtonePreference, a na listingu 9.7
zosta wstawiony jego kod XML.

Rysunek 9.5. Przykadowy interfejs UI widoku RingtonePreference

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

309

Listing 9.7. Definiowanie preferencji RingtonePreference


<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/xml/ringtone.xml -->


<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="ringtone_option_preference"
android:title="Moje preferencje"
android:summary="Wybierz preferencje dzwonka">
<RingtonePreference
android:key="ring_tone_pref"
android:title="Wybierz preferencj dzwonka"
android:showSilent="true"
android:ringtoneType="alarm"
android:summary="Ustanawia dzwonek" />
</PreferenceScreen>

// RingtonePreferenceActivity.java
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class RingtonePreferenceActivity extends PreferenceActivity
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.ringtone);
}
}

Kiedy uytkownik kliknie przycisk Wybierz preferencj dzwonka, zostanie wywietlony widok
ListPreference zawierajcy dzwonki zapisane w urzdzeniu (rysunek 9.5). W widoku
tym mona wybra dzwonek, a nastpnie klikn przycisk OK lub Anuluj. Po wybraniu
opcji OK dane zostan zapisane w magazynie preferencji. Zwrmy uwag, e w przypadku
dzwonkw wartoci przechowywan w magazynie preferencji jest identyfikator URI danego
dzwonka chyba e zostanie wybrana preferencja Cichy, ktra w miejsce przechowywanej
wartoci wstawia pusty cig znakw. Przykadowy identyfikator URI wyglda nastpujco:
<string name="ring_tone_pref">content://media/internal/audio/media/26</string>

Jeeli emulator jest skpo wyposaony w dzwonki, mona doda je samemu. W tym celu
wystarczy skopiowa pliki muzyczne na kart SD, nastpnie uruchomi aplikacj Music
Player, zaznaczy plik muzyczny, po czym klikn przycisk Menu i wybra opcj Use
as ringtone. Mechanizm kopiowania plikw na kart SD zosta omwiony w rozdziale 19.

Przedstawiony na listingu 9.7 widok RingtonePreference korzysta z takiego samego algorytmu


jak pozostae preferencje. Rnica polega na tym, e ustawiamy kilka rnych atrybutw, w tym
showSilent i ringtoneType. Atrybut showSilent suy do wstawienia wyciszonego dzwonka na
list, a ringtoneType do ograniczania typw wywietlanych dzwonkw. Dla tej waciwoci
dostpne s wartoci ringtone, notification, alarm i all.

310 Android 3. Tworzenie aplikacji

Organizowanie preferencji
Struktura preferencji pozwala na organizowanie preferencji w kategorie. Jeli nasza aplikacja
pozwala na ustawienie wielu preferencji, moemy zbudowa widok przedstawiajcy ich wysokopoziomowe kategorie. Uytkownicy mogliby wtedy wywietla kad z tych kategorii w celu przegldania preferencji umieszczonych w danej grupie i zarzdzania nimi.
Istniej dwa sposoby implementacji takiej struktury. Moemy albo wprowadzi zagniedone
elementy PreferenceScreen wewntrz nadrzdnej klasy PreferenceScreen, albo w podobny
sposb wykorzysta elementy PreferenceCategory. Pierwszy z wymienionych sposobw, polegajcy na grupowaniu preferencji za pomoc zagniedonych elementw PreferenceScreen,
zosta zaprezentowany na rysunku 9.6 i listingu 9.8.

Rysunek 9.6. Tworzenie grup preferencji poprzez zagniedenie elementw PreferenceScreen


Listing 9.8. Zagniedanie elementw PreferenceScreen umoliwiajce organizowanie preferencji
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="using_categories_in_root_screen"
android:title="Kategorie"
android:summary="Stosowanie kategorii preferencji">
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="meats_screen"
android:title="Miso"
android:summary="Preferencje odnoszce si do misa">
<CheckBoxPreference
android:key="fish_selection_pref"
android:title="Ryba"
android:summary="Ryby s bardzo zdrowe" />
<CheckBoxPreference
android:key="chicken_selection_pref"
android:title="Kurczak"
android:summary="Popularny gatunek drobiu" />
<CheckBoxPreference
android:key="lamb_selection_pref"
android:title="Jagni"
android:summary="Jagni jest mod owc" />
</PreferenceScreen>
<PreferenceScreen

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

311

xmlns:android="http://schemas.android.com/apk/res/android"
android:key="vegi_screen"
android:title="Warzywa"
android:summary=" Preferencje odnoszce si do warzyw">
<CheckBoxPreference
android:key="tomato_selection_pref"
android:title="Pomidor "
android:summary="W rzeczywistoci jest to owoc" />
<CheckBoxPreference
android:key="potato_selection_pref"
android:title="Ziemniak"
android:summary="Moje ulubione warzywo" />
</PreferenceScreen>
</PreferenceScreen>

Widok z lewej strony na rysunku 9.6 prezentuje dwa ekrany preferencji, jeden zatytuowany Miso,
a drugi noszcy nazw Warzywa. Kliknicie danej grupy spowoduje wywietlenie jej elementw.
Na listingu 9.8 pokazalimy, w jaki sposb tworzy si zagniedone ekrany.
Grupy pokazane na rysunku 9.6 zostay utworzone poprzez zagniedenie elementw
wewntrz nadrzdnej klasy PreferenceScreen. Organizowanie w ten
sposb preferencji jest korzystne, w przypadku gdy istnieje wiele preferencji, a nie chcemy,
aby uytkownicy musieli przewija ekran w celu znalezienia jednej z nich. Jeeli w aplikacji
nie ma zbyt wielu preferencji, a mimo to naley utworzy ich wysokopoziomowe kategorie,
mona zastosowa drug metod, zwizan z obiektem PreferenceCategory. Szczegy
zostay zaprezentowane na rysunku 9.7 i listingu 9.9.
PreferenceScreen

Rysunek 9.7. Zastosowanie obiektu PreferenceCategory do organizowania preferencji


Listing 9.9. Tworzenie kategorii preferencji
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="using_categories_in_root_screen"
android:title="Kategorie"

312 Android 3. Tworzenie aplikacji


android:summary="Zastosowanie kategorii preferencji">
<PreferenceCategory
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="meats_category"
android:title="Miso"
android:summary="Preferencje odnoszce si do misa">
<CheckBoxPreference
android:key="fish_selection_pref"
android:title="Ryba"
android:summary="Ryby s bardzo zdrowe" />
<CheckBoxPreference
android:key="chicken_selection_pref"
android:title="Kurczak"
android:summary="Popularny gatunek drobiu" />
<CheckBoxPreference
android:key="lamb_selection_pref"
android:title="Jagni"
android:summary="Jagni jest mod owc" />
</PreferenceCategory>
<PreferenceCategory
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="vegi_category"
android:title="Warzywa"
android:summary=" Preferencje odnoszce si do warzyw">
<CheckBoxPreference
android:key="tomato_selection_pref"
android:title="Pomidor "
android:summary="W rzeczywistoci jest to owoc" />
<CheckBoxPreference
android:key="potato_selection_pref"
android:title="Ziemniak"
android:summary="Moje ulubione warzywo" />
</PreferenceCategory>
</PreferenceScreen>

Na rysunku 9.7 pokazano grupy wykorzystane rwnie w poprzednim przykadzie. Teraz jednak
grupy te s zorganizowane w kategorie preferencji. Jedyna rnica pomidzy kodem XML z listingu 9.9 a kodem XML z listingu 9.8 polega na utworzeniu obiektu PreferenceCategory
dla zagniedonych ekranw zamiast zagniedenia elementw PreferenceScreen.

Programowe zarzdzanie preferencjami


Nie trzeba tumaczy, e moe zaistnie potrzeba uzyskania dostpu do rzeczywistych kontrolek
preferencji w sposb programowy. Przykadowo naley zapewni wprowadzanie parametrw
entry i entryValue do klasy ListPreference w trakcie dziaania aplikacji. Moemy definiowa
kontrolki preferencji i uzyskiwa do nich dostp w podobny sposb jak w przypadku plikw ukadu graficznego i aktywnoci. Aby na przykad uzyska dostp do listy preferencji zdefiniowanej na
listingu 9.1, musielibymy wywoa metod findPreference() aktywnoci PreferenceActivity,

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

313

przekazujc warto waciwoci key (zwrmy uwag na podobiestwo do metody find


ViewById()). Nastpnie mona by odda kontrol obiektowi ListPreference i zaj si prac
z kontrolk. Jeli na przykad chcemy ustanowi wpisy widoku ListPreference, wywoujemy
metod setEntries() i tak dalej. Na listingu 9.10 pokazujemy przykadow implementacj tego
mechanizmu za pomoc kodu konfigurujcego preferencje.
Listing 9.10. Konfigurowanie wartoci widoku ListPreference w sposb programistyczny
public class FlightPreferenceActivity extends PreferenceActivity
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.flightoptions);
ListPreference listpref = (ListPreference) findPreference(
"selected_flight_sort_option");
listpref.setEntryValues(new String[] {"0","1","2"});
listpref.setEntries(new String[] {"Jedzenie", "Poczekalnia",
"Program Frequent Flyer"});
}
}

Zapisywanie stanu za pomoc preferencji


Preferencje w znakomity sposb umoliwiaj uytkownikom dostosowywanie aplikacji do swoich potrzeb, jednak struktura preferencji pozwala jeszcze na inne zastosowania. Jeli aplikacja
ma zapamitywa wartoci danych pomidzy jej wywoaniami, preferencje stanowi jedno ze
stosowanych w tym celu rozwiza. Omawialimy wczeniej rol dostawcw treci w utrzymywaniu danych. Moglibymy rwnie wykorzysta w podobny sposb niestandardowe pliki
przechowywane na karcie SD. Moemy take wprowadzi pliki i kod preferencji.
W klasie Activity znajduje si metoda getPreferences(int mode). W rzeczywistoci wywouje ona po prostu metod getSharedPreferences(), ktrej argumentami s nazwa aktywnoci
oraz tryb uprawnie. W wyniku jej dziaania otrzymujemy plik preferencji przeznaczony
dla danej aktywnoci, w ktrym moemy przechowywa dane o tej aktywnoci pomidzy jej
wywoaniami. Prosty przykad zastosowania w taki sposb preferencji moemy przeledzi
na listingu 9.11.
Listing 9.11. Stosowanie preferencji do przechowywania stanu aktywnoci
final String INITIALIZED = "initialized";
SharedPreferences myPrefs = getPreferences(MODE_PRIVATE);
boolean hasPreferences = myPrefs.getBoolean(INITIALIZED, false);
if(hasPreferences) {
Log.v("Preferences", "Bylysmy juz wczesniej wywolane");

// Odczytuje w razie potrzeby inne wartoci z pliku preferencji


someString = myPrefs.getString("someString", "");
}

314 Android 3. Tworzenie aplikacji


else {
Log.v("Preferences", "Wywolane po raz pierwszy");

// Ustanawia pocztkowe wartoci dla danych,


// ktre znajd si w pliku preferencji
someString = "jaka domylna warto";
}

// Gdy wartoci bd ju gotowe do wypisania


Editor editor = myPrefs.edit();
editor.putBoolean(INITIALIZED, true);
editor.putString(someString, someString);

// W razie potrzeby zapisuje inne wartoci


editor.commit();

Powyszy kod otrzymuje dostp do odniesienia do preferencji nalecych do danej aktywnoci


i sprawdza istnienie dwuwartociowej preferencji, nazwanej initialized. Piszemy preferencja w cudzysowie, poniewa uytkownik nie zobaczy ani nie zmodyfikuje jej wartoci; jest to
jedynie warto, ktr chcemy przechowa w pliku preferencji do nastpnego wywoania aplikacji.
Jeeli otrzymamy t warto, oznacza to, e plik preferencji istnieje, wic nasza aplikacja
musiaa zosta wczeniej wywoana. Moemy wtedy odczyta pozostae wartoci znajdujce
si w tym pliku.
Aby zapisa wartoci w pliku preferencji, musimy najpierw uzyska ich klas Editor. Moemy nastpnie wstawi wartoci do preferencji i zapisa zmiany po zakoczeniu ich wprowadzania. Zwrmy uwag, e poza wzrokiem uytkownika Android zarzdza obiektem
SharedPreferences, ktry jest w istocie wspdzielony (ang. shared). W idealnym przypadku w jednej chwili powinna by aktywna tylko jedna klasa Editor. Niezwykle istotne jest
jednak, aby wywoywa metod commit() w celu cigego aktualizowania interfejsu Shared
Preferences i pliku XML.
W kadej chwili moemy uzyskiwa dostp do wartoci, zapisywa i przypisywa je w pliku preferencji. Wrd moliwych zastosowa s takie, jak zapisywanie wynikw gry lub rejestrowanie
daty ostatniego uruchomienia aplikacji. Moemy rwnie wywoywa metod getShared
Preferences() zawierajc inne nazwy w celu zarzdzania osobnymi zestawami preferencji,
wszystko w zakresie jednej aplikacji, a nawet aktywnoci.
Do tej pory korzystalimy w naszych przykadach z trybu MODE_PRIVATE. Pozostae dostpne
tryby to MODE_WORLD_READABLE oraz MODE_WORLD_WRITEABLE. Za pomoc tych trybw ustanawiamy odpowiednie uprawnienia dostpu w trakcie tworzenia pliku preferencji. Poniewa pliki
preferencji s przechowywane wewntrz katalogu danych aplikacji, a zatem nie s dostpne dla
innych aplikacji, moemy tu zastosowa jedynie tryb MODE_PRIVATE.

Odnoniki
Poniszy odnonik moe si przyda Czytelnikom, ktrzy zechc zapozna si lepiej z omwion w tym rozdziale tematyk:
ftp://ftp.helion.pl/przyklady/and3ta.zip z tego adresu moemy pobra projekty
utworzone z myl o niniejszej ksice. Plik ten zawiera wszystkie przykady omwione
w tym rozdziale, umieszczone w oddzielnych katalogach. Waciwy plik znajdziesz
w katalogu o nazwie ProAndroid3_R09_Preferencje. Dostpny jest tu take plik Czytaj.TXT,
stanowicy dokadn instrukcj importowania projektw do rodowiska Eclipse.

Rozdzia 9 Praca z preferencjami i zachowywanie stanw

315

Podsumowanie
W tym rozdziale omwilimy zarzdzanie preferencjami w Androidzie. Pokazalimy, w jaki
sposb mona korzysta z widokw ListPreference, CheckBoxPreference, EditText
Preference i RingtonePreference. Omwilimy take techniki organizowania preferencji
w grupy oraz programowy sposb modyfikowania preferencji. Na koniec wyjanilimy, w jaki
sposb mona wykorzysta, poprzez przywoania, struktur preferencji w procesie zapisywania
i odczytywania informacji z aktywnoci.

316 Android 3. Tworzenie aplikacji

R OZDZIA

10
Analiza zabezpiecze
i uprawnie

W niniejszym rozdziale zajmiemy si modelem zabezpiecze aplikacji. Kwestie


bezpieczestwa w systemie Android maj fundamentalne znaczenie s brane
pod uwag na wszystkich etapach cyklu ycia aplikacji, poczwszy od rozwaa na
temat polityki czasu projektowania aplikacji, na testowaniu aplikacji w rodowisku
uruchomieniowym skoczywszy. Czytelnicy poznaj architektur zabezpiecze
i dowiedz si, w jaki sposb tworzy bezpieczne aplikacje.
Zapoznajmy si z modelem zabezpiecze w Androidzie.

Model zabezpiecze w Androidzie


W pierwszym podrozdziale przyjrzymy si zabezpieczeniom na poszczeglnych
etapach wdraania oraz uruchamiania aplikacji. Na etapie wdraania aplikacje musz zosta podpisane za pomoc cyfrowego certyfikatu, zanim zostan zainstalowane na urzdzeniu. Na etapie uruchamiania kada aplikacja dziaa wewntrz
oddzielnego procesu, ktry posiada unikalny i stay identyfikator uytkownika
(przydzielony podczas instalacji). Zostaje w ten sposb utworzona granica wok
procesu, uniemoliwiajca uzyskanie bezporedniego dostpu przez jedn aplikacj
do danych innej aplikacji. Ponadto w Androidzie zdefiniowano deklaracyjny model
uprawnie, sucy do ochrony wraliwych funkcji (na przykad listy kontaktw).
Omwimy te zagadnienia w kilku nastpnych podrozdziaach. Zanim jednak do
nich przejdziemy, musimy nakreli pewne pojcia dotyczce zabezpiecze, do
ktrych bdziemy si pniej odnosi.

Przegld poj dotyczcych zabezpiecze


Android wymaga, eby aplikacje byy podpisywane certyfikatami cyfrowymi.
Jedn z zalet takiej polityki jest moliwo zaktualizowania aplikacji jedynie do
wersji opublikowanej przez oryginalnego autora. Na przykad jeeli my autorzy tej

318 Android 3. Tworzenie aplikacji


ksiki opublikujemy aplikacj, Czytelnik nie bdzie mg jej zaktualizowa swoj wasn
wersj (chyba e w jaki sposb uzyska nasz certyfikat). A zatem, co to znaczy, e aplikacja
musi zosta podpisana? I jak wyglda proces jej podpisywania?
Dokonuje si tego za pomoc certyfikatu cyfrowego. Certyfikat cyfrowy jest artefaktem zawierajcym informacje o producencie, takie jak nazwa firmy, adres i tak dalej. Wrd najwaniejszych poj zwizanych z certyfikatem cyfrowym mona wskaza podpis oraz klucz publiczny
i prywatny. Klucz publiczny i prywatny jest rwnie nazywany par kluczy. Zauwamy, e chocia uywamy tu kluczy do podpisywania plikw .apk, s one rwnie wykorzystywane w innych
celach (na przykad w komunikacji szyfrowanej). Certyfikat cyfrowy mona otrzyma od zaufanego wydawcy certyfikatw (ang. certificate authority CA), istnieje take moliwo samodzielnego wygenerowania wasnego certyfikatu za pomoc takiego narzdzia, jak omwiona
w dalszej czci rozdziau aplikacja keytool. Certyfikaty cyfrowe s przechowywane w magazynach kluczy. Magazyn kluczy zawiera list certyfikatw cyfrowych, z ktrych kady posiada
alias, sucy jako odniesienie do tego certyfikatu.
Podpisanie aplikacji w systemie Android wymaga trzech elementw: certyfikatu cyfrowego, pliku
.apk oraz aplikacji, ktra zastosuje podpis cyfrowy z certyfikatu dla pliku .apk. Jak si wkrtce okae,
tak aplikacj moe by bezpatne narzdzie jarsigner, dostpne w zestawie Java Development Kit.
Jest to program wiersza polece, ktry potrafi podpisa plik .jar za pomoc certyfikatu cyfrowego.
Przejdmy teraz do tematu podpisywania pliku .apk za pomoc certyfikatu cyfrowego.

Podpisywanie wdraanych aplikacji


eby zainstalowa aplikacj systemu Android w urzdzeniu, musimy najpierw podpisa plik
pakietu Android (.apk) za pomoc certyfikatu cyfrowego. Jednak certyfikat mona wygenerowa
samodzielnie nie ma koniecznoci zakupu certyfikatu od wydawcy, takiego jak firma VeriSign.
Podpisywanie wdraanej aplikacji obejmuje trzy etapy. Pierwszym krokiem jest wygenerowanie
certyfikatu za pomoc aplikacji keytool (lub podobnego narzdzia). W drugim etapie wykorzystujemy na przykad narzdzie jarsigner do podpisania pliku .apk wygenerowanym certyfikatem. W ostatnim etapie nastpuje przypisanie fragmentw aplikacji do segmentw pamici,
dziki czemu podczas dziaania programu pami jest wykorzystywana efektywniej. Warto
zwrci uwag, e podczas etapu projektowania wtyczka ADT automatycznie obsuguje wszystkie
wymienione czynnoci: podpisuje plik .apk i przydziela pami jeszcze przed wdroeniem aplikacji na emulatorze.

Wygenerowanie samoistnie podpisanego certyfikatu


za pomoc narzdzia keytool
Aplikacja keytool zarzdza baz danych kluczy prywatnych oraz powizanych z nimi certyfikatw X.509 (standard certyfikatw cyfrowych). Jest dostpna w zestawie JDK, dokadniej
w jego katalogu bin. Po wykonaniu omwionych w rozdziale 2. czynnoci modyfikowania
zmiennej rodowiskowej PATH katalog bin zestawu JDK powinien by czci tej zmiennej.
W niniejszym podrozdziale pokaemy, w jaki sposb mona wygenerowa magazyn kluczy
zawierajcy jeden wpis sucy do podpisania pliku .apk. eby utworzy taki wpis, naley
wykona nastpujce czynnoci:
1. Utwrz folder, w ktrym bdzie przechowywany magazyn kluczy, na przykad
c:\android\release\.

Rozdzia 10 Analiza zabezpiecze i uprawnie

319

2. Otwrz okno narzdzi i uruchom narzdzie keytool wraz z parametrami pokazanymi


na listingu 10.1 (w rozdziale 2. wyjanilimy, co mamy na myli, uywajc terminu
okno narzdzi).
Listing 10.1. Generowanie wpisu magazynu kluczy za pomoc narzdzia keytool
keytool -genkey -v -keystore "c:\android\release\release.keystore" -alias
androidbook -storepass paxxword -keypass paxxword -keyalg RSA -validity 14000

Wszystkie argumenty przekazane do narzdzia keytool zostay opisane w tabeli 10.1.


Tabela 10.1. Argumenty przekazane aplikacji keytool
Argument

Opis

genkey

Powoduje, e aplikacja keytool generuje par kluczy: publiczny i prywatny.

Powoduje, e aplikacja keytool wywietla dane wyjciowe w formie opisowej


podczas generowania klucza.

keystore

cieka do bazy danych magazynu kluczy (w naszym przypadku do pliku).


W razie potrzeby plik zostanie utworzony.

alias

Niepowtarzalna nazwa wpisu magazynu kluczy. Alias bdzie zastosowany


jako odniesienie do wpisu.

storepass

Haso magazynu kluczy.

keypass

Haso dostpu do klucza prywatnego.

keyalg

Algorytm.

validity

Okres wanoci.

Aplikacja keytool wywietli monit o podanie hase wymienionych w tabeli 10.1, jeeli nie zostan zdefiniowane w wierszu polece. Jeeli komputer jest wspuytkowany przez wiele osb,
bezpieczniej bdzie nie okrela parametrw storepass i keypass w wierszu polece, lecz
zdefiniowa je, gdy aplikacja keytool tego zada. Polecenie przedstawione na listingu 10.1 wygeneruje bazodanowy plik magazynu kluczy w utworzonym przez nas folderze. Baza danych
bdzie plikiem noszcym nazw release.keystore. Okres wanoci wpisu (parametr validity)
wynosi 14 000 dni (w przyblieniu 38 lat) co stanowi dugi czas. Wane jest zrozumienie
powodu ustanowienia tak dugiego okresu wanoci. W dokumentacji Androida znalazo si
zalecenie, aby definiowa wystarczajco dugi okres wanoci, tak by przewysza cakowity
okres istnienia aplikacji, wliczajc w to jej wielokrotne aktualizacje. Zalecanym okresem wanoci jest 25 lat. Co wicej, jeeli aplikacja ma zosta umieszczona w serwisie Android Market
(http://www.android.com/market/), certyfikat musi by wany przynajmniej do dnia 22 padziernika 2033 roku. Serwis ten sprawdza kad umieszczon aplikacj pod ktem takiego okresu
wanoci. Poniewa podpisy cyfrowe aktualizacji musz bezwzgldnie pasowa do podpisu pierwszej wersji aplikacji, plik magazynu kluczy musi by zabezpieczony za wszelk cen! Jeeli
Czytelnik go utraci bez moliwoci odtworzenia, nie bdzie mg aktualizowa aplikacji, a jej
usprawnianie stanie si nagle olbrzymim problemem.
Wracajc do aplikacji keytool, argument alias jest niepowtarzaln nazw, przydzielan kademu wpisowi magazynu kluczy; mona jej pniej uywa jako odniesienia do tego wpisu. Po
uruchomieniu pokazanego na listingu 10.1 polecenia keytool aplikacja wywietli kilka pyta
(rysunek 10.1), a nastpnie wygeneruje baz danych oraz jej wpis.

320 Android 3. Tworzenie aplikacji

Rysunek 10.1. Dodatkowe pytania wywietlane przez aplikacj keytool

Po utworzeniu pliku magazynu kluczy moemy dodawa do niego kolejne certyfikaty. Wystarczy
uruchomi ponownie narzdzie keytool i wprowadzi ciek do istniejcego magazynu kluczy.

Magazyn kluczy debugowania i certyfikat programistyczny


Wspomnielimy, e wtyczka ADT tworzy magazyn certyfikatu programistycznego. Jednak
domylny certyfikat, utworzony w trakcie programowania aplikacji, nie moe by wykorzystany
w przypadku aplikacji wdraanej na rynek. Wynika to czciowo z faktu, e certyfikat ten jest
wany jedynie przez 365 dni, co oczywicie nie spenia wymagania dotyczcego terminu jego
wanoci trwajcego do 22 padziernika 2033 roku. Co si zatem stanie po upyniciu roku
tworzenia aplikacji? Pojawi si bd kompilacji. Istniejce aplikacje bd dziaay, jednak w celu
utworzenia nowej wersji danego programu musimy wygenerowa nowy certyfikat. Najprostszym rozwizaniem jest usunicie pliku debug.keystore, a w razie potrzeby narzdzie ADT
wygeneruje kolejny plik magazynu, a tym samym certyfikat wany przez nastpne 365 dni.
Aby znale plik debug.store, otwieramy w rodowisku Eclipse okno Preferences i przechodzimy
do menu Android/Build. W polu Default debug keystore zostanie wywietlona cieka do certyfikatu debugowania, co zostao pokazane na rysunku 10.2 (w rozdziale 2. znajdziemy informacje,
jak dotrze do okna Preferences).

Rysunek 10.2. Pooenie certyfikatu debugowania

Rozdzia 10 Analiza zabezpiecze i uprawnie

321

Naturalnie, skoro posiadamy nowy certyfikat programistyczny, nie moemy za jego pomoc
aktualizowa biecych wersji aplikacji obecnych w emulatorach AVD lub fizycznych urzdzeniach. rodowisko Eclipse bdzie wywietlao w konsoli monit o odinstalowanie istniejcej
aplikacji za pomoc narzdzia adb, co oczywicie powinnimy uczyni. Jeeli mamy zainstalowanych wiele aplikacji w urzdzeniu AVD, prostszym sposobem moe si okaza utworzenie
nowego egzemplarza, dziki czemu bdziemy mogli pracowa na nim od nowa. eby nie mie
problemu z terminem wanoci certyfikatu, moemy utworzy wasny plik debug.keystore z dowolnie zdefiniowanym okresem wanoci certyfikatu. Musi on oczywicie posiada t sam
nazw pliku oraz znajdowa si w tym samym miejscu co plik, ktry zosta utworzony przez narzdzie ADT. Alias certyfikatu posiada warto androiddebugkey, a obydwa hasa storepass
i keypass brzmi tak samo: android. Narzdzie ADT utworzy nazw certyfikatu Android Debug,
ustanowi jego jednostk organizacyjn Android oraz dwuliterowy kod kraju US. Wartoci parametrw organizacji, miasta oraz stanu mog pozosta nieznane (Unknown).
Jeeli Czytelnik otrzyma klucz map-api od firmy Google, korzystajc z przestarzaego certyfikatu,
bdzie musia uzyska nowy klucz powizany z nowym certyfikatem. Klucze map-api zostan
omwione w rozdziale 17.
Skoro wiemy ju, jak zdoby certyfikat cyfrowy pozwalajcy nam na podpisywanie plikw .apk,
warto si dowiedzie, w jaki sposb wykorzysta narzdzie jarsigner do samego procesu podpisywania. Poniej omawiamy, w jaki sposb moemy tego dokona.

Zastosowanie narzdzia jarsigner do podpisania pliku .apk


Narzdzie keytool, omwione we wczeniejszym podrozdziale, wygenerowao certyfikat cyfrowy, ktry stanowi jeden z parametrw aplikacji jarsigner. Jego drugim parametrem jest
rzeczywisty pakiet Androida, ktry ma zosta podpisany. eby utworzy pakiet Androida, musimy uy narzdzia Export Unsigned Application Package dostpnego we wtyczce ADT rodowiska Eclipse. Dostp do niego uzyskuje si poprzez kliknicie prawym przyciskiem myszy wza
projektu w aplikacji Eclipse, kliknicie opcji Android Tools, a nastpnie wybranie Export Unsigned Application Package. W ten sposb zostanie wygenerowany plik .apk niepodpisany przez
certyfikat testowy. eby sprawdzi dziaanie tego mechanizmu, zastosujmy narzdzie Export
Unsigned Application Package wobec jednego z projektw i zapiszmy gdzie wygenerowany plik
.apk. W tym przykadzie uyjemy utworzonego uprzednio folderu magazynu kluczy i wygenerujemy plik .apk c:\android\release\myappraw.apk.
Po utworzeniu pliku .apk oraz wpisu w magazynie kluczy uruchamiamy aplikacj jarsigner
w celu podpisania tego pliku (listing 10.2). Wpisujemy pen ciek do pliku magazynu kluczy
oraz pliku .apk.
Listing 10.2. Zastosowanie aplikacji jarsigner do podpisania pliku .apk
jarsigner -keystore "CIEKA DO PLIKU release.keystore" -storepass
paxxword -keypass paxxword "CIEKA DO NIEPRZETWORZONEGO PLIKU APK" androidbook

eby podpisa plik .apk, podajemy lokalizacj magazynu kluczy, haso do tego magazynu, haso
do klucza prywatnego, ciek do pliku .apk oraz alias wpisu magazynu kluczy. Aplikacja
jarsigner nastpnie podpisze plik .apk za pomoc sygnatury pochodzcej z wpisu magazynu
kluczy. Aby uruchomi narzdzie jarsigner, naley otworzy okno narzdzi (rozdzia 2.) albo
wiersz polece bd okno terminala. Nastpnie trzeba przej do katalogu bin zestawu JDK lub
upewni si, e katalog ten znajduje si w zmiennej systemowej PATH. Ze wzgldw bezpieczestwa

322 Android 3. Tworzenie aplikacji


bdzie lepiej, jeli pominiemy w poleceniu argumenty stanowice hasa i pozwolimy aplikacji jarsigner na wywietlenie zapytania o nie. Na rysunku 10.3 widzimy wywoanie narzdzia
jarsigner.

Rysunek 10.3. Uycie narzdzia jarsigner

Jak ju wczeniej stwierdzilimy, Android wymaga cyfrowego podpisu aplikacji, eby uniemoliwi zoliwemu programicie aktualizowanie programu utworzonego przez kogo innego
do wasnej wersji. Wynika z tego wniosek, e aktualizacja aplikacji musi by podpisana za pomoc tej samej sygnatury co jej pierwotna wersja. Jeeli aktualizacja zostanie podpisana za pomoc innej sygnatury, zostanie potraktowana jak odrbna aplikacja. Przypominamy wic ponownie, e naley zachowa szczegln ostrono z plikiem magazynu kluczy, ktry musi by
dostpny podczas aktualizowania aplikacji.

Optymalizacja aplikacji za pomoc narzdzia zipalign


Jest rzecz podan, eby podczas dziaania na urzdzeniu aplikacja jak najwydajniej korzystaa
z pamici. Jeeli w trakcie pracy uywa ona nieskompresowanych danych (na przykad niektrych rodzajw obrazw lub plikw danych), Android moe je odwzorowa bezporednio w pamici za pomoc wywoania mmap(). Jednak aby to byo moliwe, dane musz zosta przydzielone do czterobajtowego bloku pamici (obrazowo rzecz ujmujc, wyrwnane do bloku
pamici). Jednostki przetwarzajce w urzdzeniach obsugujcych system Android stanowi
procesory 32-bitowe, a 32 bity s rwnowane 4 bajtom. Wywoanie mmap() sprawia, e dane
umieszczone w pliku .apk staj si odwzorowaniem pamici. Jeeli jednak to czterobajtowe
(blokowe) przydzielanie danych nie zostao przeprowadzone, nie mona te dokona procesu
odwzorowania i w czasie dziaania aplikacji bdzie wykonywane dodatkowe kopiowanie danych
na poziomie jednostki przetwarzajcej (np. procesora lub emulatora). Dostpne w katalogu
pakietu Android SDK narzdzie zipalign analizuje dan aplikacj i w sposb transparentny
z punktu widzenia aplikacji przenosi wszelkie nieskompresowane dane do czterobajtowych
blokw. W wyniku tego procesu rozmiar aplikacji moe si nieznacznie zwikszy, nie bd to
jednak due zmiany. eby przeprowadzi ten proces na naszym pliku .apk, naley uy poniszego polecenia w oknie narzdzi (rwnie rysunek 10.4):
zipalign v 4 infile.apk outfile.apk

Rysunek 10.4. Zastosowanie aplikacji zipalign

Rozdzia 10 Analiza zabezpiecze i uprawnie

323

Zauwamy, e aplikacja zipalign nie modyfikuje pliku wejciowego, dlatego stosujemy czon
raw (ang. nieprzetworzony) w nazwie pliku podczas jego eksportowania ze rodowiska Eclipse.
W wyniku tego plik wyjciowy posiada nazw odpowiedni do przeprowadzenia wdraania.
W przypadku potrzeby nadpisania istniejcego pliku wyjciowego outfile.apk mona uy opcji
f. Zwrmy ponadto uwag, e aplikacja zipalign weryfikuje sposb odwzorowania (wyrwnania) danych w pliku po jego przetworzeniu. eby zweryfikowa poprawno wyrwnania pliku, mona uy aplikacji zipalign w poniszy sposb:
zipalign c v 4 filename.apk

Istotne jest, eby omawiany proces zosta przeprowadzony po procesie podpisywania, w przeciwnym razie podpisanie aplikacji moe zniweczy efekt zoptymalizowania plikw. Nie oznacza
to wcale, e aplikacja bdzie si zawiesza, po prostu moe zajmowa wicej pamici, ni potrzeba.
W rodowisku Eclipse w zakadce Android Tools mona natrafi na opcj Export Signed Application Package. Powoduje ona uruchomienie tak zwanego kreatora eksportu, za pomoc
ktrego mona przeprowadzi wszystkie omwione powyej etapy. Jedynymi danymi, jakie
naley wprowadzi, s cieka do magazynu kluczy, alias klucza, a take hasa i nazwa pliku
output.apk. Kreator w razie koniecznoci wygeneruje nawet nowy magazyn kluczy lub sam
klucz. Niektrym wygodniej bdzie korzysta z kreatora, innym przeprowadza po kolei
poszczeglne etapy na eksportowanym, niepodpisanym pakiecie aplikacji. Skoro ju Czytelnik wie, jak dziaaj obydwa rozwizania, bdzie mg wybra wygodniejsze rozwizanie.
Po podpisaniu i wyrwnaniu zawartoci pliku .apk mona rcznie zainstalowa aplikacj na
emulatorze za pomoc narzdzia adb. W celach wiczeniowych zachcamy teraz do uruchomienia emulatora. Jednym ze sposobw uruchomienia emulatora, o ktrym jeszcze nie wspomnielimy, jest kliknicie menu Window w rodowisku Eclipse i wybranie opcji Android SDK
and AVD Manager. Zobaczymy wywietlone okno z list posiadanych urzdze AVD. Wybierzmy jedno z nich i kliknijmy przycisk Start. Emulator uruchomi si bez funkcji kopiowania jakichkolwiek projektw ze rodowiska Eclipse. Otwrzmy okno narzdzi i wczmy narzdzie adb wraz z poleceniem install:
adb install "CIEKA DO PLIKU APK"

Proces moe zakoczy si niepowodzeniem z kilku powodw. Najczciej jednak jego przyczyn jest wczeniejsze zainstalowanie na emulatorze wersji testowej aplikacji, a to powoduje
konflikt certyfikatw i wywietlenie informacji o bdzie. Moliwe te, e wczeniej zainstalowano gotow wersj aplikacji, co spowoduje wywietlenie komunikatu, e dana aplikacja jest
ju zainstalowana na urzdzeniu. W pierwszym przypadku mona odinstalowa aplikacj testow za pomoc polecenia:
adb uninstall packagename

Zwrmy uwag, e argumentem jest tutaj nazwa pakietu, a nie nazwa pliku .apk. Nazwa
pakietu jest zdefiniowana w pliku AndroidManifest.xml zainstalowanej aplikacji.
W przypadku wystpienia bdu drugiego rodzaju mona wpisa ponisze polecenie, w ktrym
parametr r oznacza ponowne zainstalowanie aplikacji z zachowaniem danych na urzdzeniu
(w emulatorze):
adb install r "CIEKA DO PLIKU APK"

Przeledmy teraz, w jaki sposb podpisywanie aplikacji wpywa na proces jej aktualizowania.

324 Android 3. Tworzenie aplikacji

Instalowanie aktualizacji aplikacji a podpisywanie


Wspomnielimy wczeniej, e certyfikat posiada okres wanoci oraz e firma Google zaleca
ustawienie bardzo dugiego terminu wyganicia certyfikatu na wypadek duej liczby aktualizacji.
Co si zatem dzieje z aplikacj po wyganiciu certyfikatu? Czy bdzie ona nadal dziaa?
Na szczcie tak Android sprawdza certyfikat jedynie w czasie instalacji programu.
Po zainstalowaniu aplikacji bdzie ona dziaaa nawet po wyganiciu wanoci certyfikatu.
A co z aktualizacjami? Niestety, po wyganiciu wanoci certyfikatu nie bdzie moliwoci
aktualizowania aplikacji. Innymi sowy, zgodnie z rad firmy Google naley ustanowi wystarczajco dugi termin wanoci certyfikatu dla aplikacji, eby obj jej cay cykl ycia. Jeeli wano certyfikatu wyganie, Android nie bdzie instalowa aktualizacji aplikacji. Jedynym wyjciem pozostanie wtedy utworzenie kolejnej aplikacji posiadajcej inn nazw pakietu
i podpisanie jej za pomoc nowego certyfikatu. Jak wic wida, podstawow kwesti jest dobranie terminu wanoci certyfikatu w momencie jego utworzenia.
Skoro Czytelnik posiada ju wiedz na temat zabezpiecze zwizanych z wdraaniem oraz instalacj aplikacji, przejdmy do omwienia zabezpiecze rodowiska wykonawczego w Androidzie.

Przeprowadzanie testw zabezpiecze


rodowiska wykonawczego
Zabezpieczenia rodowiska wykonawczego w Androidzie zostay zaimplementowane na poziomie procesu oraz na poziomie operacyjnym. Na poziomie procesu Android zapobiega uzyskiwaniu bezporedniego dostpu do danych jednej aplikacji przez inn aplikacj. W Androidzie
mona to byo osign poprzez uruchomienie kadej aplikacji w odrbnym procesie oraz przez
przydzielenie kadej z nich unikatowego i trwaego identyfikatora uytkownika. Na poziomie
operacyjnym istnieje zdefiniowana lista chronionych funkcji i zasobw. eby aplikacja moga
uzyska dostp do tych informacji, naley doda przynajmniej jedno danie uprawnie w pliku
AndroidManifest.xml. Mona take zdefiniowa niestandardowe uprawnienie dla aplikacji.
W nastpnych podrozdziaach zajmiemy si tematyk zabezpiecze granicy procesu oraz sposobami deklarowania i stosowania predefiniowanych uprawnie. Omwimy take proces tworzenia niestandardowych uprawnie i ustawiania ich dla aplikacji. Zacznijmy od analizy zabezpiecze na granicach procesu.

Zabezpieczenia na granicach procesu


W przeciwiestwie do rodowisk komputerw biurkowych, w ktrych wikszo aplikacji korzysta z tego samego identyfikatora uytkownika, niemal kada aplikacja w Androidzie posiada
swj wasny identyfikator. W ten sposb zostaje utworzona granica izolujca procesy od siebie.
adna aplikacja nie moe uzyska bezporedniego dostpu do danych innej aplikacji.
Chocia kady proces jest oddzielony od innych, wspdzielenie danych pomidzy aplikacjami
jest oczywicie moliwe, musi jednak zosta jawnie zadeklarowane. Innymi sowy, eby otrzyma dane z innej aplikacji, naley podj interakcj z jej skadnikami. Mona na przykad wysa
zapytanie do dostawcy treci innej aplikacji, wywoa aktywno w innej aplikacji lub jak si
przekonamy w rozdziale 11. nawiza czno z usug innej aplikacji. Dla kadego z wymienionych sposobw okrelono metody umoliwiajce wymian informacji pomidzy aplikacjami, dokonuje si tego jednak w jawny sposb, poniewa wymiana ta nie polega na bezporednim dostpie do waciwej bazy danych, plikw i tak dalej.

Rozdzia 10 Analiza zabezpiecze i uprawnie

325

W Androidzie zabezpieczenia na poziomie granicy procesu s proste i zrozumiae. Sprawy staj


si jednak bardziej interesujce, gdy zwrcimy uwag na ochron zasobw (na przykad danych
kontaktowych), funkcji (na przykad aparatu fotograficznego) oraz naszych wasnych skadnikw. W celu zapewnienia odpowiedniej ochrony Android definiuje schemat uprawnie. To
wanie nim zajmiemy si teraz.

Deklarowanie oraz stosowanie uprawnie


Android zapewnia schemat uprawnie sucy do ochrony zasobw i funkcji urzdzenia. Na
przykad domylnie aplikacje nie mog uzyskiwa dostpu do listy kontaktw, umoliwia wykonywania pocze telefonicznych i tak dalej. Aby chroni uytkownika przed zoliwym
oprogramowaniem, Android wymusza na aplikacjach danie uprawnie, gdy do ich dziaania
jest niezbdny dostp do chronionego zasobu lub funkcji. Jak niebawem wyjanimy, dania
uprawnie s umieszczane w pliku manifecie. W trakcie instalacji instalator plikw APK przydziela lub odrzuca dane uprawnienia w zalenoci od sygnatury pliku .apk oraz (lub) decyzji
uytkownika. Jeeli uprawnienie nie zostanie przyznane, kada prba wykonania dziaania lub
uzyskania dostpu do danej funkcji zakoczy si niepowodzeniem.
W tabeli 10.2 zaprezentowano niektre powszechnie stosowane funkcje oraz wymagane do nich
uprawnienia. Chocia wikszo wymienionych funkcji nie zostaa jeszcze omwiona, zajmiemy
si nimi w dalszej czci ksiki (w dalszej czci tego rozdziau oraz w nastpnych rozdziaach).
Kompletn list uprawnie mona znale pod adresem:
http://developer.android.com/reference/android/Manifest.permission.html
Programici mog da uprawnie poprzez dodawanie wpisw w pliku AndroidManifest.xml.
Na przykad na listingu 10.3 zostao utworzone danie uzyskania dostpu do aparatu fotograficznego i odczytania listy kontaktw oraz danych kalendarza.
Listing 10.3. Uprawnienia w pliku AndroidManifest.xml
<manifest ... >
<application>
...
</application>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_CALENDAR" />
</manifest>

Warto wiedzie, e danie uprawnienia mona wprowadza do pliku AndroidManifest.xml


rcznie albo za pomoc edytora manifestu. Jest on gotowy do uruchomienia tu po otwarciu
(dwukrotnym klikniciu) pliku manifestu. W edytorze tym jest dostpna rozwijana lista wszystkich uprawnie, dziki czemu mona unikn popenienia bdu. Jak wida na rysunku 10.5,
mona uzyska dostp do listy uprawnie poprzez wybranie zakadki Permissions w edytorze
manifestu.
Wiemy ju, w jaki sposb jest zdefiniowany w Androidzie zestaw uprawnie sucych do ochrony
funkcji i zasobw. W podobny sposb moemy definiowa i wymusza niestandardowe uprawnienia dla danej aplikacji. Zobaczmy, jak to dziaa.

326 Android 3. Tworzenie aplikacji


Tabela 10.2. Funkcje, zasoby oraz wymagane do nich uprawnienia
Funkcja/zasb

Wymagane uprawnienie

Opis

Aparat
fotograficzny

android.permission.
CAMERA

Udziela dostpu do aparatu fotograficznego


urzdzenia.

Internet

android.permission.
INTERNET

Umoliwia poczenie sieciowe.

Dane
kontaktw
uytkownika

android.permission.
READ_CONTACTS

Pozwala na odczytywanie lub zapisywanie


danych kontaktw uytkownika.

Dane
kalendarza
uytkownika

android.permission.
READ_CALENDAR

Dyktafon

android.permission.
RECORD_AUDIO

Umoliwia nagrywanie dwiku.

Informacje
pooenia
geograficznego
GPS

android.permission.
ACCESS_FINE_LOCATION

Pozwala na uzyskanie dokadnych danych


dotyczcych pooenia geograficznego.
Obejmuje informacje lokalizacyjne GPS.
Jest ono rwnie wystarczajce dla sieci Wi-Fi
oraz wie komrkowych.

Informacje
pooenia
geograficzneg
o sieci Wi-Fi

android.permission.
ACCESS_COARSE_LOCATION

Pozwala na uzyskanie zgrubnych danych


dotyczcych pooenia geograficznego.
Obejmuje informacje lokalizacyjne sieci Wi-Fi
oraz uzyskiwane z wie komrkowych.

Informacje
o stanie baterii

android.permission.
BATTERY_STATS

Umoliwia uzyskanie informacje o stanie


baterii.

Bluetooth

android.permission.
BLUETOOTH

Pozwala na poczenie ze sparowanym


urzdzeniem Bluetooth.

android.permission.
WRITE_CONTACTS

Pozwala na odczytywanie lub zapisywanie


danych kalendarza uytkownika.

android.permission.
WRITE_CALENDAR

Stosowanie niestandardowych uprawnie


Android pozwala na zdefiniowanie wasnych uprawnie wobec danej aplikacji. Jeeli na przykad chcemy uniemoliwi okrelonym uytkownikom uruchamianie jednej z aktywnoci w aplikacji, moemy tego dokona za pomoc uprawnienia niestandardowego. Aby korzysta z wasnych uprawnie, naley je najpierw zadeklarowa w pliku AndroidManifest.xml. Po zdefiniowaniu
uprawnienia mona si do niego odnosi jak do pozostaych skadowych definicji. Zademonstrujemy, jak to dziaa.
Zbudujmy zatem aplikacj, ktrej aktywno nie bdzie dostpna dla kadego uytkownika.
eby tego dokona, bdzie potrzebowa specjalnego uprawnienia. Po utworzeniu aplikacji zawierajcej tak uprzywilejowan aktywno bdziemy mogli napisa klienta zdolnego do wywoania tej aktywnoci.

Rozdzia 10 Analiza zabezpiecze i uprawnie

327

Rysunek 10.5. Narzdzie edytora manifestu w rodowisku Eclipse


Na kocu tego rozdziau podalimy adres URL, pod ktrym mona znale projekty
utworzone specjalnie na potrzeby tego rozdziau. Projekty te mona zaimportowa
bezporednio do rodowiska Eclipse.

Najpierw naley utworzy projekt zawierajcy niestandardowe uprawnienie i aktywno. Dokonujemy tego poprzez uruchomienie rodowiska Eclipse i kliknicie opcji New/New Project/
Android Project. Otworzy si okno dialogowe New Android Project. Jako nazw projektu wpisujemy CustomPermission, wybieramy opcj Create new project in workspace i zaznaczamy Use
default location. Nazwa aplikacji moe brzmie Niestandardowe uprawnienie, nazwa pakietu
com.cust.perm, a nazwa aktywnoci CustPermMainActivity. Powinnimy rwnie wpisa
dowoln wersj docelowej platformy w polu Build Target. eby utworzy projekt, naley klikn przycisk Finish. Wygenerowany projekt bdzie zawiera dopiero co utworzon aktywno,
penic rol domylnej (gwnej) aktywnoci. Stwrzmy take aktywno uprzywilejowan
aktywno, dla ktrej wymagane jest specjalne uprawnienie. W aplikacji Eclipse naley
przej do pakietu com.cust.perm, utworzy klas PrivActivity, dla ktrej superklas jest
android.app.Activity, a nastpnie przepisa kod umieszczony na listingu 10.4.
Listing 10.4. Klasa PrivActivity
package com.cust.perm;
import
import
import
import
import

android.app.Activity;
android.os.Bundle;
android.view.ViewGroup.LayoutParams;
android.widget.LinearLayout;
android.widget.TextView;

public class PrivActivity extends Activity


{
@Override
public void onCreate(Bundle savedInstanceState) {

328 Android 3. Tworzenie aplikacji


super.onCreate(savedInstanceState);
LinearLayout view = new LinearLayout(this);
view.setLayoutParams(new LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
view.setOrientation(LinearLayout.HORIZONTAL);
TextView nameLbl = new TextView(this);
nameLbl.setText("Pozdrowienia z aktywnoci PrivActivity");
view.addView(nameLbl);
setContentView(view);
}
}

Jak wida, aktywno PrivActivity nie ma adnych nadzwyczajnych funkcji. Chcemy jedynie
pokaza, w jaki sposb mona chroni j za pomoc uprawnienia i wywoa j za pomoc
klienta. Jeeli klient zostanie waciwie zaimplementowany, na ekranie pojawi si wiadomo
Pozdrowienia z aktywnoci PrivActivity. Po wygenerowaniu tej chronionej aktywnoci
mona utworzy uprawnienie do niej.
eby stworzy niestandardowe uprawnienie, naley je zdefiniowa w pliku AndroidManifest.xml. Najatwiej to zrobi za pomoc edytora manifestu. Wystarczy dwukrotnie klikn plik
AndroidManifest.xml i wybra zakadk Permissions. W oknie Permissions klikamy przycisk
Add, wybieramy opcj Permission i wciskamy przycisk OK. Zostanie wygenerowane puste
uprawnienie. Naley wypeni je atrybutami, tak jak zilustrowano na rysunku 10.6. Wypeniamy
pola z prawej strony, a jeeli etykieta po prawej stronie wci bdzie nosia nazw Permission,
klikamy j, dziki czemu nazwa uprawnienia powinna zosta zaktualizowana.

Rysunek 10.6. Deklarowanie niestandardowego uprawnienia za pomoc edytora manifestu

Jak wida na rysunku 10.6, uprawnienie posiada nazw, etykiet, ikon, grup uprawnienia, opis
i poziom ochrony. W tabeli 10.3 opisalimy wymienione parametry.

Rozdzia 10 Analiza zabezpiecze i uprawnie

329

Tabela 10.3. Atrybuty uprawnienia


Atrybut

Wymagany? Opis

android:name

Tak

Nazwa uprawnienia. Zazwyczaj naley przestrzega


konwencji nazewnictwa Androida
(*.permission.*).

android:protectionLevel

Tak

Definiuje potencja zagroenia zwizany


z uprawnieniem. Naley wybra jedn spord
nastpujcych wartoci:
normal
dangerous
signature
signatureOrSystem

W zalenoci od poziomu ochrony system podejmuje


inne dziaanie podczas okrelania, czy naley
przydzieli dostp uytkownikowi, czy nie. Warto
normal oznacza, e uprawnienie stanowi niewielkie
zagroenie i nie stanowi niebezpieczestwa dla
systemu, uytkownika lub innych aplikacji. Poziom
dangerous oznacza, e istnieje due ryzyko oraz
e system najprawdopodobniej bdzie potrzebowa
zgody uytkownika przed akceptacj uprawnienia.
Warto signature przydziela uprawnienie jedynie
aplikacjom posiadajcym tak sam sygnatur
co aplikacja deklarujca to uprawnienie. Warto
signatureOrSystem pozwala przydziela
uprawnienie aplikacjom posiadajcym tak sam
sygnatur jak aplikacja deklarujca to uprawnienie
lub klasom pakietu Android. Ten poziom ochrony
jest stosowany w szczeglnych przypadkach,
w ktrych wielu producentw musi wspdzieli
funkcje poprzez obraz systemu.
android:permissionGroup

Nie

Uprawnienia mona grupowa, jednak w przypadku


uprawnie niestandardowych powinno si pomija
t waciwo. Jeeli rzeczywicie trzeba utworzy
grup uprawnie, lepiej skorzysta z tego atrybutu:

android:label

Nie

Ta waciwo rwnie nie jest wymagana.


Suy do stworzenia krtkiego opisu uprawnienia.

android:description

Nie

Ta waciwo take nie jest wymagana. Mona


tu umieci dokadniejsze informacje na temat
przeznaczenia uprawnienia.

android:icon

Nie

Uprawnienia mona powiza z ikonami


umieszczonymi w zasobach (na przykad
@drawable/mojaikona).

android.permission-group.SYSTEM_TOOLS

330 Android 3. Tworzenie aplikacji


Po utworzeniu niestandardowego uprawnienia trzeba sprawi, by aktywno PrivActivity moga
by uruchamiana jedynie przez aplikacje posiadajce uprawnienie syh.permission.STARTMY
ACTIVITY. Mona doczy wymagane uprawnienie do aktywnoci poprzez dodanie atrybutu
android:permission do definicji aktywnoci w pliku AndroidManifest.xml. eby uruchomi
aktywno, bdzie konieczne rwnie dodanie do niej filtru intencji. Zaktualizujmy plik
AndroidManifest.xml kodem zawartym na listingu 10.5.
Listing 10.5. Plik AndroidManifest.xml projektu zawierajcego niestandardowe uprawnienie
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cust.perm"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".CustPermMainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="PrivActivity"
android:permission="syh.permission.STARTMYACTIVITY">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
<permission
android:protectionLevel="normal"
android:label="Uruchom moj aktywno"
android:description="@string/startMyActivityDesc"
android:name="syh.permission.STARTMYACTIVITY"></permission>
<uses-sdk android:minSdkVersion="4" />
</manifest>

Jak wida na listingu 10.5, musimy doda sta w postaci cigu znakw startMyActivityDesc
do zasobw typu string. eby kompilacja kodu przebiega bezbdnie, dodajmy nastpujcy
cig znakw do pliku res/values/strings.xml:
<string name="startMyActivityDesc">Umoliwia uruchomienie mojej aktywnoci</string>

Wczmy teraz projekt na emulatorze. Chocia gwna aktywno nie wykonuje adnej czynnoci, musimy zainstalowa aplikacj na emulatorze, zanim bdzie mona napisa klienta
dla uprzywilejowanej aktywnoci.
Napiszmy wic takiego klienta. W rodowisku Eclipse naley 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 moe brzmie
Klient niestandardowego uprawnienia, nazwa pakietu com.client.cust.perm, a nazwa aktywnoci ClientCustPermMainActivity. W polu Build Target wpisujemy nazw dowolnej wersji
platformy. Aby utworzy projekt, naley klikn przycisk OK.

Rozdzia 10 Analiza zabezpiecze i uprawnie

331

Teraz napiszemy aktywno zawierajc przycisk, ktrego kliknicie spowoduje wywoanie


uprzywilejowanej aktywnoci. Przepiszmy kod ukadu graficznego z listingu 10.6 do pliku
main.xml, znajdujcego si w naszym nowym projekcie.
Listing 10.6. Plik main.xml w projekcie klienta
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/btn"
android:text="Uruchom PrivActivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doClick" />
</LinearLayout>

Jak wida, plik XML ukadu graficznego definiuje pojedynczy przycisk nazwany Uruchom
PrivActivity. Napiszmy teraz aktywno, ktra bdzie generowaa zdarzenie wywoane klikniciem i uruchamiaa uprzywilejowan aktywno. Kod z listingu 10.7 naley umieci w klasie
ClientCustPermMainActivity.
Listing 10.7. Zmodyfikowana klasa ClientCustPermMainActivity
package com.client.cust.perm;

// To jest plik ClientCustPermMainActivity.java


import
import
import
import

android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;

public class ClientCustPermMainActivity extends Activity {


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void doClick(View view) {
Intent intent = new Intent();
intent.setClassName("com.cust.perm","com.cust.perm.PrivActivity");
startActivity(intent);
}
}

Na listingu 10.7 wida, e po wywoaniu przycisku zostaje utworzona nowa intencja, a nastpnie ustanowiona nazwa klasy uruchamianej aktywnoci. W naszym przypadku chcemy uruchomi aktywno com.cust.perm.PrivActivity z pakietu com.cust.perm.

332 Android 3. Tworzenie aplikacji


W tym momencie jedynym brakujcym elementem jest wpis uses-permission, ktry jest dodawany do pliku manifestu po to, aby poinformowa Androida, e naley skorzysta z uprawnienia syh.permission.STARTMYACTIVITY. Zamiemy kod manifestu z projektu klienta na
zawarty na listingu 10.8.
Listing 10.8. Plik manifest klienta
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.client.cust.perm"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".ClientCustPermMainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="syh.permission.STARTMYACTIVITY">
<uses-sdk android:minSdkVersion="4" />
</manifest>

Na listingu 10.8 dodalimy wpis uses-permission, dajcy niestandardowego uprawnienia,


ktre jest wymagane do uruchomienia aktywnoci PrivActivity zaimplementowanej w naszym projekcie.
Teraz mona zainstalowa projekt klienta na emulowanym urzdzeniu i klikn przycisk
Uruchom PrivActivity. Po przetworzeniu zdarzenia kliknicia zostanie wywietlony napis
Pozdrowienia z aktywnoci PrivActivity.
Po udanym wywoaniu uprzywilejowanej aktywnoci mona usun wpis uses-permission
z pliku manifestu klienta i ponownie wdroy projekt. Po klikniciu przycisku wywoania aktywnoci pojawi si komunikat o bdzie. Zauwamy, e aplikacja LogCat wywietli informacj
o wyjtku odmowy dostpu.
Wiemy ju, w jaki sposb dziaaj niestandardowe uprawnienia w Androidzie. Oczywicie nie
s one ograniczone wycznie do aktywnoci. Mona stosowa uprawnienia zarwno predefiniowane, jak i niestandardowe rwnie wobec innych rodzajw skadnikw Androida. Zajmiemy si teraz kolejnym rodzajem uprawnie uprawnieniami identyfikatorw URI.

Stosowanie uprawnie identyfikatorw URI


W przypadku dostawcw treci (omwionych w rozdziale 3.) cakowite przydzielanie dostpu
lub jego cakowite blokowanie czsto nie wchodzi w gr. Na szczcie w Androidzie zostaa
zaimplementowana odpowiednia technologia. Dobrym przykadem s zaczniki do wiadomoci e-mail. eby zacznik zosta wywietlony, musi zosta odczytany przez inn aktywno. Ale
ta aktywno nie powinna mie dostpu do wszystkich danych poczty e-mail, a nawet do pozostaych zacznikw. W tym momencie mona wykorzysta uprawnienia do identyfikatorw URI.

Rozdzia 10 Analiza zabezpiecze i uprawnie

333

Przekazywanie uprawnie do identyfikatorw URI w intencjach


Po wywoaniu innej aktywnoci oraz przekazaniu jej identyfikatora URI aplikacja moe okreli, e przydziela uprawnienia jedynie do przekazywanych identyfikatorw URI. Najpierw jednak dany program sam musi otrzyma uprawnienia do identyfikatora URI. Z kolei dostawca
tego identyfikatora musi wsppracowa z tym programem oraz umoliwia przydzia uprawnie
do innej aktywnoci. Na listingu 10.9 zaprezentowano kod umoliwiajcy wywoanie aktywnoci
wraz z przydzielaniem uprawnie. Kod ten w rzeczywistoci stanowi wycinek programu Android
Email, w ktrym suy do uruchamiania aplikacji pozwalajcej na przegldanie zacznikw.
Listing 10.9. Kod uruchamiajcy aktywno wraz z przydzielaniem uprawnie
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
} catch (ActivityNotFoundException e) {
mHandler.attachmentViewError();

// Do zrobienia: dodanie w nastpnej wersji odpowiedniego ostrzeenia (oraz mnstwa


// mechanizmw czyszczcych, zapobiegajcych jego pojawieniu si).
}

Rodzaj zacznika okrela obiekt ContentUri. Zwrmy uwag, e utworzenie intencji jest
efektem dziaania Intent.ACTION_VIEW, a metoda setData() powoduje ustawienie danych.
Wida te flag przydzielajc uprawnienia do odczytu zacznika dowolnej aktywnoci dopasowanej do intencji. W tym miejscu do gry wkracza dostawca treci. To, e aktywno odczytaa
uprawnienia do treci, wcale nie oznacza, e bdzie przekazywa te uprawnienia innej aktywnoci, ktra jeszcze nie posiada uprawnie. Musi na to pozwoli rwnie dostawca treci. Po znalezieniu w aktywnoci pasujcego filtra intencji Android sprawdza dostawc treci w celu upewnienia si, czy mona przydzieli dane uprawnienia. W istocie dostawca treci otrzymuje danie
umoliwienia dostpu do tej nowej aktywnoci, dokadniej do treci okrelonej w identyfikatorze
URI. Jeeli dostawca odmwi, zostanie wywietlony komunikat o wyjtku SecurityException,
a caa operacja zakoczy si niepowodzeniem. Na listingu 10.9 ta konkretna aplikacja nie sprawdza wyjtku SecurityException, poniewa programista nie spodziewa si adnej odmowy
udzielenia uprawnie. Wynika to z faktu, e dostawca zacznika jest czci aplikacji Email! Istnieje jednak moliwo, e nie bdzie adnej aktywnoci obsugujcej zacznik, dlatego wic
mamy do czynienia tylko z tym wyjtkiem. W przypadku gdy aktywno przetwarzajca identyfikator URI uzyska do niego uprawnienie, dostawca treci nie moe go odmwi. To znaczy, e
wywoujca aktywno moe uzyska uprawnienie, a jeeli aktywno po stronie odbiorczej intencji posiada ju wymagane uprawnienia do obiektu contentURI, wywoana aktywno bdzie
moga kontynuowa dziaanie bez najmniejszego problemu.
Oprcz flagi Intent.FLAG_GRANT_READ_URI_PERMISSION istnieje jeszcze flaga dajca uprawnienia do zapisu Intent.FLAG_GRANT_WRITE_URI_PERMISSION. Istnieje moliwo ustanowienia kadej z tych flag w obiekcie klasy Intent. Poza tym znajduj one zastosowanie rwnie
w klasach typu Service, Broadcast Receiver, a take Activity, poniewa aktywnoci take
mog odbiera intencje.

334 Android 3. Tworzenie aplikacji

Definiowanie uprawnie identyfikatorw URI w dostawcach treci


W jaki zatem sposb dostawca treci definiuje uprawnienia identyfikatorw URI? Dokonuje
tego w pliku AndroidManifest.xml na jeden z dwch sposobw:
Pierwszy sposb polega na przypisaniu wartoci atrybutowi
android:grantUriPermissions wewntrz znacznika <provider>. Jeeli wstawimy
warto true, dostp do treci tego dostawcy bdzie nieograniczony. W przypadku
wprowadzenia wartoci false moe si pojawi drugi sposb okrelania uprawnie
identyfikatorw URI, ewentualnie dostawca treci moe nie udzieli adnych uprawnie.
Drugie rozwizanie polega na definiowaniu tych uprawnie w wzach potomnych
znacznika <provider>. Taki potomny znacznik nosi nazw <grant-uripermission> i moemy wstawi ich kilka w jednym wle nadrzdnym. Znacznik
<grant-uri-permission> posiada trzy atrybuty:
Za pomoc atrybutu android:path moemy okreli pen ciek, ktra bdzie
moga uzyskiwa wszelkie uprawnienia.
W analogiczny sposb atrybut android:pathPrefix definiuje pocztek cieki
identyfikatora URI.
Atrybut android:pathPattern dopuszcza wyraenia wieloznaczne (np. *)
w definiowaniu cieki.
Jak ju wczeniej stwierdzilimy, jednostka przyznajca musi sama posiada odpowiednie uprawnienia, zanim bdzie moga je przekaza innej jednostce. Dostawcy treci posiadaj dodatkowe
sposoby kontrolowania dostpu do swoich zasobw poprzez atrybut android:readPermission,
a take atrybuty android:writePermission oraz android:permission (wygodny sposb
okrelenia uprawnie zapisu oraz odczytu za pomoc jednej wartoci) w znaczniku <provider>.
Warto kadego z tych trzech atrybutw przybiera posta cigu znakw, reprezentujcego
uprawnienie, ktre musi posiada jednostka wywoujca w celu przeprowadzania operacji zapisu/
odczytu na tym dostawcy treci. Zanim aktywno bdzie moga przydzieli uprawnienie odczytu identyfikatora URI innej jednostce, sama musi posiada uprawnienie odczytu, co zostaje
zapewnione dziki obecnoci atrybutw android:readPermission lub android:Permission.
Jednostka dajca uprawnie moe zadeklarowa je w pliku manifecie, dokadniej w znaczniku <uses-permissions>.

Odnoniki
Poniej zamieszczamy przydatne cza do materiaw, z ktrymi warto si dokadniej zapozna:
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw projektw
bezporednio zwizanych z t ksik. Projekt ukazujcy aspekty omwione w tym
rozdziale zosta umieszczony w katalogu ProAndroid3_R10_Zabezpieczenia. Umiecilimy
w nim rwnie plik Czytaj.TXT wyjaniajcy szczegowo proces importowania
projektw do rodowiska Eclipse.
http://developer.android.com/guide/topics/security/security.html pod tym adresem
znajdziemy podrozdzia Security and Permissions, bdcy czci dokumentacji Android
Developers Guide. Stanowi on oglne omwienie zagadnienia wraz z odnonikami
do wielu dalszych materiaw.

Rozdzia 10 Analiza zabezpiecze i uprawnie

335

http://developer.android.com/guide/publishing/app-signing.html adres kierujcy


nas do podrozdziau Signing Your Application z wyej wspomnianego dokumentu
Android Developers Guide.
http://android.git.kernel.org/?p=platform/packages/apps/Email.git;a=blob_plain;f=src
/com/android/email/activity/MessageView.java pod tym adresem1 znajdziemy
kod rdowy podstawowej aplikacji Email, w ktrym zostaa uyta flaga
FLAG_GRANT_READ_URI_PERMISSION. Przegldajc ten kod rdowy,
moemy si przekona, w jaki sposb zesp twrcw Androida implementuje
uprawnienia identyfikatorw URI.

Podsumowanie
W niniejszym rozdziale wyjanilimy, e Android wymaga cyfrowych certyfikatw od wszystkich aplikacji. Opisalimy sposoby zapewnienia bezpieczestwa podczas projektowania aplikacji
na emulatorze oraz w rodowisku Eclipse, a take metody podpisywania kocowych wersji pakietu Android. Przedstawilimy take kwesti zabezpiecze rodowiska wykonawczego mona
si byo dowiedzie, e instalator w systemie Android wymaga od aplikacji uprawnie podczas
jej instalowania. Pokazalimy rwnie, w jaki sposb definiowa uprawnienia wymagane przez
aplikacj, a take jak utworzy wasne, niestandardowe uprawnienia. Na koniec wyjanilimy,
jak dostawcy treci kontroluj dostp do swoich zasobw, a take jak pozwalaj jednym jednostkom przekazywa uprawnienia dla innych jednostek, ktre mog zosta wywoane w celu przeprowadzenia okrelonych operacji na danych z dostawcy treci, bez koniecznoci nadawania
takiej pomocniczej jednostce uprawnie do wszystkich informacji zawartych w dostawcy.
W nastpnym rozdziale zajmiemy si tworzeniem oraz uytkowaniem usug w Androidzie.

W trakcie tumaczenia ksiki strona http://android.git.kernel.org zostaa zaatakowana przez nieznanych


sprawcw i od tego czasu wszelkie dostpne na niej zasoby s niedostpne dla uytkownikw.
Prawdopodobnie strona ta zostanie ponownie oddana do uytku w niedalekiej przyszoci, do tego
czasu osoby pragnce przejrze kod rdowy Androida musz skorzysta z alternatywnego rda.
Na stronie http://source.android.com/source/downloading.html znajdziemy instrukcj korzystania
z narzdzia Repo, pozwalajcego na pobieranie i przegldanie wspomnianego kodu rdowego
przyp. tum.

336 Android 3. Tworzenie aplikacji

R OZDZIA

11
Tworzenie i uytkowanie usug

System Android zawiera kompletny stos programowy. Oznacza to, e otrzymujemy


system operacyjny oraz oprogramowanie integracyjne (ang. middleware), a take
dziaajce aplikacje (na przykad program do obsugi telefonu). Poza tymi skadnikami otrzymujemy dostp do rodowiska SDK, dziki ktremu moemy pisa
aplikacje dla tego systemu. Dotychczas zajmowalimy si aplikacjami, ktre w bezporedni sposb wsppracuj z uytkownikiem poprzez interfejs UI. Nie omawialimy jednak usug dziaajcych w tle oraz moliwoci tworzenia skadnikw
przetwarzanych w tle.
W niniejszym rozdziale skupimy si na tworzeniu i uytkowaniu usug w Androidzie.
Na pocztku omwimy uytkowanie usug HTTP, nastpnie pokaemy przyjemny sposb przeprowadzania prostych zada w tle, a w dalszej kolejnoci zajmiemy
si komunikacj midzyprocesow czyli komunikacj pomidzy aplikacjami
znajdujcymi si w obrbie jednego urzdzenia. Na kocu pjdziemy jeszcze jeden krok naprzd i utworzymy dziaajc przykadow aplikacj, integrujc si
z interfejsem Tumacza Google.

Uytkowanie usug HTTP


Aplikacje w Androidzie, a oglnie aplikacje dla urzdze mobilnych, s maymi
programami oferujcymi du liczb funkcji. Jednym ze sposobw zapewnienia tak
duej funkcjonalnoci aplikacji w tak maym urzdzeniu jest uzyskiwanie przez nie
informacji z rnych rde. Na przykad wikszo smartfonw zawiera aplikacj
Mapy, ktra jest wyposaona w rozbudowane funkcje przetwarzania map. Wiemy
ju jednak, e program ten jest zintegrowany z serwerem Mapy Google oraz innymi
usugami, dziki ktrym uzyskuje on wspomnian zoono.
Skoro o tym mowa, jest cakiem prawdopodobne, e tworzone przez nas aplikacje
bd rwnie wykorzystyway informacje z innych aplikacji i interfejsw API.
Standardow strategi integracji jest stosowanie protokou HTTP. Na przykad
moemy przewidzie udostpnianie w internecie serwletu Java, ktry bdzie
dostarcza usugi uzyskiwane poprzez jedn z aplikacji Androida. Jak tego dokona

338 Android 3. Tworzenie aplikacji


w Androidzie? Interesujcy jest fakt, e zestaw Android SDK jest zaopatrzony w powszechnie
stosowany w rodowisku J2EE modu HttpClient, w wersji zaprojektowanej przez organizacj
Apache (http://hc.apache.org/httpclient-3.x/). W rodowisku Android SDK wersja tego moduu
zostaa dopasowana do Androida, ale interfejsy API zostay niemal niezmienione w stosunku do
wersji dla rodowiska J2EE.
Modu HttpClient jest rozbudowanym klientem HTTP. Chocia posiada pen obsug
protokou HTTP, najczciej bdziemy wykorzystywa jedynie wywoania metod GET i POST.
W niniejszym podrozdziale przedstawimy zatem wspomniane wywoania GET i POST moduu
HttpClient.

Wykorzystanie moduu HttpClient do da wywoania GET


Poniej przedstawiamy jeden z oglnych algorytmw stosowania moduu HttpClient:
1. Utwrz modu HttpClient (lub skorzystaj z istniejcego odniesienia).
2. Utwrz now metod HTTP, na przykad PostMethod lub GetMethod.
3. Skonfiguruj pary nazwa warto dla parametrw protokou HTTP.
4. Wykonaj wywoanie HTTP za pomoc moduu HttpClient.
5. Przetwrz odpowied protokou HTTP.
Na listingu 11.1 zosta pokazany sposb przeprowadzenia wywoania GET za pomoc moduu
HttpClient.

Na kocu tego rozdziau zamieszczamy adres, pod ktrym Czytelnik znajdzie projekty
pozwalajce zrozumie koncepcje omwione w rozdziale. Istnieje moliwo ich
bezporedniego zaimportowania do rodowiska Eclipse. Ponadto, poniewa kod
bdzie prbowa uzyska dostp do internetu, podczas wywoywania protokou HTTP
za pomoc moduu HttpClient musimy doda w pliku manifecie upowanienie
android.permission.INTERNET.
Listing 11.1. Stosowanie klas HttpClient i HttpGet plik HttpGetDemo.java
import
import
import
import
import
import
import
import
import

java.io.BufferedReader;
java.io.IOException;
java.io.InputStreamReader;
org.apache.http.HttpResponse;
org.apache.http.client.HttpClient;
org.apache.http.client.methods.HttpGet;
org.apache.http.impl.client.DefaultHttpClient;
android.app.Activity;
android.os.Bundle;

public class HttpGetDemo extends Activity {

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
BufferedReader in = null;
try {

Rozdzia 11 Tworzenie i uytkowanie usug

339

HttpClient client = new DefaultHttpClient();


HttpGet request = new HttpGet("http://code.google.com/android/");
HttpResponse response = client.execute(request);
in = new BufferedReader(
new InputStreamReader(
response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
String page = sb.toString();
System.out.println(page);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

HttpClient zawiera abstrakcje rnych typw dania protokou HTTP, na przykad


HttpGet, HttpPost i tak dalej. Na listingu 11.1 modu HttpClient suy do pobrania zawartoci

Modu

z adresu http://code.google.com/android/. Waciwe danie protokou HTTP jest przeprowadzane


poprzez wywoanie metody client.execute(). Po wykonaniu dania kod przeksztaca ca
odpowied w pojedynczy obiekt typu string. Zauwamy, e obiekt BufferedReader jest zamknity w bloku finally, ktry jednoczenie zamyka podstawowe poczenie HTTP.
W powyszym przykadzie umiecilimy logik protokou HTTP wewntrz aktywnoci, ale nie
musimy stosowa moduu HttpClient z poziomu kontekstu aktywnoci. Moemy go wykorzystywa wewntrz kontekstu skadnika Androida lub stosowa go jako cz samodzielnej
klasy. W rzeczywistoci nie powinnimy wykorzystywa klasy HttpClient bezporednio wewntrz aktywnoci, poniewa wywoanie sieci moe trwa pewien czas i spowodowa wymuszone zamknicie aktywnoci. Zajmiemy si tym zagadnieniem w dalszej czci rozdziau.
Na razie posuymy si niewielkim uproszczeniem, gdy chcemy skupi si na mechanizmie
wywoywania klasy HttpClient.
Kod widoczny na listingu 11.1 wykonuje danie protokou HTTP bez przekazywania jakichkolwiek parametrw serwerowi. Moemy przekazywa parametry (pary nazwa warto)
jako cz dania poprzez dodanie tych par do adresu URL, podobnie jak ma to miejsce na
listingu 11.2.

340 Android 3. Tworzenie aplikacji


Listing 11.2. Dodawanie parametrw do dania metody GET protokou HTTP
HttpGet request = new HttpGet("http://somehost/WS2/Upload.aspx?one=valueGoesHere");
client.execute(request);

Podczas wykonywania dania GET parametry (nazwy i wartoci) tego dania s przekazywane
w postaci czci adresu URL. Przekazywanie parametrw w ten sposb ma pewne ograniczenia.
Gwoli cisoci, dugo adresu URL nie powinna przekracza 2048 znakw. Jeeli chcemy wysa wiksz ilo danych, powinnimy skorzysta z dania metody HTTP POST. Metoda POST
jest elastyczniejsza i przekazuje parametry w formie czci treci dania.

Wykorzystanie moduu HttpClient do da wywoania POST


(przykad wieloczciowy)
Wykonanie wywoania POST jest bardzo podobne do wywoania GET (listing 11.3).
Listing 11.3. danie metody HTTP POST za pomoc moduu HttpClient
HttpClient client = new DefaultHttpClient();
HttpPost request = new HttpPost(
"http://192.165.13.37/uslugi/robCos.do");
List<NameValuePair> postParameters = new ArrayList<NameValuePair>();
postParameters.add(new BasicNameValuePair("first", "param value one"));
postParameters.add(new BasicNameValuePair("issuenum", "10317"));
postParameters.add(new BasicNameValuePair("username", "dave"));
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(
postParameters);
request.setEntity(formEntity);
HttpResponse response = client.execute(request);

Kod widoczny na listingu 11.3 zastpuje trzy wiersze z listingu 11.1, w miejscu, gdzie jest wykorzystywana metoda HttpGet. Caa reszta pozostaje bez zmian. Aby da wywoania metody
POST za pomoc moduu HttpClient, musimy wywoa jego metod execute() wobec instancji HttpPost. Podczas przeprowadzania wywoa metody POST zazwyczaj przekazujemy
parametry nazwa warto zakodowane w postaci adresu URL jako cz dania protokou
HTTP. W celu wykonania tego za pomoc moduu HttpClient musimy utworzy list zawierajc wystpienia obiektw NameValuePair i umieci j w klasie UrlEncodedFormEntity.
Obiekt NameValuePair zawiera kombinacj parametrw nazwa warto, natomiast klasa
UrlEncodedFormEntity potrafi przetumaczy list tych obiektw na jzyk zrozumiay dla wywoa protokou HTTP (ogem wywoa metody POST). Po utworzeniu klasy UrlEncodedForm
Entity mona ustanowi typ obiektu funkcji HttpPost, a nastpnie odpowiedzie na danie.
W kodzie pokazanym na listingu 11.3 stworzylimy modu HttpClient, a nastpnie wywoalimy
funkcj HttpPost posiadajc adres URL punktu kocowego protokou HTTP. Wygenerowalimy
list obiektw NameValuePair i umiecilimy w niej pojedynczy parametr nazwa warto.
Nastpnie utworzylimy wystpienie obiektu UrlEncodedFormEntity i przekazalimy jego
konstruktorowi list obiektw NameValuePair. Na koniec wywoalimy metod setEntity()
dania metody POST i spenilimy to danie za pomoc instancji HttpClient.

Rozdzia 11 Tworzenie i uytkowanie usug

341

W rzeczywistoci metoda HTTP POST jest o wiele potniejszym narzdziem. Dziki niej
moemy rwnie atwo przekazywa pojedyncze parametry nazwa warto, co zostao przedstawione na listingu 11.3, jak rwnie zoone obiekty, takie jak pliki. Metoda HTTP POST obsuguje inny format treci dania, znany jako wieloczciowa metoda POST (ang. multipart
POST). Dziki temu typowi metody POST moemy wraz z parametrami nazwa warto wysya wasne pliki. Niestety, wersja moduu HttpClient dostpna w Androidzie nie obsuguje
wieloczciowych metod POST w sposb bezporedni. Aby mona byo przeprowadza wieloczciowe wywoania metody POST, potrzebne jest dodatkowe oprogramowanie. Chodzi
o trzy posiadajce jawny kod rdowy projekty firmy Apache: Apache Commons IO, Mime4J
i HttpMime, ktre mona pobra z nastpujcych witryn:
Commons IO http://commons.apache.org/io/,
Mime4J http://james.apache.org/mime4j/,
HttpMime http://hc.apache.org/downloads.cgi (wewntrz wersji HttpClient).
Na listingu 11.4 zostao przedstawione zastosowanie wieloczciowej metody POST w Androidzie.
Listing 11.4. Tworzenie wywoania wieloczciowej metody POST
import
import
import
import
import
import
import
import
import
import

java.io.ByteArrayInputStream;
java.io.InputStream;
org.apache.commons.io.IOUtils;
org.apache.http.HttpResponse;
org.apache.http.client.HttpClient;
org.apache.http.client.methods.HttpPost;
org.apache.http.entity.mime.MultipartEntity;
org.apache.http.entity.mime.content.InputStreamBody;
org.apache.http.entity.mime.content.StringBody;
org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
public class TestMultipartPost extends Activity
{
public void executeMultipartPost()throws Exception
{
try {
InputStream is = this.getAssets().open("data.xml");
HttpClient httpClient = new DefaultHttpClient();
HttpPost postRequest =
new HttpPost("http://jakisserwersieciowy.com/uslugi/robCos.do");
byte[] data = IOUtils.toByteArray(is);
InputStreamBody isb = new InputStreamBody(new
ByteArrayInputStream(data),"uploadedFile");
StringBody sb1 = new StringBody("Wpisujemy Jaki Tekst");
StringBody sb2 = new StringBody("Rwnie Wpisujemy Jaki Tekst");
MultipartEntity multipartContent = new MultipartEntity();
multipartContent.addPart("uploadedFile", isb);
multipartContent.addPart("one", sb1);
multipartContent.addPart("two", sb2);
postRequest.setEntity(multipartContent);

342 Android 3. Tworzenie aplikacji


HttpResponse response =httpClient.execute(postRequest);
response.getEntity().getContent().close();
} catch (Throwable e)
{

// tutaj obsuguje wyjtki


}
}
}

W powyszym przykadowym kodzie, gdzie zaprezentowano sposb korzystania


z wieloczciowej metody, uyto kilku plikw .jar, ktre nie s dostpne w
rodowisku Android. Aby pliki te zostay doczone do pakietu .apk, musimy doda je jako
zewntrzne pliki .jar w rodowisku Eclipse. W tym celu naley klikn prawym
przyciskiem myszy nazw projektu w programie Eclipse, wybra opcj Properties,
nastpnie Java Class Path, przej do zakadki Libraries i zaznaczy opcj Add External JARs.
Wykonanie tych czynnoci sprawi, e pliki te bd dostpne zarwno w trakcie
kompilacji, jak i dziaania aplikacji.

eby aktywowa wieloczciow metod POST, musimy wywoa modu HttpPost i uruchomi
jego metod setEntity() wraz z instancj MultiPartEntity (zamiast obiektu UrlEncoded
FormEntity, ktry tworzylimy dla pojedynczego parametru nazwa warto). Element
MultiPartEntity reprezentuje tre dania wywoania wieloczciowej metody POST. Jak
wida, najpierw tworzymy wystpienie MultiPartEntity, a nastpnie wywoujemy metod
addPart() dla kadej czci. Na listingu 11.4 dodano do dania trzy czci: dwa cigi znakw oraz plik XML.
Jeeli tworzymy aplikacj wymagajc przekazania wieloczciowej metody POST do zasobu
sieciowego, prawdopodobnie zechcemy sprawdzi nasze rozwizanie pod ktem bdw,
korzystajc z pozorowanej implementacji usugi na lokalnej stacji roboczej. Podczas pracy
z aplikacjami na stacji roboczej uzyskujemy dostp do komputera lokalnego za pomoc polecenia localhost lub adresu IP 127.0.0.1. Jednak w przypadku aplikacji dla Androida nie bdziemy korzysta z polecenia localhost (ani z adresu 127.0.0.1), poniewa emulator bdzie
swoim wasnym lokalnym hostem. Nie chcemy wskazywa tego klienta usudze znajdujcej si
w urzdzeniu obsugujcym Androida, tylko nakierowa na stacj robocz. eby uzyska dostp do swojej projektowej stacji roboczej z poziomu emulatora, musimy uy jej adresu IP
(w rozdziale 2. omwilimy sposb okrelenia adresu IP stacji roboczej). Na listingu 11.4 naley
podstawi w miejsce widocznego adresu IP adres IP stacji roboczej Czytelnika.

Parsery SOAP, JSON i XML


Co z protokoem SOAP? W internecie istnieje wiele usug sieciowych bazujcych na protokole
SOAP, ale do tej pory firma Google nie wprowadzia w Androidzie bezporedniej obsugi wywoywania tych usug. Preferowane s raczej usugi oparte na architekturze REST, co na pierwszy
rzut oka powoduje zredukowanie iloci oblicze w urzdzeniu klienckim. Jednak z drugiej strony
projektant musi powici wicej pracy na wysyanie danych oraz analizowanie otrzymanych
informacji. Idealnym rozwizaniem byoby posiadanie opcji umoliwiajcych wybr sposobu
interakcji z usugami sieciowymi. Niektrzy twrcy korzystaj z zestawu projektowego kSOAP2
do tworzenia klientw opartych na protokole SOAP dla systemu Android. Nie bdziemy si
zajmowa t technologi, jednak jest ona dostpna dla zainteresowanych osb.

Rozdzia 11 Tworzenie i uytkowanie usug

343

Oryginalny kod zestawu kSOAP2 znajdziemy na stronie http://ksoap2.sourceforge.net/.


Na szczcie spoeczno ludzi piszcych projekty o otwartym rdle nie pi i utworzya
wersj zestawu kSOAP2 dla systemu Android. Wicej informacji na ten temat znajdziemy
pod adresem: http://code.google.com/p/ksoap2-android/.

Jednym z najskuteczniejszych rozwiza jest implementacja w internecie wasnych usug,


umoliwiajcych komunikowanie si protokou SOAP (lub innego) z docelow usug. Wtedy
dana aplikacja musi kontaktowa si tylko z okrelonymi usugami, a programista uzyskuje
pen kontrol. Jeeli usugi docelowe ulegaj zmianom, moemy sobie z tym poradzi bez
koniecznoci aktualizowania aplikacji i wydawania jej nowej wersji. Wystarczy, e zaktualizujemy usugi na serwerze. Dodatkow korzyci jest fakt, e moemy w atwy sposb wprowadzi model patnej subskrypcji dla aplikacji. Jeeli upynie termin subskrypcji danego uytkownika, moemy go odczy z poziomu serwera.
Android obsuguje specyfikacj JSON (ang. JavaScript Object Notation notacja obiektw
JavaScript). Jest to cakiem popularna metoda kompresji danych przesyanych pomidzy
serwerem sieciowym a klientem. Klasy parserw JSON bardzo uatwiaj rozpakowywanie
danych otrzymywanych w odpowiedzi, dziki czemu nasza aplikacja moe je przetwarza.
W dalszej czci rozdziau, w trakcie opisywania interfejsu Tumacza Google, zaprezentujemy kod zawierajcy notacj JSON.
Android posiada take szereg parserw XML, za pomoc ktrych moemy interpretowa odpowiedzi uzyskiwane z wywoa HTTP. Gwny parser (XmlPullParser) zosta omwiony
w rozdziale 3.

Obsuga wyjtkw
Obsuga wyjtkw jest czci kadego programu, jednak podczas tworzenia oprogramowania
wykorzystujcego usugi zewntrzne (na przykad usugi HTTP) trzeba zwraca szczegln uwag na wyjtki, poniewa prawdopodobiestwo wystpowania bdw zostaje zwielokrotnione.
Istnieje kilka rodzajw wyjtkw, jakich mona si spodziewa podczas korzystania z usug HTTP.
Do nich zalicza si wyjtki transportowe, wyjtki protokoowe oraz przekroczenia limitu czasu.
Naley wiedzie, kiedy te wyjtki mog si pojawi.
Wyjtki transportowe mog wystpi z wielu rnych powodw, jednak w przypadku urzdze
mobilnych najczstszym scenariuszem jest niska jako poczenia sieciowego. Wyjtki protokoowe wystpuj na poziomie warstwy protokou HTTP. Nale do nich bdy uwierzytelniania, niewaciwe pliki cookies i tak dalej. Moemy spodziewa si wyjtku protokoowego na
przykad wtedy, gdy mamy dostarczy powiadczenie tosamoci za pomoc identyfikatora
uytkownika, a tego nie uczynimy. Pod ktem wywoa protokou HTTP przekroczenia limitu
czasu dzielimy na dwie kategorie: przekroczenie limitu czasu poczenia oraz przekroczenie
limitu czasu gniazda. Przekroczenie limitu czasu poczenia nastpuje wtedy, gdy modu
HttpClient nie moe ustanowi poczenia z serwerem na przykad jeli serwer nie odpowiada. Przekroczenie limitu czasu gniazda wystpuje, gdy modu HttpClient nie otrzyma odpowiedzi w okrelonym czasie. Innymi sowy, modu ten zdoa si poczy z serwerem, lecz
serwer nie przekaza odpowiedzi w wyznaczonym limicie czasowym.
Skoro ju znamy rodzaje pojawiajcych si wyjtkw, to jak sobie z nimi poradzi? Na szczcie
modu HttpClient jest solidn struktur, zdejmujc wikszo obowizkw z barkw projektanta. Tak naprawd jedynymi wyjtkami, jakich obsug powinnimy przewidzie, s te,
ktrymi najatwiej zarzdza. Modu HttpClient obsuguje wyjtki transportowe poprzez

344 Android 3. Tworzenie aplikacji


wykrywanie problemw z przesyaniem danych i ponawianie da (w przypadku tego typu
wyjtkw opisany sposb jest bardzo skuteczny). Wyjtki protokoowe s zazwyczaj usuwane
w procesie projektowania. Prawdopodobnie najczciej bdziemy si zajmowa przekroczeniami limitu czasu. Prostym i skutecznym sposobem radzenia sobie z obydwoma rodzajami
przekrocze limitu czasu przekroczeniami czasu poczenia i przekroczeniami czasu gniazda
jest umieszczenie metody execute() dania HTTP wewntrz instrukcji try/catch i sprawdzenie, czy znowu wystpi niepowodzenie. Zostao to zaprezentowane na listingu 11.5.
Listing 11.5. Implementacja prostej techniki ponawiania prb w przypadku przekrocze limitu czasu
import
import
import
import

java.io.BufferedReader;
java.io.IOException;
java.io.InputStreamReader;
java.net.URI;

import
import
import
import

org.apache.http.HttpResponse;
org.apache.http.client.HttpClient;
org.apache.http.client.methods.HttpGet;
org.apache.http.impl.client.DefaultHttpClient;

public class TestHttpGet {


public String executeHttpGetWithRetry() throws Exception {
int retry = 3;
int count = 0;
while (count < retry) {
count += 1;
try {
String response = executeHttpGet();

/**
* Jeli tutaj trafimy, to znaczy, e prba przebiega pomylnie
* i moemy zakoczy.
*/
return response;
} catch (Exception e) {

/**
* Jeli wyczerpalimy limit powtrze.
*/
if (count < retry) {

/**
* Mamy jeszcze powtrzenia, zatem wywietlamy wiadomo
* i ponawiamy prb.
*/
System.out.println(e.getMessage());
} else {
System.out.println("wszystkie proby nieudane...");
throw e;
}
}
}
return null;

Rozdzia 11 Tworzenie i uytkowanie usug

345

}
public String executeHttpGet() throws Exception {
BufferedReader in = 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 InputStreamReader(response.getEntity()
.getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
String result = sb.toString();
return result;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

Kod na listingu 11.5 jest ilustracj moliwoci zaimplementowania prostej techniki ponawiania prb w celu wywoania protokou HTTP po przekroczeniu limitu czasu. Na listingu
zostay ukazane dwie metody: pierwsza wykonuje funkcj HTTP GET (executeHttpGet()),
druga natomiast umieszcza pierwsz metod w algorytmie ponawiania prb (executeHttpGet
WithRetry()). Algorytm ten jest niezmiernie prosty. Liczbie powtrze prb przypisujemy warto 3, a nastpnie wprowadzamy ptl while. We wntrzu tej ptli egzekwujemy
danie. Zauwamy, e jest ono umieszczone w bloku try/catch, a w bloku catch sprawdzamy, czy nie wyczerpalimy limitu powtrze.
Podczas korzystania z moduu HttpClient jako czci zwykej aplikacji musimy powici baczniejsz uwag problemom wielowtkowoci, ktre mog si pojawi. Zajmiemy si nimi teraz.

Problemy z wielowtkowoci
W dotychczas ukazanych przykadach dla kadego dania tworzylimy nowy modu HttpClient.
W praktyce jednak powinnimy tworzy jeden modu HttpClient dla caej aplikacji i uywa
go podczas wszelkiej komunikacji poprzez protok HTTP. Jeeli jeden modu HttpClient
przetwarza wszystkie dania protokou HTTP, naley rwnie uwaa na problemy zwizane
z wielowtkowoci, ktre mog si pojawi w trakcie jednoczesnego przetwarzania wielu

346 Android 3. Tworzenie aplikacji


da przez ten modu. Na szczcie posiada on funkcje uatwiajce to zadanie wystarczy
utworzy obiekt DefaultHttpClient za pomoc klasy ThreadSafeClientConnManager, tak jak
zostao to pokazane na listingu 11.6.
Listing 11.6. Utworzenie wielowtkowego moduu HttpClient
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import

org.apache.http.HttpVersion;
org.apache.http.client.HttpClient;
org.apache.http.conn.ClientConnectionManager;
org.apache.http.conn.params.ConnManagerParams;
org.apache.http.conn.scheme.PlainSocketFactory;
org.apache.http.conn.scheme.Scheme;
org.apache.http.conn.scheme.SchemeRegistry;
org.apache.http.conn.ssl.SSLSocketFactory;
org.apache.http.impl.client.DefaultHttpClient;
org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
org.apache.http.params.BasicHttpParams;
org.apache.http.params.HttpConnectionParams;
org.apache.http.params.HttpParams;
org.apache.http.params.HttpProtocolParams;
org.apache.http.protocol.HTTP;

public class CustomHttpClient {


private static HttpClient customHttpClient;

/** Prywatny konstruktor zapobiega tworzeniu wystpie */


private CustomHttpClient() {
}
public static synchronized HttpClient getHttpClient() {
if (customHttpClient == null) {
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params,
HTTP.DEFAULT_CONTENT_CHARSET);
HttpProtocolParams.setUseExpectContinue(params, true);
HttpProtocolParams.setUserAgent(params,
"Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; Nexus One Build/FRG83) AppleWebKit/533.1
(KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"
);
ConnManagerParams.setTimeout(params, 1000);
HttpConnectionParams.setConnectionTimeout(params, 5000);
HttpConnectionParams.setSoTimeout(params, 10000);
SchemeRegistry schReg = new SchemeRegistry();
schReg.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
schReg.register(new Scheme("https",
SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager conMgr = new
ThreadSafeClientConnManager(params,schReg);
customHttpClient = new DefaultHttpClient(conMgr, params);
}

Rozdzia 11 Tworzenie i uytkowanie usug

347

return customHttpClient;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}

Jeeli aplikacja musi wykonywa wicej wywoa protokou HTTP, naley utworzy modu
HttpClient obsugujcy wszystkie te dania. Najprociej moemy tego dokona poprzez
utworzenie klasy singletonowej, do ktrej dostp bdzie uzyskiwany z dowolnego miejsca aplikacji, tak jak pokazalimy powyej. Jest to standardowy wzorzec w rodowisku Java, za pomoc ktrego synchronizujemy dostp do metody pobierania, a ta z kolei przekazuje tylko i wycznie jeden obiekt HttpClient dla klasy singletonowej (i w razie koniecznoci tworzy go
za pierwszym razem).
Przyjrzyjmy si teraz metodzie getHttpClient() klasy CustomHttpClient. Jest ona odpowiedzialna za tworzenie naszego singletonowego moduu HttpClient. Wprowadzilimy pewne
podstawowe parametry, wartoci przekroczenia czasu, a take schematy obsugiwane przez nasz klas HttpClient (na przykad HTTP i HTTPS). Odnotujmy fakt, e podczas tworzenia
metody DefaultHttpClient() przekazujemy obiekt klasy ClientConnectionManager. Jest ona
odpowiedzialna za zarzdzanie poczeniami HTTP moduu HttpClient. Poniewa chcemy,
eby wszystkie poczenia HTTP przetwarza pojedynczy modu HttpClient (a to dlatego,
e dania mog si nakada na siebie w przypadku korzystania z wtku), tworzymy klas
ThreadSafeClientConnManager.
Prezentujemy take prostszy sposb uzyskiwania odpowiedzi z dania HTTP za pomoc klasy
Kod naszej aktywnoci wykorzystujcej klas CustomHttpClient
zosta ukazany na listingu 11.7.
BasicResponseHandler.

Listing 11.7. Korzystanie z klasy CustomHttpClient plik HttpActivity.java


import
import
import
import
import
import
import
import
import

java.io.IOException;
org.apache.http.client.HttpClient;
org.apache.http.client.methods.HttpGet;
org.apache.http.impl.client.BasicResponseHandler;
org.apache.http.params.HttpConnectionParams;
org.apache.http.params.HttpParams;
android.app.Activity;
android.os.Bundle;
android.util.Log;

public class HttpActivity extends Activity


{
private HttpClient httpClient;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
httpClient = CustomHttpClient.getHttpClient();
getHttpContent();

348 Android 3. Tworzenie aplikacji


}
public void getHttpContent()
{
try {
HttpGet request = new HttpGet("http://www.google.com/");
String page = httpClient.execute(request,
new BasicResponseHandler());
System.out.println(page);
} catch (IOException e) {

// obejmuje:
// ClientProtocolException
// ConnectTimeoutException
// ConnectionPoolTimeoutException
// SocketTimeoutException
e.printStackTrace();
}
}
}

W tej przykadowej aplikacji przeprowadzamy proste pobranie strony gwnej wyszukiwarki


Google poprzez HTTP. Wykorzystujemy rwnie obiekt BasicResponseHandler do przetwarzania strony w jeden olbrzymi cig znakw, ktry nastpnie zostaje wywietlony w oknie
LogCat. Jak wida, dodanie obiektu BasicResponseHandler do metody execute() jest banalnie prost czynnoci.
By moe poczujemy pokus wykorzystania faktu, e kada aplikacja Androida posiada powizany z ni obiekt Application. Jeeli nie zdefiniujemy wasnego obiektu aplikacji, Android
domylnie bdzie korzysta z obiektu android.app.Application. Maa ciekawostka dotyczca
obiektu aplikacji: dla danej aplikacji zawsze bdzie istnia tylko jeden obiekt Application i wszystkie jej skadniki mog uzyskiwa do niego dostp (za pomoc obiektu globalnego kontekstu).
Istnieje moliwo rozszerzenia klasy Application oraz dodania nowych elementw, na przykad naszej klasy CustomHttpClient. W naszym przypadku jednak nie ma potrzeby, aby dokonywa tego w samej klasie Application i najlepiej bdzie jej nie modyfikowa, gdy moemy
w atwy i przyjemny sposb utworzy oddzieln klas singletonow, co rozwie nasze problemy.

Zabawa z przekroczeniami limitu czasu


Istnieje rwnie mnstwo innych zalet wynikajcych z umieszczenia pojedynczego obiektu
klasy HttpClient w naszej aplikacji. Moemy modyfikowa jej waciwoci w jednym miejscu i kady moe na tym skorzysta. Jeeli na przykad chcemy skonfigurowa wsplne
wartoci przekroczenia limitu czasu dla wywoa HTTP, moemy tego dokona w trakcie tworzenia obiektu HttpClient poprzez wywoanie odpowiednich metod ustawiania wobec
obiektu HttpParams. Widzimy to na listingu 11.6 i w metodzie getHttpClient(). Zwrmy
uwag, e mamy wpyw na trzy rodzaje przekroczenia limitu czasu. Pierwszy z nich to przekroczenie limitu czasu w menederze pocze definiujemy tutaj czas oczekiwania, zanim
poczenie zostanie usunite z puli pocze zarzdzanych przez ten meneder. W naszym
przykadzie ustawiamy warto przekroczenia czasu rwn 1 sekundzie. Jedynym przypadkiem, w ktrym bdziemy musieli czeka, jest moment, gdy wszystkie poczenia z puli s zajte. Druga warto przekroczenia limitu czasu okrela, jak dugo powinnimy czeka, zanim

Rozdzia 11 Tworzenie i uytkowanie usug

349

poczenie z serwerem zostanie zakoczone. Dalimy w tym przypadku 2 sekundy. Na kocu


zdefiniowalimy czterosekundow warto przekroczenia limitu czasu dla gniazda, co oznacza
czas oczekiwania na otrzymanie danych danych.
W zwizku z trzema rodzajami przekroczenia limitu czasu opisanymi w poprzednim akapicie moemy wprowadzi trzy wyjtki: ConnectionPoolTimeoutException, ConnectTimeout
Exception albo SocketTimeoutException. Wszystkie trzy wyjtki s podelementami klasy
IOException, ktr wykorzystalimy w klasie HttpActivity, dziki czemu obyo si bez
oddzielnego wprowadzania kadej podklasy wyjtku.
Jeeli przeanalizujemy kad klas wykorzystan w metodzie getHttpClient(), pozwalajc
na konfigurowanie ustawie, bardzo moliwe, e znajdziemy tam o wiele wicej interesujcych
parametrw.
Opisalimy sposb konfigurowania wsplnej puli pocze HTTP, wykorzystywanej przez ca
aplikacj. Moemy wywnioskowa, e za kadym razem, gdy jest potrzebne poczenie, wymagania te zostan zaspokojone dziki rnorodnym ustawieniom. A co jeli w przypadku konkretnego komunikatu potrzebne s odmienne ustawienia? Na szczcie i na to znajdzie si sposb.
Pokazalimy mechanizm wykorzystania obiektw HttpGet oraz HttpPost do przesania dania przez sie. W podobny sposb, jak wykorzystujemy obiekt HttpParams wobec obiektu
HttpClient, moemy go ustanowi rwnie dla obiektw HttpGet oraz HttpPost. Ustawienia
wprowadzane na poziomie komunikatu bd przesaniay ustawienia dostpne w obiekcie
HttpClient, ktre jednak nie zostan zmienione. Listing 11.8 przedstawia sytuacj, w ktrej
chcielibymy zmieni warto przekroczenia limitu czasu na gniedzie z 4 sekund na jedn minut dla jednego, konkretnego dania. Poniszy fragment kodu powinien zosta wprowadzony na miejsce wierszy znajdujcych si w bloku try metody getHttpContent(), widocznej
na listingu 11.7.
Listing 11.8. Przesanianie na poziomie dania wartoci przekroczenia czasu na gniedzie
HttpGet request = new HttpGet("http://www.google.com/");
HttpParams params = request.getParams();
HttpConnectionParams.setSoTimeout(params, 60000); // 1 minuta
request.setParams(params);
String page = httpClient.execute(request,
new BasicResponseHandler());
System.out.println(page);

Stosowanie klasy HttpURLConnection


W Androidzie znajdziemy jeszcze jeden sposb radzenia sobie z usugami HTTP korzystanie
z klasy java.net.HttpURLConnection. Jest ona do podobna do prezentowanych wczeniej
klas HttpClient, jednak wymaga wikszej liczby instrukcji do dziaania. Wybr, z ktrej klasy
skorzysta, zaley od programisty.

Uywanie klasy AndroidHttpClient


W wersji 2.2 Androida wprowadzono nowy element potomny klasy HttpClient, noszcy nazw AndroidHttpClient. Zadaniem tej klasy jest uatwienie pracy programicie aplikacji dla
Androida poprzez zapewnienie domylnych wartoci i logiki zoptymalizowanych pod ktem

350 Android 3. Tworzenie aplikacji


tego systemu. Na przykad wartoci przekroczenia limitu czasu dla poczenia i gniazda (przykadowo operacji) posiadaj domyln warto po 20 sekund. Meneder poczenia uzyskuje wartoci domylne klasy ThreadSafeClientConnManager. W wikszoci przypadkw
klasa ta moe by stosowana wymiennie z pokazan w poprzednich przykadach klas HttpClient. Istnieje jednak kilka rnic, o ktrych powinnimy wiedzie.
Aby utworzy klas AndroidHttpClient, wywoujemy statyczn metod
newInstance() teje klasy, na przykad w nastpujcy sposb:
AndroidHttpClient httpClient = AndroidHttpClient.newInstance
("moj-ciag-znakow-agenta=http");

Zwrmy uwag, e parametrem metody newInstance() jest cig znakw agenta


HTTP. W domylnej przegldarce Android cig ten wyglda nastpujco, ale
moemy rwnie dobrze skorzysta z odmiennych wartoci:
Mozilla/5.0 (Linux; U; Android 2.1; en-us; ADR6200 Build/ERD79) AppleWebKit/530.17
(KHTML, like Gecko) Version/ 4.0 Mobile Safari/530.17
Wywoywanie metody execute() wobec tego klienta musi nastpowa z poziomu
wtku rnego od gwnego wtku interfejsu uytkownika. Oznacza to, e jeli
sprbujemy po prostu podmieni klas HttpClient klas AndroidHttpClient,
wystpi wyjtek. Przeprowadzanie wywoa HTTP w gwnym wtku jest bardzo
zym nawykiem, dlatego klasa AndroidHttpClient na to nie pozwala. W nastpnym
podrozdziale bdziemy zajmowa si zagadnieniem wtkw.
Po zakoczeniu uywania wystpienia klasy AndroidHttpClient musimy wywoa
metod close(). W ten sposb pami zostanie poprawnie zwolniona.
Dostpnych jest kilka przydatnych metod, przetwarzajcych skompresowane
odpowiedzi ze strony serwera, wrd nich takie jak:
modifyRequestToAcceptGzipResponse(HttpRequest request),
getCompressedEntity(byte[] data, ContentResolver resolver),
getUngzippedContent(HttpEntity entity).

Po uzyskaniu wystpienia klasy AndroidHttpClient nie moemy w nim zmieni ani doda
adnego parametru (na przykad wersji protokou HTTP). Nasze opcje maj suy do przesaniania ustawie wewntrz obiektu HttpGet (co pokazalimy ju wczeniej) albo nie powinnimy korzysta z klasy AndroidHttpClient.
Na tym zakoczymy dyskusj dotyczc przetwarzania usug HTTP za pomoc moduu
HttpClient. W kolejnych podrozdziaach przedstawimy kolejny interesujcy element Androida:

tworzenie usug przetwarzanych w tle i nastawionych na dugie dziaanie. Chocia pocztkowo


nie jest to oczywiste, procesy tworzenia wywoa protokou HTTP i tworzenia usug w Androidzie s ze sob powizane w taki sposb, e czsto bdziemy musieli integrowa wiele elementw
za pomoc usug Androida. Wemy na przykad prost aplikacj pocztow. W urzdzeniu obsugujcym system Android taka aplikacja bdzie si prawdopodobnie skadaa z dwch czci:
jedna bdzie dostarcza interfejs uytkownika, druga natomiast bdzie sprawdzaa, czy s dostpne nowe wiadomoci. Proces sprawdzania bdzie najprawdopodobniej przeprowadzany
w tle. Skadnik odpowiedzialny za sprawdzanie bdzie usug Androida, czego skutkiem z kolei
bdzie wykorzystywanie w tym celu moduu HttpClient.
Na stronie firmy Apache http://hc.apache.org/httpcomponents-client-ga/tutorial/html/
znajdziemy wietny samouczek dotyczcy klasy HttpClient oraz pozostaych koncepcji.

Rozdzia 11 Tworzenie i uytkowanie usug

351

Stosowanie wtkw drugoplanowych (AsyncTask)


Do tej pory wykorzystywalimy w przykadach gwny wtek aktywnoci do wywoa protokou
HTTP. Chocia szczliwie mona otrzymywa za kadym razem byskawiczne odpowiedzi na
wywoania, poczenia sieciowe i internetowe nie musz by wcale takie szybkie. Poniewa gwny
wtek aktywnoci przede wszystkim ma przetwarza zdarzenia generowane przez uytkownika
(np. kliknicia) oraz aktualizowa interfejs uytkownika, do przeprowadzania operacji mogcych
zajmowa troch wicej czasu powinnimy uywa wtkw drugoplanowych. Android niejako
wymusza na programistach takie rozwizanie, poniewa jeeli gwny wtek zatrzyma swoje
dziaanie na czas piciu sekund, zostanie uruchomiony warunek ANR (ang. Application Not
Responding aplikacja nie odpowiada), ktry ostatecznie zakoczy dziaanie programu, gdy
pojawi si brzydkie okno dialogowe zawierajce monit o potwierdzenie zamknicia aplikacji
(jest to tak zwane wymuszone zamknicie). W rozdziale 13. przyjrzymy si dokadniej gwnemu
wtkowi, na razie jednak wystarczy nam wiedza, e nie moemy zajmowa gwnego wtku
jedn czynnoci przez duszy czas.
Jeeli jedynym zadaniem w danej chwili jest dokonywanie oblicze bez koniecznoci aktualizowania interfejsu uytkownika, moglibymy zwyczajnie wykorzysta prosty obiekt Thread,
ktry odciyby nieco gwny wtek. Technika ta jednak nie jest odpowiednia, jeli trzeba aktualizowa interfejs uytkownika. Dlatego wanie zestaw narzdziowy interfejsu uytkownika
w Androidzie nie jest zabezpieczony przed wtkami. A zatem powinien by on zawsze aktualizowany wycznie z poziomu gwnego wtku.
Jeeli zamierzamy aktualizowa interfejs uytkownika w jakikolwiek sposb w wyniku dziaania
wtku drugoplanowego, powinnimy si powanie zastanowi nad implementacj klasy
AsyncTask. Umoliwia ona wygodne przenoszenie do drugiego planu pewnych operacji, ktre
wymagaj aktualizowania interfejsu uytkownika. Klasa AsyncTask zapewnia utworzenie wtku
drugoplanowego, w ktrym bd przeprowadzane dane operacje, a take dziaajcych w gwnym
wtku wywoa zwrotnych, dajcych atwy dostp do elementw interfejsu uytkownika (np.
widokw). Wywoania te mog zosta uruchomione przed, w trakcie lub po wczeniu wtku
drugoplanowego.
Rozwamy na przykad problem pobierania obrazu z serwera sieciowego, ktry bdzie wywietlany w naszej aplikacji. By moe obraz ten bdzie musia by rekonstruowany w locie. Nie
moemy zagwarantowa, po jakim czasie obraz ten zostanie wywietlony uytkownikowi, wic
w tym celu rzeczywicie trzeba zastosowa wtek drugoplanowy.
Listing 11.9 przedstawia prost implementacj klasy AsyncTask, ktra wykona za nas ca
brudn robot. Najpierw j omwimy, a nastpnie zaprezentujemy plik ukadu graficznego oraz
kod Java aktywnoci wywoujcej t funkcj.
Listing 11.9. Klasa AsyncTask odpowiedzialna za pobieranie obrazu plik DownloadImageTask.java
import
import
import
import
import
import
import
import
import
import

java.io.IOException;
org.apache.http.HttpResponse;
org.apache.http.client.HttpClient;
org.apache.http.client.methods.HttpGet;
org.apache.http.params.BasicHttpParams;
org.apache.http.params.HttpConnectionParams;
org.apache.http.params.HttpParams;
org.apache.http.util.EntityUtils;
android.app.Activity;
android.content.Context;

352 Android 3. Tworzenie aplikacji


import
import
import
import
import
import

android.graphics.Bitmap;
android.graphics.BitmapFactory;
android.os.AsyncTask;
android.util.Log;
android.widget.ImageView;
android.widget.TextView;

public class DownloadImageTask extends AsyncTask<String, Integer, Bitmap> {


private Context mContext;
DownloadImageTask(Context context) {
mContext = context;
}
protected void onPreExecute() {

// Moglibymy tutaj wprowadzi jakie czynnoci konfiguracyjne przed uruchomieniem


// metody doInBackground()
}
protected Bitmap doInBackground(String... urls) {
Log.v("doInBackground", "pobieranie obrazu");
return downloadImage(urls);
}
protected void onProgressUpdate(Integer... progress) {
TextView mText = (TextView)
((Activity) mContext).findViewById(R.id.text);
mText.setText("Dotychczasowy postp: " + progress[0]);
}
protected void onPostExecute(Bitmap result) {
if(result != null) {
ImageView mImage = (ImageView)
((Activity) mContext).findViewById(R.id.image);
mImage.setImageBitmap(result);
}
else {
TextView errorMsg = (TextView)
((Activity) mContext).findViewById(R.id.errorMsg);
errorMsg.setText("Problem z pobraniem obrazu. Prosimy sprbowa pniej.");
}
}
private Bitmap downloadImage(String... urls)
{
HttpClient httpClient = CustomHttpClient.getHttpClient();
try {
HttpGet request = new HttpGet(urls[0]);
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setSoTimeout(params, 60000); // 1 minuta
request.setParams(params);
publishProgress(25);
HttpResponse response = httpClient.execute(request);
publishProgress(50);

Rozdzia 11 Tworzenie i uytkowanie usug

353

byte[] image = EntityUtils.toByteArray(response.getEntity());


publishProgress(75);
Bitmap mBitmap = BitmapFactory.decodeByteArray(
image, 0, image.length);
publishProgress(100);
return mBitmap;
} catch (IOException e) {

// Obejmuje:
// ClientProtocolException
// ConnectTimeoutException
// ConnectionPoolTimeoutException
// SocketTimeoutException
e.printStackTrace();
}
return null;
}

Poniewa AsyncTask jest klas abstrakcyjn, musimy j dostosowa za pomoc rozszerzania,


w czym pomocna okazuje si klasa DownloadImageTask. Wykorzystamy tu konstruktor pobierajcy odniesienie do wywoujcego kontekstu, ktry w naszym przypadku bdzie wywoujc aktywnoci. Kontekst ten posuy nam do uzyskania dostpu do widokw aktywnoci. Uywamy tu rwnie znanej z wczeniejszych przykadw klasy CustomHttpClient.
Przygotowanie funkcji AsyncTask do pracy skada si z czterech etapw:
1. Wszelkie dziaania konfiguracyjne przeprowadzamy w metodzie onPreExecute().
Jest ona wykonywana w gwnym wtku.
2. Za pomoc metody doInBackground() uruchamiamy wtek drugoplanowy. Samo
tworzenie wtku nie wymaga naszego udziau. Kod ten jest wykonywany w oddzielnym
wtku drugoplanowym.
3. Aktualizujemy postpy za pomoc metod publishProgress() i onProgressUpdate().
Ta pierwsza metoda zostaje wywoana z wntrza kodu metody doInBackground(),
podczas gdy ta druga jest wykonywana w gwnym wtku jako wynik wywoania metody
publishProgress(). Za pomoc tych dwch metod uruchomiony wtek drugoplanowy
moe komunikowa si z wtkiem gwnym, zatem w interfejsie uytkownika mog by
dokonywane aktualizacje stanu, jeszcze zanim wtek drugoplanowy zakoczy swoje zadanie.
4. Interfejs uytkownika zostaje zaktualizowany o wyniki za pomoc metody
onPostExecute(). Metoda ta jest wykonywana w gwnym wtku.
Etapy 1. i 3. s opcjonalne. W naszym przykadzie nie przeprowadzilimy procesu inicjalizacji
w metodzie onPreExecute(), jednak zastosowalimy mechanizm aktualizacji postpw
omwiony w punkcie 3. Zasadnicza praca wtku drugoplanowego jest wykonywana przez
metod downloadImage() wywoywan z poziomu metody doInBackground(). Metoda
downloadImage() przyjmuje adres URL i wykorzystuje klas HttpClient do wykonania dania
metody HttpGet oraz uzyskania odpowiedzi. Zwrmy uwag, e czas wyganicia ustawiamy
na 60 sekund bez obawy o wystpienie warunku ANR. Widzimy w kodzie, e postpy s aktualizowane na etapach konfigurowania poczenia HttpClient, wykonywania dania HTTP,

354 Android 3. Tworzenie aplikacji


konwertowania odpowiedzi na tablic bajtw i tworzenia z niej obiektu klasy Bitmap. Kiedy
metoda downloadImage() powraca do metody doInBackground(), ktra z kolei rwnie powraca, Android zajmuje si pobraniem otrzymanej wartoci i przekazaniem jej do metody
onPostExecute(). Gdy zostanie do niej przekazany obiekt klasy Bitmap, mona za jego pomoc
bezpiecznie zaktualizowa widok ImageView, poniewa metoda ta dziaa w gwnym wtku
aktywnoci. Co jednak w przypadku wystpienia jakiego wyjtku na etapie pobierania? Jeeli
nie otrzymamy obrazu z wywoania HTTP, a zamiast niego pojawi si wyjtek, obiekt Bitmap
uzyska warto null. Wykrywamy ten fakt w metodzie onPostExecute() i wywietlamy
komunikat o bdzie, nie prbujemy natomiast przydzieli obiektu klasy Bitmap do widoku
ImageView. Oczywicie, moemy rwnie wykona jak inn czynno, jeli wiemy, e
proces pobierania zosta zakoczony niepowodzeniem.
Musimy pamita, e jedynie kod umieszczony w metodzie doInBackground() jest wykonywany poza gwnym wtkiem. Nie moemy wic pracowa z interfejsem uytkownika wewntrz tej metody, poniewa w przeciwnym wypadku moemy spowodowa problemy. Na
przykad nie naley wywoywa z jej wntrza metod modyfikujcych elementy interfejsu
uytkownika. Elementy interfejsu uytkownika moemy modyfikowa jedynie w metodach
onPreExecute(), onProgressUpdate() oraz onPostExecute().
Uzupenijmy nasz najnowszy przykad o plik ukadu graficznego oraz kod Java aktywnoci,
ktre zostan umieszczone, odpowiednio, na listingach 11.10 i 11.11.
Listing 11.10. Ukad graficzny sucy do wywoywania klasy AsyncTask plik /res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<Button android:id="@+id/button" android:text="Pobierz obraz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doClick"
/>
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<TextView android:id="@+id/errorMsg" android:textColor="#ff0000"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<ImageView android:id="@+id/image"
android:layout_width="fill_parent" android:layout_height="0dip"
android:layout_weight="1" />
</LinearLayout>

Rozdzia 11 Tworzenie i uytkowanie usug

355

Listing 11.11. Aktywno wywoujca klas AsyncTask plik HttpActivity.java


import
import
import
import
import

android.app.Activity;
android.os.AsyncTask;
android.os.Bundle;
android.util.Log;
android.view.View;

public class HttpActivity extends Activity {


private DownloadImageTask diTask;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void doClick(View view) {
if(diTask != null) {
AsyncTask.Status diStatus = diTask.getStatus();
Log.v("doClick", "status diTask wynosi" + diStatus);
if(diStatus != AsyncTask.Status.FINISHED) {
Log.v("doClick", "...nie ma potrzeby, aby uruchomi nowe zadanie");
return;
}

// Poniewa diStatus musi posiada warto FINISHED, moemy sprbowa ponownie.


}
diTask = new DownloadImageTask(this);
diTask.execute("http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:15.3,
20.3,0.
2,59.7,4.5&chl=Android%201.5%7CAndroid%201.6%7COther*%7CAndroid%202.1%7CAndroid%
202.2&ch
co=c4df9b,6fad0c");
}
}

Po uruchomieniu aplikacji i naciniciu przycisku ujrzymy ekran przedstawiony na rysunku 11.1.


Ukad graficzny nie jest skomplikowany. Widzimy przycisk oraz obok niego komunikat tekstowy. Tekst ten bdzie naszym wskanikiem postpw. Pod spodem mamy miejsce na komunikat o bdzie, ktry zostanie wywietlony czerwon czcionk. Na kocu znajduje si obszar
przeznaczony dla rysunku.
Wewntrz metody zwrotnej przycisku doClick() musimy utworzy nowe wystpienie naszej
skonfigurowanej klasy AsyncTask oraz wywoa metod execute(). Jest to bardzo powszechny
wzorzec. Tworzymy wystpienie rozszerzenia klasy AsyncTask i wywoujemy metod execute().
W naszym przykadzie wywoujemy usug tworzenia wykresw firmy Google, ktra przyjmuje
wartoci danych, nazwy etykiet i tworzy z nich wykres, ktry jest prezentowany w postaci
obrazu PNG. Zanim jednak uruchomimy nasze zadanie, powinnimy starannie sprawdzi, czy
nie zostao ju uruchomione. Jeeli uytkownik kliknie przycisk dwukrotnie, moe si okaza,
e w tle s przetwarzane dwa zadania. Na szczcie klasa AsyncTask pozwala nam sprawdzi stan
zadania. Jeeli warto zmiennej diTask nie wynosi null, istnieje prawdopodobiestwo, e

356 Android 3. Tworzenie aplikacji

Rysunek 11.1. Wykorzystanie klasy AsyncTask do pobrania obrazu (wykres dostpnoci


poszczeglnych wersji Androida na rynku na dzie 2 sierpnia 2010 roku)

jakie zadanie jest ju uruchomione. Sprawdzamy wic stan klasy AsyncTask. Jeeli ma on
jakkolwiek inn warto ni FINISHED, to znaczy, e zadanie jest uruchomione (RUNNING)
lub oczekujce (PENDING) i gotowe do uruchomienia. A zatem jeli dla danego zadania klasa
AsyncTask pozostaje w stanie FINISHED, moemy je uzna za zakoczone i utworzy now
klas AsyncTask. Oczywicie, jeli poprzednie wystpienie klasy AsyncTask mogo z powodzeniem pobra obraz, moemy nie chcie pobiera go ponownie. Jednak w naszym przykadzie
pozwalamy na jego ponowne pobranie.
W trakcie dziaania naszej przykadowej aplikacji po wciniciu przycisku pojawi si aktualizowany komunikat o postpach pracy, a po chwili rysunek. Przycisk przechodzi ze stanu kliknicia
w stan normalny, jeszcze zanim komunikat o postpach zacznie si aktualizowa. Jest to bardzo
wana obserwacja, poniewa oznacza ona, e gwny wtek powrci do zarzdzania interfejsem
uytkownika w trakcie pobierania obrazu.
Z czystej ciekawoci przejdmy do adresu URL kierujcego aplikacj na stron z wykresami
i wprowadmy jak zmian, ktra zepsuje ten cig znakw. Wczmy teraz ponownie aplikacj. Wynik powinien by podobny do przedstawionego na rysunku 11.2.

Rysunek 11.2. Komunikat o wyjtku przekazany do interfejsu uytkownika

Warto dowiedzie si jeszcze kilku rzeczy na temat klasy AsyncTask. Po utworzeniu rozszerzenia klasy AsyncTask i uruchomieniu metody execute() nasz gwny wtek wraca do
przetwarzania kodu. Cigle jednak posiadamy odniesienie do zadania i moemy na nim
dziaa z poziomu gwnego wtku. Na przykad moemy wywoa metod cancel(), aby zakoczy to zadanie. Za pomoc metody isCancelled() sprawdzamy, czy zostao ono anulowane. Moemy chcie zmodyfikowa logik w metodzie onPostExecute() pod ktem
anulowania zadania. Z kolei klasa AsyncTask zawiera dwie odmiany metody get(), w ktrych
wynik otrzymujemy z metody doInBackground(), bez koniecznoci wykorzystywania metody

Rozdzia 11 Tworzenie i uytkowanie usug

357

onPostExecute(). Jedna z tych odmian metody get() blokuje korzystanie z metody onPost
Execute(), podczas gdy druga korzysta z wartoci przekroczenia limitu czasu zapobie-

ga w ten sposb zbyt dugiemu oczekiwaniu przez wtek wywoujcy.


Klasa AsyncTask moe by uruchomiona tylko jednokrotnie. Jeli wic przechowujemy odniesienie do tej klasy, nie wywoujmy metody execute() wicej ni raz, w przeciwnym wypadku
zostanie wywietlona informacja o wyjtku. Nic nie stoi na przeszkodzie, aby utworzy nowe
wystpienia klasy AsyncTask, jednak kada z tych instancji bdzie uruchomiona tylko jednokrotnie. Dlatego za kadym razem tworzymy nowe wystpienie klasy DownloadImageTask, gdy
jest nam ono potrzebne.

Obsuga zmian konfiguracji za pomoc klasy AsyncTask


Istnieje jednak jeszcze jedna niezwykle wana cecha klasy AsyncTask, o ktrej musimy wiedzie.
Mianowicie nie zadziaa ona, jeeli uruchamiajca j aktywno zostanie zakoczona lub ponownie utworzona. To naprawd istotna rzecz. Oczywicie, jeeli wywoanie zwrotne metody
onPostExecute() zostanie przeprowadzone wobec pierwotnej aktywnoci, ktra jednak w aplikacji zostaa zastpiona przez now aktywno, klasa AsyncTask bdzie aktualizowaa widoki niewidoczne dla uytkownika. Jak to moliwe, e aktywno zostanie zakoczona i ponownie
odtworzona? Tak naprawd procesy te zachodz przez cay czas. Po kadej modyfikacji konfiguracji urzdzenia, na przykad po zmianie trybu wywietlania obrazu z pionowego na poziomy,
aktywno zostanie zakoczona i odtworzona.
Warto pamita, e w czasie tworzenia interfejsu uytkownika Android okrela, za pomoc
konfiguracji urzdzenia, ktre ukady graficzne i zasoby maj zosta wykorzystane. Jest to do
skomplikowany mechanizm, wic najatwiejszym i najszybszym rozwizaniem dla systemu jest
zakoczenie biecej aktywnoci i odtworzenie jej za pomoc nowej konfiguracji. Na szczcie
nie wszystkie dotychczas utworzone obiekty zostaj utracone, zakoczeniu nie ulega dziaanie
m.in. klasy AsyncTask. Poniewa klasa ta jest przetwarzana w osobnym wtku aplikacji, cigle
jest dostpna w trakcie ponownego tworzenia aktywnoci. Naszym zadaniem jest utworzenie
poczenia pomidzy nimi, tak aby klasa AsyncTask odnalaza widoki mieszczce si w nowej
aktywnoci. Aktywno posiada, odpowiedzialne za to zadanie, funkcj zwrotn oraz metody.
S to, odpowiednio, onRetainNonConfigurationInstance() oraz getLastNonConfiguration
Instance(). Zasadniczo obiekty te zapewniaj przekazywanie obiektu ze starej aktywnoci do nowej (listing 11.12).
Listing 11.12. Nowa wersja pliku HttpActivity.java. Plik obsuguje proces rekonfiguracji
import
import
import
import
import

android.app.Activity;
android.os.AsyncTask;
android.os.Bundle;
android.util.Log;
android.view.View;

public class HttpActivity extends Activity {


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

358 Android 3. Tworzenie aplikacji


// Poniszy fragment sprawdza, czy uruchamiamy ponownie aktywno
// w obecnoci klasy AsyncTask. Jeli tak, zostaje ponownie ustanowione poczenie.
// Poza tym w przypadku zakoczenia dziaania klasy AsyncTask obraz nie zosta
// zachowany w trakcie cyklu koczenia/tworzenia aktywnoci, naley wic
// pobra ten obraz z klasy AsyncTask.
if( (diTask =
(DownloadImageTask)getLastNonConfigurationInstance())
!= null)
{
diTask.setContext(this); // Daje klasie AsyncTask nowe

// odniesienie do aktywnoci.
if(diTask.getStatus() == AsyncTask.Status.FINISHED)
diTask.setImageInView();
}
}
public void doClick(View view) {
if(diTask != null) {
AsyncTask.Status diStatus = diTask.getStatus();
Log.v("doClick", "Stan diTask wynosi " + diStatus);
if(diStatus != AsyncTask.Status.FINISHED) {
Log.v("doClick", "...nie trzeba uruchamia nowego zadania");
return;
}

// Poniewa diStatus musi posiada warto FINISHED, moemy sprbowa ponownie.


}
diTask = new DownloadImageTask(this);
diTask.execute("http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:15.3,
20.3,0.
2,59.7,4.5&chl=Android%201.5%7CAndroid%201.6%7COther*%7CAndroid%202.1%7CAndroid%
202.2&ch
co=c4df9b,6fad0c");
}

// Metoda ta zostaje wywoana przed metod onDestroy(). Chcemy zawczasu przekaza


// odniesienie do klasy AsyncTask.
@Override
public Object onRetainNonConfigurationInstance() {
return diTask;
}
}

Powyszy przykadowy kod jest bardzo podobny do aktywnoci HttpActivity widocznej na listingu 11.11. Rnica midzy nimi polega na wywoaniu metody getLastNonConfiguration
Instance() w celu sprawdzenia, czy w starym wystpieniu tej aktywnoci znajduje si obiekt
DownloadImageTask. Jeeli istnieje taki obiekt, musimy przekaza mu odniesienie do nowej
aktywnoci i w ten sposb da mu dostp do nowych widokw. Na listingu 11.13 ujrzymy
zmodyfikowany kod klasy DownloadImageTask. Po ustanowieniu nowego kontekstu w klasie
DownloadImageTask sprawdzamy, czy zadanie zostao zakoczone, poniewa mogo si tak
sta w trakcie odtwarzania klasy HttpActivity. Jeli stwierdzimy zakoczenie zadania, stosujemy metod setImageInView() do zaktualizowania obrazu wicej informacji na ten temat
znajdzie si poniej.

Rozdzia 11 Tworzenie i uytkowanie usug

359

Listing 11.13. Zmodyfikowany plik DownloadImageTask.java, obsugujcy zmian konfiguracji


urzdzenia
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import

java.io.IOException;
org.apache.http.HttpResponse;
org.apache.http.client.HttpClient;
org.apache.http.client.methods.HttpGet;
org.apache.http.params.BasicHttpParams;
org.apache.http.params.HttpConnectionParams;
org.apache.http.params.HttpParams;
org.apache.http.util.EntityUtils;
android.app.Activity;
android.content.Context;
android.graphics.Bitmap;
android.graphics.BitmapFactory;
android.os.AsyncTask;
android.util.Log;
android.widget.ImageView;
android.widget.TextView;

public class DownloadImageTask extends AsyncTask<String, Integer, Bitmap> {


private Context mContext; // Odniesienie do wywoujcej aktywnoci
int progress = -1;
Bitmap downloadedImage = null;
DownloadImageTask(Context context) {
mContext = context;
}

// Wywoywane z gwnego wtku w celu ponownego poczenia


protected void setContext(Context context) {
mContext = context;
if(progress >= 0) {
publishProgress(this.progress);
}
}
protected void onPreExecute() {
progress = 0;

// Moemy tutaj wprowadzi inne ustawienia konfiguracyjne,


// zanim zostanie uruchomiona metoda doInBackground()
}
protected Bitmap doInBackground(String... urls) {
Log.v("doInBackground", "pobieranie obrazu...");
return downloadImage(urls);
}
protected void onProgressUpdate(Integer... progress) {
TextView mText = (TextView)
((Activity) mContext).findViewById(R.id.text);
mText.setText("Dotychczasowy postp: " + progress[0]);
}
protected void onPostExecute(Bitmap result) {
if(result != null) {

360 Android 3. Tworzenie aplikacji


downloadedImage = result;
setImageInView();
}
else {
TextView errorMsg = (TextView)
((Activity) mContext).findViewById(R.id.errorMsg);
errorMsg.setText("Wystpi problem z pobieraniem pliku. Prosimy sprbowa
pniej.");
}
}
public Bitmap downloadImage(String... urls)
{
HttpClient httpClient = CustomHttpClient.getHttpClient();
try {
HttpGet request = new HttpGet(urls[0]);
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setSoTimeout(params, 60000); // 1 minuta
request.setParams(params);
setProgress(25);
HttpResponse response = httpClient.execute(request);
setProgress(50);
sleepFor(5000);

// 5 sekund hibernacji

byte[] image = EntityUtils.toByteArray(response.getEntity());


setProgress(75);
Bitmap mBitmap = BitmapFactory.decodeByteArray(image, 0,
image.length);
setProgress(100);
return mBitmap;
} catch (IOException e) {

// Obejmuje:
// ClientProtocolException
// ConnectTimeoutException
// ConnectionPoolTimeoutException
// SocketTimeoutException

e.printStackTrace();
}
return null;

private void setProgress(int progress) {


this.progress = progress;
publishProgress(this.progress);
}
protected void setImageInView() {
if(downloadedImage != null) {
ImageView mImage = (ImageView)

Rozdzia 11 Tworzenie i uytkowanie usug

361

((Activity) mContext).findViewById(R.id.image);
mImage.setImageBitmap(downloadedImage);
}
}

private void sleepFor(long msecs) {


try {
Thread.sleep(msecs);
} catch (InterruptedException e) {
Log.v("sleep", "przerwano");
}
}

Powinnimy zwrci uwag, e procedura obsugi przycisku doClick() pozostaje niezmieniona. Teraz jednak posiadamy implementacj opart na funkcji onRetainNonConfiguration
Instance(), ktrej jedynym zadaniem jest przekazywanie zwracanego obiektu. W naszym
przypadku zaley nam wycznie na obiekcie DownloadImageTask, wic tylko ten obiekt musimy przekaza. Gdybymy chcieli przekaza wicej elementw, musielibymy utworzy obiekt
przechowujcy je wszystkie, a nastpnie przekaza ten obiekt. Mamy do czynienia z podstawowymi obiektami rodowiska Java, nie musimy wic przejmowa si serializacj ani obiektami typu Parcel (w dalszej czci rozdziau zajmiemy si tymi obiektami). Klas AsyncTask przekazujemy wycznie po to, aby j wykorzysta nieco pniej. Moemy wic ponownie nawiza
z ni poczenie.
Zachcamy teraz do uruchomienia naszej przykadowej aplikacji. Dodalimy piciosekundowe opnienie do klasy AsyncTask, aby da Czytelnikowi czas na obrcenie ekranu w trakcie
dziaania drugoplanowego zadania. Aby zasymulowa obrt ekranu urzdzenia za pomoc
emulatora, uywamy skrtu klawiaturowego Ctrl+F11. Interfejs uytkownika powinien si
obrci i odpowiednio dostosowa do pooenia wywietlacza. Z kadym obrotem pobrany
obraz znika (jeli by widoczny) i pojawia si ponownie w trakcie przetwarzania kodu. Warto
obraca ekran na rnych etapach dziaania aplikacji, aby sprawdzi efekty. Moemy nawet
wrci do poprzedniego przykadu, doda opnienie i przekona si, e w trakcie obracania
nie zachowuje si on zgodnie z yczeniem uytkownika. Przyjrzymy si teraz nowemu kodowi,
aby zrozumie mechanizm jego dziaania.
Tym razem klasa rozszerzajca klas AsyncTask jest nieco inna.
W trakcie zmiany konfiguracji dzieje si kilka rzeczy, z ktrymi musimy sobie poradzi. Przede
wszystkim trzeba wiedzie, e klasa AsyncTask musi otrzyma nowe odniesienie do aktywnoci, aby mc aktualizowa odpowiednie widoki zarwno w metodzie onProgressUpdate(),
jak i onPostExecute(). Po zakoczeniu starej aktywnoci odniesienie do niej staje si bezuyteczne i potrzebujemy nowego. Gdybymy zmienili jaki element w interfejsie uytkownika
umieszczonym w metodzie onPreExecute(), musielibymy rwnie w niej zamieci odniesienie do nowej aktywnoci. Teraz mona wykorzysta metod nazwan setContext(), dziki
ktrej kontekst jest aktualizowany wraz z pozostaymi obiektami, wic w razie potrzeby nie
powinnimy mie problemw ze znalezieniem widokw.
Poza tym nieco inaczej zapewniamy obsug aktualizacji postpw. W dalszym cigu korzystamy z obiektu postpw, do ktrego moemy si odnosi w metodzie setContext(), a take
w metodzie setProgress(). Wywoujemy teraz metod setProgress() w odpowiednich

362 Android 3. Tworzenie aplikacji


miejscach metody downloadImage(). Gdy w kocu poczymy si z now aktywnoci, chcemy
natychmiast wywietli aktualny postp pobierania obrazu, zatem ustanawiamy metod publish
Progress() w metodzie setContext().
Wreszcie, obrazy nie s utrzymywane w trakcie cyklu zakaczania odtwarzania aktywnoci. Jeeli aktywno zostanie odtworzona przed zakoczeniem dziaania klasy AsyncTask,
wszystko bdzie w porzdku, poniewa metoda onPostExecute() ustawi now bitmap. Jeli
jednak funkcja AsyncTask zostaa zakoczona jaki czas temu, a my obrcimy wywietlacz,
aktywno zostanie odtworzona, jednak obraz nie zostanie wywietlony. Moglibymy ponownie
pobra obraz z serwera, jednak w naszym przykadzie zatrzymalimy t bitmap za pomoc nowego czonka downloadedImage oraz wprowadzilimy chronion metod setImageInView(),
ktra zacza rysunek do obiektu ImageView. Jak ju wczeniej powiedzielimy, nie chcemy
przechowywa elementu interfejsu uytkownika (na przykad klasy View) wewntrz klasy
AsyncTask. Dlatego wanie przechowujemy bitmap, a nie widok ImageView. Nie chcemy, eby
nastpi wyciek pamici poprzez odniesienia do widokw utworzonych w starej aktywnoci.

Pobieranie plikw za pomoc klasy DownloadManager


W pewnych warunkach aplikacja moe pobiera wikszy plik na urzdzenie. Poniewa ten proces moe zaj nieco czasu, a kod, ktry go obsuguje, mg zosta znormalizowany, w wersji
2.3 Androida wprowadzono specjaln klas, ktrej jedynym zadaniem jest pobieranie duych
plikw DownloadManager. DownloadManager pobiera due pliki na lokalny nonik urzdzenia, dziaajc w wtku drugoplanowym. Istnieje moliwo skonfigurowania tej klasy w taki
sposb, eby wywietlaa uytkownikowi powiadomienie o procesie pobierania.
W kolejnej prezentowanej aplikacji wykorzystujemy klas DownloadManager do pobrania jednego z plikw zestawu Android SDK. Ten przykadowy projekt skada si z nastpujcych
plikw:
res/layout/main.xml (listing 11.14),
MainActivity.java (listing 11.15),
AndroidManifest.xml (listing 11.16).
Listing 11.14. Korzystanie z klasy DownloadManager plik /res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<Button android:onClick="doClick" android:text="Start"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView android:id="@+id/tv"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>

Na ukad graficzny projektu skada si jeden przycisk i jedno pole tekstowe. Wcinicie przycisku spowoduje rozpoczcie pobierania, a w polu tekstowym bd wywietlane informacje dotyczce rozpoczcia oraz zakoczenia tego procesu. Interfejs uytkownika zosta zaprezentowany na rysunku 11.3.

Rozdzia 11 Tworzenie i uytkowanie usug

Rysunek 11.3. Interfejs uytkownika w aplikacji DownloadManagerDemo

Z kolei listing 11.15 przedstawia kod Java naszej aplikacji.


Listing 11.15. Korzystanie z klasy DownloadManager plik MainActivity.java
import
import
import
import
import
import
import
import
import
import
import

android.app.Activity;
android.app.DownloadManager;
android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.content.IntentFilter;
android.net.Uri;
android.os.Bundle;
android.util.Log;
android.view.View;
android.widget.TextView;

public class MainActivity extends Activity {


protected static final String TAG = "DownloadMgr";
private DownloadManager dMgr;
private TextView tv;
private long downloadId;

/**Wywoywane podczas pierwszego utworzenia aktywnoci. */


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

tv = (TextView)findViewById(R.id.tv);

@Override
protected void onResume() {
super.onResume();
dMgr = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
}
public void doClick(View view) {
DownloadManager.Request dmReq = new DownloadManager.Request(
Uri.parse(
"http://dl-ssl.google.com/android/repository/" +
"platform-tools_r01-linux.zip"));
dmReq.setTitle("Narzdzia dla platformy");
dmReq.setDescription("Wersja dla systemu Linux");
dmReq.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE);
IntentFilter filter = new
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);

363

364 Android 3. Tworzenie aplikacji


registerReceiver(mReceiver, filter);
downloadId = dMgr.enqueue(dmReq);
tv.setText("Pobieranie rozpoczte... (" + downloadId + ")");
}
public BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
long doneDownloadId =
extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
tv.setText(tv.getText() + "\nPobieranie zakoczone (" +
doneDownloadId + ")");
if(downloadId == doneDownloadId)
Log.v(TAG, "Nasze pobieranie zostao ukoczone.");
}
};

@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mReceiver);
dMgr = null;
}

Zrozumienie tego kodu nie powinno nastrcza wikszych trudnoci. Najpierw inicjalizujemy
gwny widok i otrzymujemy odniesienie do pola tekstowego. Wewntrz metody onResume()
uzyskujemy odniesienie do usugi DOWNLOAD_SERVICE. Zauwamy, e usuwamy porednio
z tej usugi w metodzie onPause(). Metoda obsugujca kliknicie doClick() generuje nowe
danie DownloadManager.Request za pomoc cieki do pliku, ktry chcemy pobra. Ponadto
definiujemy tytu, opis i rodzaj preferowanego typu sieci. Istnieje jeszcze kilka dodatkowych
opcji do wyboru. Zostay one szczegowo opisane w dokumentacji.
W celach demonstracyjnych jako typ poczenia wybralimy sie komrkow, moemy jednak
zdefiniowa sie Wi-Fi (za pomoc wartoci NETWORK_WIFI zamiast NETWORK_MOBILE), moemy
te poczy obydwie wartoci za pomoc operatora OR. Domylnie obydwa rodzaje sieci mog
pobiera dane, co w przypadku naszej aplikacji oznacza, e bdziemy korzysta z sieci komrkowej, nawet jeli bdzie dostpna sie Wi-Fi.
Po zdefiniowaniu danego obiektu utworzylimy i zarejestrowalimy filtr dla odbiorcy komunikatw. Wkrtce przedstawimy kod tego odbiorcy. Dziki jego zarejestrowaniu uytkownik
zostanie poinformowany o zakoczeniu pobierania kadego pliku. Oznacza to, e musimy ledzi identyfikator dania, odsyany po wywoaniu metody enqueue() wobec klasy Download
Manager. Na koniec aktualizujemy w interfejsie uytkownika informacj o stanie, tym samym
uytkownik bdzie informowany o rozpoczynaniu pobierania.
Aby aplikacja zadziaaa, musimy zdefiniowa kilka uprawnie w pliku AndroidManifest.xml,
co zostao ukazane na listingu 11.16. Wrd wymaganych uprawnie musimy zadeklarowa
dostp do internetu oraz moliwo zapisywania plikw na karcie SD. Co dziwne, jeeli w wersji
2.3 Androida nie wprowadzimy tych uprawnie, pojawi si komunikat o bdzie, informujcy
o braku uprawnie ACCESS_ALL_DOWNLOADS, mimo e uprawnienie to nie jest naszej przykadowej
aplikacji potrzebne.

Rozdzia 11 Tworzenie i uytkowanie usug

365

Listing 11.16. Korzystanie z klasy DownloadManager plik AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.services.download"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="9" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

Po uruchomieniu aplikacji powinnimy ujrze przycisk. Po jego klikniciu Android rozpocznie


pobieranie i wywietli komunikat widoczny na rysunku 11.3. Zauwamy, e widoczna jest ikona
pobierania w pasku powiadomie (lewy grny rg ekranu). Gdyby umieci wskanik myszy
na tej ikonie, zostaoby wywietlone okno powiadomie, widoczne na rysunku 11.4.

Rysunek 11.4. Informacja o pobieraniu widoczna na licie powiadomie

Powiadomieniem w tym przypadku jest wywietlenie paska postpu pobierania pliku. Po zakoczeniu pobierania element ten zostanie wyczyszczony, a w oknie aplikacji pojawi si dodatkowe informacje, widoczne na rysunku 11.5.

Rysunek 11.5. Okno aplikacji informujce o zakoczeniu pobierania

366 Android 3. Tworzenie aplikacji


Teraz naley przeanalizowa intencj w odbiorcy komunikatw. W ten sposb mona si przekona, czy zakoczone pobieranie byo czci naszej aplikacji. Jeli tak, trzeba jeszcze tylko
zaktualizowa komunikat o stanie w interfejsie uytkownika. Nie zapominajmy, e nie mona
zbyt mocno rozbudowywa odbiorcy komunikatw, gdy musimy szybko wrci z metody
onReceive(). Moemy na przykad zamiast tego przywoa usug przetwarzajc pobrany
plik. W jej wntrzu moglibymy wstawi fragment kodu zaprezentowany na listingu 11.17,
umoliwiajcy przejrzenie zawartoci pliku.
Listing 11.17. Odczytywanie pobranego pliku
try {
ParcelFileDescriptor pfd = dMgr.openDownloadedFile(doneDownloadId);

// Posiadamy teraz uchwyt w trybie odczytu, przeznaczony do pobranego pliku


// W dalszej czci odczytujemy plik...
} catch (FileNotFoundException e) {
e.printStackTrace();
}

Jednym ze sposobw zlokalizowania pobranego pliku jest wykorzystanie usugi klasy Download
Manager. Klasa ta wymaga zdefiniowanego identyfikatora pobierania. Widzielimy to na listingu 11.17. Klasa DownloadManager zapewnia rozpoznawanie pobranego pliku po identyfikatorze
pobierania. Chocia nasza aplikacja umiecia ten plik w publicznym obszarze karty SD, moemy
w rzeczywistoci wskaza, e zostanie umieszczony w rejonie prywatnych danych aplikacji za
pomoc jednej z metod typu setDestination*() obiektu DownloadManager.Request.
Klasa DownloadManager posiada wasn aplikacj, dziki ktrej mona przejrze list pobranych plikw. Na poziomie menu aplikacji na emulatorze lub w urzdzeniu poszukajmy ikony
przedstawionej na rysunku 11.6.

Rysunek 11.6. Ikona aplikacji Downloads

Dziki tej aplikacji moemy rwnie uzyska dostp do tych plikw. Sprbujmy ju teraz. Po
uruchomieniu programu Downloads ujrzymy okno zaprezentowane na rysunku 11.7. Menu
widoczne w dolnej czci ekranu pojawi si dopiero w momencie zaznaczenia pola wyboru znajdujcego si przy nazwie pobranego pliku, co zrobilimy tu przed wykonaniem zrzutu ekranu.
Klasa DownloadManager zawiera dostawc treci przechowujcego informacje o pobranym pliku.
Aplikacja Downloads po prostu uzyskuje dostp do tego dostawcy w celu wygenerowania listy
plikw dostpnych dla uytkownika. Oznacza to, e my rwnie moemy przebada tego dostawc wewntrz naszej aplikacji, aby zdoby dane o pobranych plikach. W tym celu wykorzystujemy zapytanie DownloadManager.Query oraz metod query(). Nie mamy tu jednak do
dyspozycji zbyt wielu opcji wyszukiwania. Moemy wyszukiwa pliki za pomoc identyfikatorw
pobierania (jednego bd kilku) lub pod ktem stanu pobierania. Wynikiem metody query()
jest obiekt Cursor, ktry moe zosta wykorzystany do sprawdzenia wierszy w dostawcy treci
klasy DownloadManager. List dostpnych kolumn znajdziemy w dokumentacji tej klasy, a niektre

Rozdzia 11 Tworzenie i uytkowanie usug

367

Rysunek 11.7. Aplikacja Downloads

z nich to lokalny identyfikator Uri pobranego pliku, rozmiar pliku w bajtach, rodzaj pliku, status
pobierania oraz kilka innych parametrw. Gdy uzyskamy w ten sposb dostp do dostawcy
treci, musimy doda w pliku AndroidManifest.xml uprawnienie ACCESS_ALL_DOWNLOADS.
Moemy rwnie wykorzysta metod remove() do anulowania procesu pobierania, chocia
znajdujcy si na karcie fragment pliku nie zostanie automatycznie usunity.
Pokazalimy, w jaki sposb mona korzysta z usug opartych na protokole HTTP, a take jak
zarzdza interfejsem tych usug za pomoc wyspecjalizowanej klasy AsyncTask. Typowo stosujemy j w sytuacji, w ktrej jaka operacja trwa przez okrelony, niezbyt dugi czas oraz ktrej
wyniki bezporednio wpywaj w jaki sposb na interfejs uytkownika. Niekiedy jednak trzeba
uruchomi jaki proces przetwarzania danych trwajcy przez duszy czas albo przywoa jak
niezwizan z interfejsem uytkownika funkcj, istniejc w osobnej aplikacji. Do takich
celw mona wykorzysta usugi systemu Android. Zajmiemy si teraz ich omwieniem.

Stosowanie usug w Androidzie


W Androidzie zdefiniowano pojcie usug. Przez usugi rozumiemy skadniki dziaajce w tle,
nieposiadajce interfejsu uytkownika. Przypominaj one usugi systemu Windows albo demony systemu Unix. Podobnie jak w przypadku wymienionych usug, usugi w Androidzie s
zawsze dostpne, lecz nie musz by bez przerwy aktywne. Co waniejsze, cykle ycia usug nie
pokrywaj si z cyklami ycia aktywnoci. Podczas wstrzymywania, zatrzymywania lub zamykania aktywnoci moemy mie jeszcze do czynienia z przetwarzaniem danych, ktre musi by
kontynuowane. Do tego celu znakomicie nadaj si wanie usugi.
Istniej dwa rodzaje usug w Androidzie: usugi lokalne i usugi zdalne. Usuga lokalna nie jest
dostpna z poziomu innych aplikacji uruchomionych w urzdzeniu. Zasadniczo usuga tego typu
jest dostpna jedynie dla aplikacji, ktra j wywoaa, a dla innych programw uruchomionych
w urzdzeniu ju nie. W przypadku usug zdalnych dostp do nich jest uzyskiwany z poziomu aplikacji, ktre je wywoay, jak rwnie z poziomu innych programw. Usugi zdalne s
definiowane wobec klientw za pomoc jzyka AIDL (ang. Android Interface Definition Language
jzyk definicji interfejsu systemu Android). Zapoznamy si z obydwoma rodzajami usug.
W kilku nastpnych rozdziaach zagbimy si w zagadnienia dotyczce usug lokalnych, zatem
wstpnie je tutaj opiszemy. Nie bdziemy jednak wdawa si w szczegy. W tym rozdziale dokadniej przedstawimy usugi zdalne.

368 Android 3. Tworzenie aplikacji

Usugi w Androidzie
Klasa Service stanowi oson dla kodu wykazujcego zachowanie charakterystyczne dla usug.
W przeciwiestwie do omawianej wczeniej klasy AsyncTask, obiekt klasy Service nie generuje
automatycznie wasnych wtkw. Niezbdna jest ingerencja programisty, aby obiekt Service
mg korzysta z wtkw. Oznacza to, e w przypadku braku funkcji wtkowania w usudze jej
kod bdzie przetwarzany w gwnym wtku. Jeli usuga prowadzi czynnoci zajmujce niewiele
czasu, nie powinno to stanowi problemu. Wtkowanie staje si niezbdne w przypadku operacji
zajmujcych wicej czasu. Pamitajmy, e nie ma adnych przeciwskaza co do wykorzystania
klasy AsyncTask do wtkowania wewntrz usug.
Android wykorzystuje koncepcj usug z dwch powodw:
Po pierwsze, ma to na celu uatwienie implementacji zada przetwarzanych w tle.
Po drugie, aby zapewni komunikacj midzyprocesow dla aplikacji
uruchomionych na jednym urzdzeniu.
Te dwa powody odpowiadaj dwm konkretnym rodzajom usug w Androidzie: usugom
lokalnym i usugom zdalnym. W pierwszym przypadku jako przykad mona wskaza usug
lokaln, zaimplementowan jako cz aplikacji pocztowej. Usuga ta zajmowaaby si wysyaniem wiadomoci na serwer pocztowy wraz z zacznikami i ponowieniami. Poniewa proces ten
moe zabra troch czasu, usuga stanowi dobre rozwizanie. Pozwala na osonicie funkcji, dziki
czemu mona je przenie z gwnego wtku, a nastpnie wrci do obsugi da uytkownika.
W dodatku, nawet jeli aktywno aplikacji pocztowej zostanie zakoczona, wiadomoci wci
musz zosta dostarczone. Jak si przekonamy, przykadem usugi wykorzystanej z drugiego
powodu jest omwiona w dalszej czci rozdziau aplikacja do tumaczenia tekstu. Zamy, e
na urzdzeniu uruchomiono kilka aplikacji korzystajcych z jednego tekstu. W tym czasie, gdy
aplikacje te pracuj, potrzebujemy usugi, ktra przyjmuje tekst do przetumaczenia z jednego
jzyka na drugi. Zamiast umieszcza odpowiedni kod w kadej aplikacji, moemy napisa
zdaln usug tumaczc i sprawi, eby aplikacje si z ni komunikoway.
Istniej pewne istotne rnice pomidzy usugami lokalnymi a zdalnymi. cilej mwic, jeeli
usuga jest uywana wycznie przez skadniki w obrbie jednego procesu, klient musi j uruchomi za pomoc wywoania metody Context.startService(). Jest to usuga lokalna, poniewa jej celem jest, zasadniczo, uruchamianie zada przetwarzanych w tle dla aplikacji, ktra
uruchomia t usug. Jeeli usuga obsuguje metod onBind(), mamy do czynienia z jej wersj zdaln, ktr mona wywoa za pomoc komunikacji midzyprocesowej (Context.bind
Service()). Usugi zdalne s rwnie nazywane usugami obsugujcymi jzyk AIDL,
poniewa klienty porozumiewaj si z nimi za pomoc jzyka AIDL.
Chocia interfejs klasy android.app.Service obsuguje zarwno usugi lokalne, jak i zdalne,
wdroenie jednej implementacji usugi, ktra obsugiwaaby obydwa rodzaje usug, nie jest dobrym pomysem. Wynika to z faktu, e kady typ usugi posiada predefiniowany cykl ycia;
poczenie obydwu rodzajw, chocia jest dopuszczalne, moe powodowa bdy.
Moemy teraz przeprowadzi szczegow analiz obydwu kategorii usug. Zaczniemy od
omwienia usug lokalnych, a nastpnie przejdziemy do usug zdalnych (usug obsugujcych
jzyk AIDL). Jak ju wspomnielimy, usuga lokalna jest wywoywana jedynie przez zarzdzajc ni aplikacj. Usugi zdalne obsuguj mechanizm wywoania RPC (ang. Remote Procedure Call zdalne wywoanie procedury). Usuga tego typu umoliwia zewntrznym klientom,
istniejcym na tym samym urzdzeniu, podczenie do niej i korzystanie z jej funkcji.

Rozdzia 11 Tworzenie i uytkowanie usug

369

Drugi rodzaj usugi nosi w Androidzie rne nazwy: usuga zdalna, usuga obsugujca
jzyk AIDL, usuga AIDL, usuga zewntrzna oraz usuga RPC. Nazwy te dotycz tego
samego typu usugi pozwalajcej na uzyskanie do niej dostpu zdalnego przez
aplikacje uruchomione na urzdzeniu.

Usugi lokalne
Usugi lokalne s usugami uruchamianymi za pomoc metody Context.startService(). Ten
typ usug bdzie dziaa od chwili uruchomienia do momentu wywoania przez klienta metody
Context.stopService() wobec usugi lub do czasu, gdy usuga sama nie wywoa metody
stopSelf(). Zwrmy uwag, e podczas wywoania metody Context.startService(),
w przypadku gdy usuga nie zostaa jeszcze utworzona, system j utworzy i wywoa jej metod
onStartCommand(). Musimy pamita, e ponowne wywoanie metody Context.startService()
nie spowoduje utworzenia nowej instancji usugi (jeeli ju istnieje), lecz przywoa ponownie
metod onStartCommand() ju istniejcej usugi. Poniej przedstawiamy przykady usug
lokalnych:
Usuga monitorujca odczyty z czujnikw urzdzenia oraz przeprowadzajca analiz
danych, a po spenieniu okrelonych warunkw wywietlajca alert. Usuga ta moe
dziaa nieprzerwanie.
Usuga wykonujca zadania, umoliwiajca aktywnociom aplikacji zgaszanie
czynnoci do wykonania oraz kolejkujca je. Usuga ta moe dziaa wycznie przez
okres zgaszania tych czynnoci.
Na listingu 11.18 przedstawiono przykad usugi lokalnej, stanowicej implementacj usugi
wykonujcej zadania w tle. Efektem naszej pracy s cztery artefakty niezbdne do utworzenia
oraz uytkowania usugi: plik BackgroundService.java (sama usuga), plik MainActivity.java
(tworzy klas aktywnoci wywoujcej usug), plik main.xml (ukad graficzny aktywnoci)
oraz plik AndroidManifest.xml. Na listingu 11.18 umieszczono wycznie kod z pliku
BackgroundService.java. Najpierw przeanalizujemy t klas, a nastpnie zajmiemy si pozostaymi trzema plikami. Ponisza implementacja wymaga co najmniej wersji 2.0 Androida.
Listing 11.18. Implementowanie usugi lokalnej plik BackgroundService.java
import
import
import
import
import
import
import

android.app.Notification;
android.app.NotificationManager;
android.app.PendingIntent;
android.app.Service;
android.content.Intent;
android.os.IBinder;
android.util.Log;

public class BackgroundService extends Service


{
private static final String TAG = "BackgroundService";
private NotificationManager notificationMgr;
private ThreadGroup myThreads = new ThreadGroup("ServiceWorker");
@Override
public void onCreate() {
super.onCreate();

370 Android 3. Tworzenie aplikacji


Log.v(TAG, "w onCreate()");
notificationMgr =(NotificationManager)getSystemService(
NOTIFICATION_SERVICE);
displayNotificationMessage("Usuga drugoplanowa zostaa uruchomiona");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
int counter = intent.getExtras().getInt("counter");
Log.v(TAG, "w onStartCommand(), counter = " + licznik +
", startId = " + startId);
new Thread(myThreads, new ServiceWorker(counter),
"BackgroundService")
.start();
return START_STICKY;
}
class ServiceWorker implements Runnable
{
private int counter = -1;
public ServiceWorker(int counter) {
this.counter = counter;
}
public void run() {
final String TAG2 = "ServiceWorker:" +
Thread.currentThread().getId();

// w tym miejscu znajduj si operacje wykonywane w tle reszta odpoczywa


try {
Log.v(TAG2, "hibernacja przez 10 sekund. licznik = " +
counter);
Thread.sleep(10000);
Log.v(TAG2, "...wybudzanie");
} catch (InterruptedException e) {
Log.v(TAG2, "...hibernacja przerwana");
}
}
}
@Override
public void onDestroy()
{
Log.v(TAG, "w onDestroy(). Przerywanie watkow i anulowanie powiadomien.");
myThreads.interrupt();
notificationMgr.cancelAll();
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
Log.v(TAG, "w onBind()");
return null;

Rozdzia 11 Tworzenie i uytkowanie usug

371

}
private void displayNotificationMessage(String message)
{
Notification notification =
new Notification(R.drawable.emo_im_winking,
message, System.currentTimeMillis());
notification.flags = Notification.FLAG_NO_CLEAR;
PendingIntent contentIntent =
PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0);
notification.setLatestEventInfo(this, TAG, message,
contentIntent);
notificationMgr.notify(0, notification);
}
}

Struktura obiektu Service jest nieco podobna do architektury aktywnoci. Mamy do dyspozycji
metod onCreate() suc do inicjalizacji, a take metod onDestroy() odpowiedzialn za
zakoczenie dziaania usugi. W wersjach Androida starszych od 2.0 usugi posiaday metod
onStart(), ktra poczwszy od wersji 2.0 zostaa przemianowana na onStartCommand().
Rnica pomidzy tymi metodami polega na wstawieniu parametru flagi w tym drugim przypadku, dziki ktremu usuga otrzymuje informacje o ponownym dostarczeniu intencji lub
wskazanie, czy usuga powinna zosta ponownie uruchomiona. W naszym przykadzie wykorzystujemy metod onStartCommand(). Usugi nie s wstrzymywane ani wznawiane w taki
sposb jak aktywnoci, zatem nie ujrzymy w nich metod onPause() ani onResume(). Poniewa
mamy do czynienia z usug lokaln, nie bdziemy wprowadzali adnego mechanizmu wizania,
jednak klasa Service wymaga implementacji metody onBind(), wic wprowadzimy jedn
metod odsyajc po prostu warto null.
Wracajc do metody onCreate() nie musimy robi zbyt wiele, wystarczy powiadomi uytkownika o utworzeniu usugi. Dokonujemy tego za pomoc klasy NotificationManager.
Prawdopodobnie Czytelnik zauway ju pasek powiadomie, znajdujcy si w lewym grnym
rogu ekranu Androida. Po jego rozcigniciu uytkownik ujrzy rnorodne komunikaty, a po
klikniciu powiadomienia moe je uruchamia, co zazwyczaj oznacza powrt do aktywnoci
zwizanej z danym powiadomieniem. Poniewa usugi dziaaj (a przynajmniej istniej) w tle,
bez adnej widocznej aktywnoci, musi istnie jaki sposb uzyskania z nimi kontaktu, chociaby po to, aby je wyczy. Tworzymy wic obiekt Notification, uzupeniamy go intencj
oczekujc, dziki ktrej powrcimy do aktywnoci sterujcej, i j tam umieszczamy. Wszystkie
te czynnoci odbywaj si w metodzie displayNotificationMessage(). Jeszcze jednym bardzo wanym zadaniem jest ustawienie flagi na obiekcie Notification, dziki czemu uytkownik
nie bdzie mg usun go z listy. Istnienie tego obiektu w trakcie trwania usugi jest naprawd
niezbdne, zatem wprowadzamy atrybut Notification.FLAG_NO_CLEAR, aby przechowywa
ten obiekt na licie, dopki nie zostanie usunity za pomoc metody onDestroy(). Dokadniej
mwic, usuwanie powiadomie z listy obsuguje znajdujca si wewntrz onDestroy() metoda
cancelAll() uyta wobec klasy NotificationManager.

372 Android 3. Tworzenie aplikacji


Musimy jeszcze zapewni jedn rzecz, aby nasza przykadowa aplikacja dziaaa. Musimy utworzy obiekt klasy Drawable, nazwany emo_im_winking, i umieci go w katalogu drawable
naszego projektu. W celach demonstracyjnych dobrym rdem obiektw klasy Drawable jest
katalog zestawu Androida SDK/platforms/<wersja>/data/res/drawable, gdzie <wersja> oznacza
aktualnie wykorzystywan wersj systemu. Niestety, nie moemy komunikowa si z systemowymi obiektami klasy Drawable w taki sam sposb, jak ma to miejsce w przypadku ukadw
graficznych, musimy wic skopiowa wszystkie wymagane obiekty do katalogu drawable.
Jeeli chcemy korzysta z innego obiektu typu Drawable, wystarczy zmieni identyfikator
zasobu w konstruktorze klasy Notification.
Po wysaniu intencji do usugi za pomoc metody startService() w razie koniecznoci zostaje
rwnie wywoana metoda onCreate(), a take w celu otrzymania intencji metoda
onStartCommand(). W naszym przypadku nie bdziemy niczego szczeglnego robi, moe poza
rozpakowaniem licznika oraz rozpoczciem dziaania wtku drugoplanowego. W usudze ustanowionej na potrzeby rzeczywistej aplikacji spodziewalibymy si przekazania jakichkolwiek
danych za pomoc intencji, dajmy na to identyfikatorw URI. Warto zauway, e do utworzenia wtku uywamy klasy ThreadGroup. Pniej, gdy bdziemy chcieli pozby si drugoplanowych wtkw, okae si to przydatne. Zwrmy rwnie uwag na parametr startId. Zostaje
on ustanowiony przez system i stanowi unikatowy identyfikator wywoa usugi od samego
momentu jej utworzenia.
Klasa ServiceWorker jest typow klas uruchamialn i to wanie w niej jest wykonywana caa
praca. W naszym przypadku zapisujemy pewne informacje w dzienniku oraz wstrzymujemy na
chwil dziaanie usugi. Na pewno nie manipulujemy interfejsem uytkownika; nie aktualizujemy na przykad widokw. Poniewa nie dziaamy ju w poziomu gwnego wtku, nie moemy bezporednio wpywa na interfejs uytkownika. Klasa ServiceWorker posiada pewne
mechanizmy umoliwiajce wprowadzanie zmian w interfejsie uytkownika; wrcimy do
ich omawiania w kilku najbliszych rozdziaach.
Ostatnim obiektem, na ktry warto zwrci uwag, omawiajc klas BackgroundService, jest
metoda onDestroy(). To za jej pomoc koczymy dziaanie usugi. W naszym przykadzie pokaemy, jak pozby si uprzednio utworzonych wtkw, jeeli jeszcze jakie bd istniay. Jeeli
tego nie zrobimy, mog pozostawa w tle i zajmowa zasoby pamici. Poza tym naley usun
powiadomienie. Jeli usuga zostanie zamknita, uytkownik nie musi uruchamia aktywnoci,
aby samodzielnie wyczy t usug. W standardowej aplikacji moemy jednak zechcie, aby
niektre wtki normalnie pracoway dalej. Jeeli nasza usuga wysya wiadomoci e-mail, zwyke
zamykanie wtkw na pewno nam nie pomoe. By moe pokazujemy nieco zbyt uproszczony
przykad, poniewa Czytelnik moe si zasugerowa, e za pomoc metody interrupt()
mona zamyka drugoplanowe wtki. W rzeczywistoci jednak ta metoda pozwala najwyej na
ich przerywanie, a to nie jest jednoznaczne z zamkniciem wtku. Istniej pewne przestarzae
metody, suce do zamykania wtkw, nie powinnimy ich jednak stosowa. Mog one powodowa problemy ze stabilnoci i pamici. W omawianym przykadowym kodzie sprawdzaj
si przerwania, poniewa korzystamy z hibernacji, ktr take mona przerywa.
Warto powici chwil na zapoznanie si z klas ThreadGroup, poniewa zawiera ona mechanizmy umoliwiajce uzyskanie dostpu do wtkw. Utworzylimy w naszej usudze jeden
obiekt ThreadGroup, ktry nastpnie by uywany podczas generowania poszczeglnych wtkw. Wewntrz metody onDestroy() stosujemy po prostu metod interrupt() wobec obiektu
ThreadGroup i tym samym przerywamy kady wtek stanowicy cz tego obiektu.

Rozdzia 11 Tworzenie i uytkowanie usug

373

Znamy wic ju podstawy tworzenia prostej usugi lokalnej. Zanim ukaemy kod aktywnoci, zapoznajmy si najpierw z zawartoci pliku ukadu graficznego, zaprezentowan na listingu 11.19.
Listing. 11.19. Implementacja usugi lokalnej plik main.xml
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/startBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rozpocznij usug" android:onClick="doClick" />
<Button android:id="@+id/stopBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Zakocz usug" android:onClick="doClick" />
</LinearLayout>

Interfejs uytkownika skada si z dwch przyciskw. Wcinicie jednego powoduje wywoanie


metody startService(), a drugiego metody stopService(). Moglibymy zamiast nich wprowadzi kontrolk ToggleButton, wtedy jednak nie moglibymy wywoa metody startService()
kilka razy pod rzd. Jest to wane spostrzeenie. Nie istnieje bezporednia relacja pomidzy
metodami startService() a stopService(). Po wywoaniu metody stopService() usuga
zostanie zakoczona, tak samo jak wszystkie wtki utworzone za pomoc wszystkich wystpie metody startService(). Nasza przykadowa aplikacja wymaga wartoci 5 parametru
minSdkVersion, poniewa stosujemy nowsz metod onStartCommand() w miejscu starszej
onStart(). Zatem moemy rwnie skorzysta z atrybutu android:onClick znacznika Button
w naszym pliku ukadu graficznego. Przyjrzyjmy si teraz kodowi aktywnoci, zaprezentowanemu na listingu 11.20.
Listing 11.20. Implementacja usugi lokalnej plik MainActivity.java
// MainActivity.java
import
import
import
import
import

android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.util.Log;
android.view.View;

public class MainActivity extends Activity


{
private static final String TAG = "MainActivity";
private int counter = 1;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

374 Android 3. Tworzenie aplikacji


setContentView(R.layout.main);
}
public void doClick(View view) {
switch(view.getId()) {
case R.id.startBtn:
Log.v(TAG, "Uruchamianie uslugi... licznik = " + counter);
Intent intent = new Intent(MainActivity.this,
BackgroundService.class);
intent.putExtra("counter", counter++);
startService(intent);
break;
case R.id.stopBtn:
stopService();
}
}
private void stopService() {
Log.v(TAG, "Zatrzymywanie uslugi...");
if(stopService(new Intent(MainActivity.this,
BackgroundService.class)))
Log.v(TAG, "metoda stopService zakonczona sukcesem");
else
Log.v(TAG, "metoda stopService zakonczona niepowodzeniem");
}
@Override
public void onDestroy()
{
stopService();
super.onDestroy();
}
}

Nasza aktywno MainActivity nie rni si zbytnio od innych aktywnoci, jakie wczeniej
tworzylimy. Istnieje prosta metoda onCreate(), suca do konfigurowania interfejsu uytkownika za pomoc pliku ukadu graficznego main.xml. Mamy rwnie do dyspozycji metod
doClick(), obsugujc wywoania zwrotne przycisku. W naszym przykadzie wywoujemy
metod startService() po wciniciu przycisku Rozpocznij usug oraz metod stopService()
po wciniciu przycisku Zakocz usug. W momencie uruchomienia usugi chcemy przekaza
jej pewne dane, czego dokonujemy za pomoc intencji. Wybralimy przekazanie tych danych
w pakiecie Extras, ale gdybymy posiadali identyfikator URI, moglibymy tego dokona za pomoc metody setData(). W czasie zatrzymywania usugi sprawdzamy otrzymane wyniki.
W normalnych warunkach powinnimy otrzyma warto true, jeli jednak usuga nie bya
uruchomiona, moe zosta przekazana warto false. Na koniec, jeli zamykamy aktywno,
chcemy rwnie zatrzyma usug, zatem dokonujemy tego w metodzie onDestroy(). Pozosta
nam jeszcze jeden plik do omwienia AndroidManifest.xml, ktry widzimy na listingu 11.21.
Listing 11.21. Implementacja usugi lokalnej plik AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.services.simplelocal"
android:versionCode="1"

Rozdzia 11 Tworzenie i uytkowanie usug

375

android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="BackgroundService"/>
</application>
<uses-sdk android:minSdkVersion="5" />
</manifest>

Poza standardowymi znacznikami <activity> widzimy take znacznik <service>. Poniewa


mamy do czynienia z usug lokaln, wywoywan jawnie za pomoc nazwy klasy, nie musimy
umieszcza zbyt wielu informacji w tym wle. Wymagana jest jedynie nazwa usugi. Jest jednak jeszcze jedna istotna kwestia dotyczca tego pliku manifestu. Nasza usuga tworzy powiadomienie, dziki ktremu uytkownik moe powrci do aktywnoci MainActivity, na
przykad w przypadku wcinicia przycisku startowego bez uprzedniego zatrzymania usugi.
Aktywno MainActivity jest wci obecna, lecz niewidoczna. Jednym ze sposobw jej przywrcenia jest kliknicie powiadomienia utworzonego przez nasz usug. Na pewno nie chcemy,
aby obok ju istniejcego, niewidocznego wystpienia aktywnoci MainActivity zostao utworzone nowe jej wystpienie. Aby tak si nie stao, wprowadzamy w pliku AndroidManifest.xml
atrybut android:launchMode i nadajemy mu warto singleTop. W ten sposb zapewnimy
wysunicie istniejcej, niewidzialnej aktywnoci MainActivity na pierwszy plan i wywietlenie jej na ekranie zamiast tworzenia jej nowego wystpienia.
Po uruchomieniu aplikacji ujrzymy dwa przyciski. Po klikniciu przycisku Rozpocznij usug
zostanie utworzony obiekt usugi oraz wywoana metoda onStartCommand(). Nasz kod generuje
kilka informacji w oknie LogCat, zatem moemy ledzi jego prac. Teraz mona kilkakrotnie
klikn przycisk Rozpocznij usug mona prbowa klika szybko. Zobaczymy tworzone
wtki, obsugujce oddzielnie kade danie. Zauwaymy take, e warto licznika jest przekazywana poprzez wszystkie wtki obiektu ServiceWorker. Po wciniciu przycisku Zatrzymaj
usug nasza usuga zostanie zakoczona i w oknie LogCat ujrzymy komunikaty pochodzce
z metod stopService() aktywnoci MainActivity, onDestroy() usugi BackgroundService
oraz prawdopodobnie z wtkw ServiceWorker (jeli zostay przerwane).
Powinnimy rwnie ujrze powiadomienie po uruchomieniu usugi. Gdy ju bdzie dziaaa,
wcinijmy przycisk cofania. Zauwaymy, e po zamkniciu aktywnoci zniknie powiadomienie. Oznacza to, e zakoczylimy rwnie usug. Aby ponownie uruchomi aktywno
MainActivity, wystarczy klikn przycisk Uruchom usug, co spowoduje rwnie wczenie
usugi. Wcinijmy teraz przycisk ekranu startowego. Aktywno znika z ekranu, ale powiadomienie pozostaje, co oznacza, e usuga wci istnieje. Kliknijmy je, a aktywno zostanie
ponownie wywietlona na ekranie.
Zwrmy uwag, e w tym przykadzie wykorzystujemy aktywno do komunikowania si
z usug, ale kady skadnik aplikacji moe rwnie korzysta z tej usugi. Dotyczy to innych
usug, aktywnoci, oglnych klas itd. Odnotujmy take fakt, e nasza usuga nie zatrzyma si

376 Android 3. Tworzenie aplikacji


samoistnie; jest pod tym wzgldem zalena od aktywnoci. Istniej jednak pewne metody pozwalajce usudze na samoistne zatrzymanie, chociaby takie jak stopSelf() czy stopSelfResult().
BackgroundService jest typowym przykadem usugi wykorzystywanej przez skadniki aplikacji przechowujcej t usug. Inaczej mwic, aplikacja przechowujca t usug jest jednoczenie jej jedynym konsumentem. Poniewa usuga nie obsuguje klientw znajdujcych
si poza jej procesem, jest ona lokalna. Z tego wynika, e w przeciwiestwie do usugi zdalnej,
odsya warto null w metodzie bind(). Zatem jedynym sposobem powizania obiektu z t
usug jest wywoanie metody Context.startService(). Najwaniejszymi metodami usugi
lokalnej s onCreate(), onStartCommand(), stop*() oraz onDestroy().

W przypadku usugi lokalnej mamy do dyspozycji jeszcze jedn opcj, wykorzystywan w przypadku uruchomienia tylko jednego jej wystpienia wraz z tylko jednym wtkiem. W takim przypadku w metodzie onCreate() tej usugi moemy utworzy wtek przetwarzajcy wszystkie operacje usugi. Moemy go umieci w metodzie onCreate() zamiast w onStartCommand(). Jest
to dopuszczalne, poniewa metoda onCreate() jest wywoywana tylko raz, a my potrzebujemy
tylko jednego wtku istniejcego przez czas trwania usugi. Nie otrzymalibymy jednak w metodzie onCreate() treci intencji przekazywanej przez metod startService(). Gdybymy
jej potrzebowali, wystarczy skorzysta z opisanego wczeniej algorytmu i wywoa metod
onStartCommand() tylko raz.
Na tym zakoczymy cz dotyczc usug lokalnych. Zostan one dokadniej opisane w nastpnych rozdziaach. Zbadajmy teraz usugi AIDL posiadajce bardziej zoon struktur.

Usugi AIDL
W poprzednim podrozdziale pokazalimy, jak naley pisa usug uytkowan przez aplikacj,
ktra uruchomia t usug. Zademonstrujemy teraz technik tworzenia usugi wykorzystywanej przez inne procesy poprzez wywoanie RPC. Podobnie jak w przypadku innych rozwiza
opartych na wywoaniach RPC, tak i teraz musimy korzysta w Androidzie z jzyka IDL (ang.
Interface Definition Language jzyk definiowania interfejsu) do definiowania interfejsu dostpnego dla klientw. W wiecie Androida jzyk ten nosi nazw AIDL. Aby utworzy usug
zdaln, naley wykona ponisze dziaania:
1. Napisz plik AIDL, ktry definiuje interfejs dla klientw. Plik AIDL wykorzystuje
skadni jzyka Java i posiada rozszerzenie .aidl. Nazwa pakietu powinna by taka
sama jak nazwa pakietu w projekcie Androida.
2. Dodaj plik AIDL do projektu w rodowisku Eclipse do katalogu src. Wtyczka ADT
wywoa kompilator jzyka AIDL, dziki ktremu z pliku AIDL zostanie wygenerowany
interfejs Java (kompilator AIDL jest wywoywany podczas procesu budowania aplikacji).
3. Zaimplementuj usug i przeka interfejs z metody onBind().
4. Dodaj konfiguracj usugi do pliku AndroidManifest.xml.
W kolejnych podrozdziaach omwilimy poszczeglne czynnoci.

Definiowanie interfejsu usugi w jzyku AIDL


W celu zademonstrowania usugi zdalnej napiszemy usug przedstawiajc notowania giedowe.
Bdzie ona zawieraa metod przyjmujc symbol notowanej firmy i odsyajc warto notowa
tej firmy. Pierwszym krokiem tworzenia usugi zdalnej w Androidzie jest zaprojektowanie

Rozdzia 11 Tworzenie i uytkowanie usug

377

definicji interfejsu tej usugi w pliku AIDL. Na listingu 11.22 zostaa ukazana definicja usugi
IStockQuoteService w jzyku AIDL. Plik ten naley umieci wraz ze wszystkimi innymi
plikami Java zwizanymi z projektem StockQuoteService.
Listing 11.22. Definicja usugi notowa giedowych w jzyku AIDL
// Jest to plik IStockQuoteService.aidl
package com.androidbook.services.stockquoteservice;
interface IStockQuoteService
{
double getQuote(String ticker);
}

Usuga IStockQuoteService przyjmuje symbol notowanej firmy i przekazuje biec warto


(typu double) notowa tej firmy. Podczas tworzenia pliku AIDL wtyczka ADT rodowiska Eclipse uruchamia kompilator jzyka AIDL przetwarzajcy ten plik (podczas procesu kompilacji
kodu caej aplikacji). W wyniku bezbdnej kompilacji pliku AIDL zostanie utworzony interfejs
Java zdolny do komunikacji typu RPC. Zwrmy uwag, e wygenerowany plik zostanie
umieszczony w pakiecie noszcym nazw pliku AIDL w naszym przypadku bdzie to
com.androidbook.services.stockquoteservice.
Na listingu 11.23 przedstawiamy wygenerowany plik Java naszego interfejsu IStockQuoteService.
Wygenerowany plik zostanie umieszczony w folderze gen naszego projektu.
Listing 11.23. Wygenerowany przez kompilator plik Java
/*
* Ten plik zosta automatycznie wygenerowany. NIE NALEY GO MODYFIKOWA.
* Oryginalny plik: C:\\android\\StockQuoteService\\src\\com\\androidbook\\
services\\stockquoteservice\\IStockQuoteService.aidl
*/
package com.androidbook.services.stockquoteservice;
import java.lang.String;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Binder;
import android.os.Parcel;
public interface IStockQuoteService extends android.os.IInterface
{

/** Implementacja IPC lokalnej klasy poredniczcej. */


public static abstract class Stub extends android.os.Binder implements
com.androidbook.services.stockquoteservice.IStockQuoteService
{
private static final java.lang.String DESCRIPTOR =
"com.androidbook.services.stockquoteservice.IStockQuoteService";

/** Tworzy procedur poredniczc, przyczan do interfejsu. */


public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}

/**

378 Android 3. Tworzenie aplikacji


* Umieszcza obiekt IBinder wewntrz interfejsu IStockQuoteService,
* w razie potrzeby tworzy porednika.
*/
public static com.androidbook.services.stockquoteservice.IStockQuoteService
asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof
com.androidbook..services.stockquoteservice.IStockQuoteService))) {
return ((com.androidbook.services.stockquoteservice.IStockQuoteService)iin);
}
return ((com.androidbook.services.stockquoteservice.IStockQuoteService)iin);
}
return new com.androidbook.services.stockquoteservice.
IStockQuoteService.Stub.Proxy(obj);
}
public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getQuote:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
double _result = this.getQuote(_arg0);
reply.writeNoException();
reply.writeDouble(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements
com.androidbook.services.stockquoteservice.IStockQuoteService
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
public android.os.IBinder asBinder()
{
return mRemote;

Rozdzia 11 Tworzenie i uytkowanie usug

379

}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
public double getQuote(java.lang.String ticker) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
double _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(ticker);
mRemote.transact(Stub.TRANSACTION_getQuote, _data, _reply, 0);
_reply.readException();
_result = _reply.readDouble();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getQuote = (IBinder.FIRST_CALL_TRANSACTION + 0);
}
public double getQuote(java.lang.String ticker) throws android.os.RemoteException;
}

Poniej przedstawiamy kilka istotnych wnioskw dotyczcych wygenerowanych klas:


Interfejs zdefiniowany w pliku AIDL zosta zaimplementowany w wygenerowanym
kodzie (to znaczy, e istnieje interfejs IStockQuoteService).
Abstrakcyjna klasa static final noszca nazw stub rozszerza klas
android.os.Binder i implementuje interfejs IStockQuoteService.
Zwrmy uwag, e jest to abstrakcyjna klasa.
Wewntrzna klasa Proxy implementuje interfejs IStockQuoteService, ktry jest
porednikiem dla klasy Stub.
Plik AIDL musi si znale w tym samym pakiecie, w ktrym bd umieszczone
wygenerowane pliki (co zostao okrelone w deklaracji pakietu pliku AIDL).
Zaimplementujmy teraz interfejs AIDL w klasie usugi.

Implementowanie interfejsu AIDL


W poprzednim podrozdziale zdefiniowalimy plik AIDL dla usugi prezentujcej notowania
giedowe i wygenerowalimy wicy plik. Teraz wprowadzimy implementacj tej usugi. eby
zaimplementowa jej interfejs, musimy napisa klas rozszerzajc klas android.app.Service
i rozszerzajc interfejs IStockQuoteService. Utworzon przez nas klas nazwiemy Stock
QuoteService. Aby mona byo wyeksponowa usug klientom, w usudze StockQuoteService
zaimplementujemy metod onBind(). Dodamy te pewne informacje do pliku AndroidManifest.
xml. Listing 11.24 przedstawia implementacj interfejsu IStockQuoteService. Rwnie ten
plik umieszczamy w folderze src projektu StockQuoteService.

380 Android 3. Tworzenie aplikacji


Listing 11.24. Implementacja usugi IStockQuoteService
// StockQuoteService.java
import
import
import
import
import

android.app.Service;
android.content.Intent;
android.os.IBinder;
android.os.RemoteException;
android.util.Log;

public class StockQuoteService extends Service


{
private static final String TAG = "StockQuoteService";
public class StockQuoteServiceImpl extends IStockQuoteService.Stub
{
@Override
public double getQuote(String ticker) throws RemoteException
{
Log.v(TAG, "Metoda getQuote() wywoana dla " + ticker);
return 20.0;
}
}
@Override
public void onCreate() {
super.onCreate();
Log.v(TAG, "Wywoana metoda onCreate()");
}
@Override
public void onDestroy()
{
super.onDestroy();
Log.v(TAG, "Wywoana metoda onDestroy()");
}
@Override
public IBinder onBind(Intent intent)
{
Log.v(TAG, "Wywoana metoda onBind()");
return new StockQuoteServiceImpl();
}
}

Klasa StockQuoteService.java przytoczona na listingu 11.24 przypomina omwion wczeniej usug lokaln BackgroundService, pozbawiona jest jednak klasy NotificationManager.
Zasadnicza rnica polega na zaimplementowaniu w tym przypadku metody onBind(). Przypominamy, e w pliku AIDL wygenerowalimy abstrakcyjn klas Stub, ktra implementowaa
interfejs IStockQuoteService. W naszej implementacji usugi zawieramy wewntrzn klas
StockQuoteServiceImpl, ktra rozszerza klas Stub. Klasa ta suy nam jako implementacja
usugi zdalnej, a instancja tej klasy jest przekazywana z metody onBind(). W ten sposb otrzymujemy dziaajc usug AIDL, chocia zewntrzne klienty nie mog si z ni jeszcze poczy.
Aby wyeksponowa usug klientom, musimy doda deklaracj usugi w pliku AndroidManifest.
xml, tym razem jednak potrzebujemy filtru intencji do jej odsonicia. Na listingu 11.25 zostaa
pokazana deklaracja usugi StockQuoteService. Znacznik <service> jest podrzdny wobec
znacznika <application>.

Rozdzia 11 Tworzenie i uytkowanie usug

381

Listing 11.25. Deklaracja usugi IStockQuoteService w pliku manifecie


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.services.stockquoteservice"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<service android:name="StockQuoteService">
<intent-filter>
<action
android:name="com.androidbook.services.stockquoteservice.IStockQuoteService" />
</intent-filter>
</service>
</application>
<uses-sdk android:minSdkVersion="4" />
</manifest>

Podobnie jak to ma miejsce w przypadku wszystkich usug, poprzez znacznik <service>


definiujemy usug, ktr chcemy wyeksponowa. W przypadku usugi AIDL musimy doda
rwnie wze <intent-filter> z wpisem <action> dla interfejsu usugi, ktr chcemy wyeksponowa.
Po umieszczeniu tego wpisu bdziemy posiada ju wszystkie elementy wymagane do wdroenia
usugi. Gdy bdziemy ju gotowi na wdroenie usugi z poziomu rodowiska Eclipse, wybierzmy po prostu opcj Run As, podobnie jak ma to miejsce w przypadku zwykej aplikacji. Ujrzymy w konsoli komunikat informujcy nas, e aplikacja nie posiada programu wywoujcego, ale
i tak zostanie wdroona, co jest zgodne z naszymi oczekiwaniami. Przyjrzyjmy si teraz, w jaki
sposb mona wywoa usug z poziomu innej aplikacji (zainstalowanej, oczywicie, na tym
samym urzdzeniu).

Wywoywanie usugi z poziomu aplikacji klienckiej


Komunikacja pomidzy klientem a usug jest moliwa pod warunkiem ustanowienia protokou lub kontraktu pomidzy tymi dwoma obiektami. W Androidzie kontrakt jest umieszczony w pliku AIDL. Zatem pierwsz czynnoci prowadzc do skorzystania z usugi jest
skopiowanie jej pliku AIDL i wklejenie go do projektu klienckiego. Podczas kopiowania tego
pliku kompilator tworzy identyczny plik definicji interfejsu jak w przypadku implementacji
usugi (w projekcie implementacji usugi). W ten sposb zostaj wyeksponowane klientowi
wszystkie metody, parametry oraz przekazywane typy usugi. Stwrzmy nowy projekt i skopiujmy do niego plik AIDL.
1. Utwrz nowy projekt Androida o nazwie StockQuoteClient. Dla pakietu uyj innej
nazwy, na przykad com.androidbook.stockquoteclient. Wpisz klas MainActivity
w polu Create Activity.
2. Utwrz nowy pakiet Java w katalogu src tego projektu i nazwij go
com.androidbook.services.stockquoteservice.
3. Skopiuj do tego pakietu plik IStockQuoteService.aidl z projektu StockQuoteService.
Zwr uwag, e po skopiowaniu tego pliku do projektu kompilator jzyka AIDL
wygeneruje powizany z nim plik Java.

382 Android 3. Tworzenie aplikacji


Odtwarzany interfejs usugi posuy nam jako kontrakt pomidzy klientem a usug. Kolejnym
krokiem jest uzyskanie odniesienia do usugi w celu wywoania metody getQuote(). W przypadku usug zdalnych musimy zamiast metody startService() wywoa metod bindService().
Na listingu 11.26 umiecilimy klas aktywnoci zachowujc si jak klient w stosunku do usugi
IStockQuoteService. Z kolei na listingu 11.27 umieszczono plik ukadu graficznego tej
aktywnoci.
Na listingu 11.26 przedstawiono plik MainActivity.java. Nazwa pakietu aktywnoci klienckiej
nie jest tak istotna moemy umieci aktywno w dowolnym pakiecie. Jednak naley pamita, e tworzone przez nas artefakty jzyka AIDL rozpoznaj nazw pakietu, poniewa kompilator generuje kod z zawartoci pliku AIDL.
Listing 11.26. Klient usugi IStockQuoteService
// Jest to plik MainActivity.java
import
import
import
import
import
import
import
import
import
import
import
import
import
import

com.androidbook.services.stockquoteservice.IStockQuoteService;
android.app.Activity;
android.content.ComponentName;
android.content.Context;
android.content.Intent;
android.content.ServiceConnection;
android.os.Bundle;
android.os.IBinder;
android.os.RemoteException;
android.util.Log;
android.view.View;
android.widget.Button;
android.widget.Toast;
android.widget.ToggleButton;

public class MainActivity extends Activity {


private static final String TAG = "StockQuoteClient";
private IStockQuoteService stockService = null;
private ToggleButton bindBtn;
private Button callBtn;

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bindBtn = (ToggleButton)findViewById(R.id.bindBtn);
callBtn = (Button)findViewById(R.id.callBtn);
}
public void doClick(View view) {
switch(view.getId()) {
case R.id.bindBtn:
if(((ToggleButton) view).isChecked()) {
bindService(new Intent(
IStockQuoteService.class.getName()),
serConn, Context.BIND_AUTO_CREATE);
}
else {

Rozdzia 11 Tworzenie i uytkowanie usug

383

unbindService(serConn);
callBtn.setEnabled(false);
}
break;
case R.id.callBtn:
callService();
break;
}
}
private void callService() {
try {
double val = stockService.getQuote("ANDROID");
Toast.makeText(MainActivity.this,
"Warto z usugi wynosi " + val,
Toast.LENGTH_SHORT).show();
} catch (RemoteException ee) {
Log.e("MainActivity", ee.getMessage(), ee);
}
}
private ServiceConnection serConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name,
IBinder service)
{
Log.v(TAG, "wywolana metoda onServiceConnected()");
stockService = IStockQuoteService.Stub.asInterface(service);
bindBtn.setChecked(true);
callBtn.setEnabled(true);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.v(TAG, "wywolana metoda onServiceDisconnected()");
bindBtn.setChecked(false);
callBtn.setEnabled(false);
stockService = null;
}
};
protected void onDestroy() {
Log.v(TAG, "wywolana metoda onDestroy()");
if(callBtn.isEnabled())
unbindService(serConn);
super.onDestroy();
}
}

Aktywno wywietla nasz ukad graficzny i pobiera odniesienie do przycisku Wywoaj


usug, dziki czemu moemy go poprawnie uruchomi w trakcie dziaania usugi oraz wyczy, gdy usuga jest zatrzymana. Po klikniciu przycisku Podcz zostanie wywoana metoda
bindService(). W analogiczny sposb po wybraniu przycisku Odcz zostanie wywoana

384 Android 3. Tworzenie aplikacji


metoda unbindService(). Zwrmy uwag, e metodzie bindService() s przekazywane
trzy parametry: nazwa usugi AIDL, instancja obiektu ServiceConnection oraz flaga automatycznego utworzenia usugi.
Listing 11.27. Ukad graficzny klienta usugi IStockQuoteService
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ToggleButton android:id="@+id/bindBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="Podcz" android:textOn="Odcz"
android:onClick="doClick" />
<Button android:id="@+id/callBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wywoaj usug" android:enabled=false
android:onClick="doClick" />
</LinearLayout>

W przypadku usugi AIDL musimy wprowadzi implementacj interfejsu ServiceConnection.


Interfejs ten definiuje dwie metody: jedna jest wywoywana przez system po ustanowieniu
poczenia z usug, a druga jest wywoywana po przerwaniu takiego poczenia. W naszej
implementacji aktywnoci definiujemy prywatnego, anonimowego czonka, implementujcego
interfejs ServiceConnection dla usugi IStockQuoteService. Podczas wywoywania metody
bindService() przekazujemy jej odniesienie do tego czonka. Po ustanowieniu poczenia
z usug zostaje przywoana metoda zwrotna onServiceConnected(), a nastpnie uzyskujemy
odniesienie do obiektu IStockQuoteService za pomoc klasy Stub, po czym aktywujemy
przycisk Wywoaj usug.
Zauwamy, e wywoanie bindService() jest asynchroniczne. Ta asynchroniczno wynika
z faktu, e proces lub usuga mog nie by uruchomione, zatem naley je utworzy lub uruchomi. Gwny wtek nie moe oczekiwa na uruchomienie usugi. Wobec tego platforma
posiada wywoanie zwrotne ServiceConnection, dziki ktremu wiadomo, kiedy usuga jest
uruchomiona lub kiedy przestaje by dostpna.
Zwrmy uwag na metod zwrotn onServiceDisconnected(). Nie zostaje ona przywoana
podczas odczania si od usugi. Zostaje ona wywoana jedynie w przypadku, gdy usuga przestanie dziaa. Jeeli tak si stanie, nie musimy si zastanawia nad tym, czy poczenie z t usug jest wci aktywne, moemy za to chcie ponownie wywoa metod onBind(). Z tego
wanie powodu zmieniamy status przyciskw po wywoaniu tej metody. Zauwamy jednak, e
napisalimy moemy chcie ponownie wywoa metod onBind(). Android moe samoczynnie uruchomi usug i wywoa metod onServiceConnected().
Moemy sami to sprawdzi, uruchamiajc klienta, podczajc si do usugi, a potem za pomoc narzdzia DDMS zatrzymujc usug.

Rozdzia 11 Tworzenie i uytkowanie usug

385

Po uruchomieniu przykadowej aplikacji obserwujmy komunikaty dziennika w oknie LogCat,


aby przeledzi tok dziaania aplikacji.
Teraz wiemy, w jaki sposb tworzy i uytkowa usugi AIDL. Zanim wprowadzimy dodatkowe,
komplikujce temat szczegy, powtrzmy, co naley zrobi, aby utworzy prost usug lokaln
oraz usug AIDL. Usuga lokalna nie obsuguje metody onBind() przekazywan wartoci
jest null. Ten rodzaj usugi jest dostpny wycznie dla skadnikw aplikacji, ktra wywoaa
t usug. Usugi lokalne s wywoywane za pomoc metody startService().
Z drugiej strony istnieje usuga AIDL, ktra moe by uytkowana zarwno przez skadniki
bdce czci tego samego obiektu, jak i skadniki innych aplikacji. W przypadku tego typu
usugi naley zdefiniowa kontrakt pomidzy t usug a klientem za pomoc pliku AIDL.
Usuga implementuje kontrakt AIDL, a klient czy si z definicj jzyka AIDL. Usuga dokonuje implementacji kontraktu poprzez przekazanie implementacji interfejsu AIDL z metody
onBind(). Klienty cz si z usug AIDL poprzez wywoanie metody bindService(), a rozczaj si z ni dziki wywoaniu metody unbindService().
W naszych dotychczasowych przykadach usug przekazywalimy wycznie proste typy danych
jzyka Java. Usugi w Androidzie w rzeczywistoci obsuguj rwnie przekazywanie zoonych
typw danych. Jest to bardzo przydatne, zwaszcza w przypadku usug AIDL, poniewa moemy
okreli dowoln liczb parametrw, ktre naley przekaza usudze, a przekazywanie ich
wszystkich w formie prostych typw danych nie jest rozsdne. Bardziej sensowne jest upakowanie ich w formie zoonych typw danych i przekazanie ich usudze w tej postaci.
Zobaczmy, w jaki sposb mona przekazywa usugom zoone typy danych.

Przekazywanie usugom zoonych typw danych


Przekazywanie do usug i z usug zoonych typw danych wymaga wicej pracy ni przekazywanie prostych typw danych. Zanim zagbimy si w ten temat, powinnimy przedstawi podstawowe koncepcje obsugi zoonych typw danych w jzyku AIDL:
Jzyk AIDL obsuguje typy String i CharSequence.
Istnieje moliwo przekazywania innych interfejsw AIDL, jednak dla kadego
interfejsu, do ktrego tworzone jest odniesienie, wymagana jest instrukcja import
(nawet jeli ten interfejs znajduje si w tym samym pakiecie).
Istnieje moliwo przekazywania zoonych typw danych, implementujcych
interfejs android.os.Parcelable. Wymagana jest instrukcja import w pliku AIDL
dla tych typw danych.
Jzyk AIDL obsuguje w ograniczonym stopniu interfejsy java.util.List
i java.util.Map. Dopuszczalne typy danych dla elementw w zbiorze obejmuj
proste typy danych Java, String, CharSequence lub android.os.Parcelable.
Instrukcje import nie s wymagane dla interfejsw List lub Map, ale s potrzebne
dla interfejsu Parcelable.
Zoone typy danych poza typem String wymagaj zdefiniowania wskanika
kierunkowego. Zaliczane s do nich parametry in, out oraz inout. Parametr in
oznacza, e warto jest definiowana przez klienta; dziki parametrowi out warto
zostaje okrelona przez usug; w przypadku parametru inout warto okrelaj
zarwno klient, jak i usuga.

386 Android 3. Tworzenie aplikacji


Interfejs Parcelable przekazuje Androidowi informacje, w jaki sposb obiekty maj by serializowane oraz deserializowane w procesie szeregowania lub rozszeregowania. Na listingu
11.28 zostaa pokazana klasa Person implementujca interfejs Parcelable.
Listing 11.28. Implementowanie interfejsu Parcelable
// Jest to plik Person.java
package com.androidbook.services.stock2;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable {
private int age;
private String name;
public static final Parcelable.Creator<Person> CREATOR =
new Parcelable.Creator<Person>() {
public Person createFromParcel(Parcel in) {
return new Person(in);
}
public Person[] newArray(int size) {
return new Person[size];
}
};
public Person() {
}
private Person(Parcel in) {
readFromParcel(in);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(age);
out.writeString(name);
}
public void readFromParcel(Parcel in) {
age = in.readInt();
name = in.readString();
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}

Rozdzia 11 Tworzenie i uytkowanie usug

387

public String getName() {


return name;
}
public void setName(String name) {
this.name = name;
}
}

Aby zaimplementowa ten kod, utwrzmy nowy projekt Androida nazwany StockQuote
Service2. Przypiszmy polu Create Activity aktywno o nazwie MainActivity i skorzystajmy
z pakietu com.androidbook.services.stock2. Nastpnie dodajmy powyszy plik Person.java
do pakietu com.androidbook.services.stock2 w naszym nowym projekcie.
Interfejs Parcelable definiuje kontrakt odpowiedzialny za doczanie lub odczanie obiektw
w trakcie procesu szeregowania lub rozszeregowania. Podstaw interfejsu Parcelable jest pojemnik Parcel. Klasa Parcel jest szybkim mechanizmem serializowania i deserializowania,
zaprojektowanym specjalnie dla komunikacji midzyprocesowej w Androidzie. W klasie tej s
zawarte metody pozwalajce na rozmieszczanie czonkw klasy w pojemniku oraz uzyskiwanie
do nich dostpu. eby zaimplementowa obiekt komunikacji midzyprocesowej we waciwy
sposb, naley postpowa zgodnie z nastpujcym algorytmem:
1. Zaimplementuj interfejs Parcelable. Oznacza to konieczno implementacji metod
writeToParcel() i readFromParcel(). Pierwsza metoda suy do zapisania obiektu
w paczce, druga natomiast pozwala odczytywa obiekty umieszczone w paczce.
Pamitajmy, e kolejno odczytywania waciwoci musi by taka sama jak kolejno
ich zapisywania.
2. Dodaj waciwo static final o nazwie CREATOR do klasy. Waciwo ta wymaga
implementacji interfejsu android.os.Parcelable.Creator<T>.
3. Przygotuj dla interfejsu Parcelable konstruktor, ktry bdzie tworzy obiekty klasy
Parcel.
4. Zdefiniuj klas Parcelable w pliku .aidl odpowiadajcym plikowi .java, w ktrym
zawarty jest zoony typ danych. Kompilator AIDL bdzie szuka tego pliku podczas
kompilowania plikw AIDL. Na listingu 11.29 umiecilimy przykadowy plik Person.aidl.
Plik ten powinien si znajdowa w tym samym miejscu co plik Person.java.
W przypadku interfejsu Parcelable moe rodzi si pytanie, dlaczego w Androidzie
nie zosta wykorzystany wbudowany w rodowisku Java mechanizm serializacji.
Okazuje si, e twrcy Androida uznali proces serializacji w rodowisku Java za zbyt
powolny, aby speni wymogi komunikacji midzyprocesowej w Androidzie. Zostao
zatem utworzone rozwizanie w postaci interfejsu Parcelable. Naley w nim jawnie
serializowa czonkw klasy, jednak w zamian cay proces przebiega o wiele szybciej.
Naley rwnie uwiadomi sobie, e istniej w Androidzie dwa procesy umoliwiajce
przekazywanie danych do innego procesu. Pierwszy z nich polega na przekazaniu
danych do aktywnoci za pomoc intencji, a drugi na przesaniu interfejsu Parcelable
do usugi. Te dwa mechanizmy nie s stosowane wymiennie i nie naley ich ze sob myli.
Oznacza to, e interfejs Parcelable nie jest przekazywany do aktywnoci. Jeeli chcemy
uruchomi aktywno i przekaza jej dane, powinnimy wykorzysta w tym celu intencj.
Interfejs Parcelable jest przeznaczony wycznie do uytku jako cz definicji AIDL.

388 Android 3. Tworzenie aplikacji


Listing 11.29. Przykadowy plik Person.aidl
// Jest to plik Person.aidl
package com.androidbook.services.stock2;
parcelable Person;

Bdziemy potrzebowa pliku .aidl dla kadego interfejsu Parcelable w projekcie. W tym przypadku posiadamy tylko jeden interfejs Parcelable Person. Warto zauway, e nie zosta utworzony plik Person.java w katalogu gen. Naleao si tego spodziewa. Plik ten utworzylimy ju wczeniej.
Zastosujmy teraz klas Person w usudze zdalnej. eby nie komplikowa sprawy, zmodyfikujemy nasz obiekt IStockQuoteService, dziki czemu bdzie pobiera parametr wejciowy
typu danych klasy Person. Pomys jest taki, aby klienty przekazyway klas Person do usugi
w celu powiadomienia, jaka aktywno da wyniku notowania. Nowy plik IStockQuoteService.aidl
zosta zaprezentowany na listingu 11.30.
Listing 11.30. Przekazywanie usugom plikw parcelowanych
// Jest to plik IStockQuoteService.aidl
package com.androidbook.services.stock2;
import com.androidbook.services.stock2.Person;
interface IStockQuoteService
{
String getQuote(in String ticker,in Person requester);
}

Metoda getQuote() przyjmuje obecnie dwa parametry: symbol notowanej firmy i obiekt Person
okrelajcy, jaki obiekt wysya danie. Zwrmy uwag, e umiecilimy wskaniki kierunkowe
dla tych parametrw, poniewa typy danych tych parametrw nie s proste, oraz e wprowadzilimy instrukcj import wobec klasy Person. Klasa Person znajduje si w tym samym pakiecie co definicja usugi (com.androidbook.services.stock2).
Implementacja usugi wyglda teraz tak jak na listingu 11.31, a jej ukad graficzny zosta
umieszczony na listingu 11.32.
Listing 11.31. Implementacja usugi StockQuoteService2
package com.androidbook.services.stock2;

// Jest to plik StockQuoteService2.java


import
import
import
import
import
import
import

android.app.Notification;
android.app.NotificationManager;
android.app.PendingIntent;
android.app.Service;
android.content.Intent;
android.os.IBinder;
android.os.RemoteException;

public class StockQuoteService2 extends Service


{
private NotificationManager notificationMgr;

Rozdzia 11 Tworzenie i uytkowanie usug

389

public class StockQuoteServiceImpl extends IStockQuoteService.Stub


{
@Override
public String getQuote(String ticker, Person requester)
throws RemoteException {
return "Witaj "+requester.getName()+"! Notowanie dla "+ticker+" wynosi 20.0";
}
}
@Override
public void onCreate() {
super.onCreate();
notificationMgr =
(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
displayNotificationMessage("Metoda onCreate() wywoana w StockQuoteService2");
}
@Override
public void onDestroy()
{
displayNotificationMessage("Metoda onDestroy() wywoana w StockQuoteService2");

// Usuwa wszelkie powiadomienia z tej usugi


notificationMgr.cancelAll();
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent)
{
displayNotificationMessage("Metoda onBind() wywoana w StockQuoteService2");
return new StockQuoteServiceImpl();
}
private void displayNotificationMessage(String message)
{
Notification notification = new Notification(R.drawable.emo_im_happy,
message,System.currentTimeMillis());
PendingIntent contentIntent =
PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
notification.setLatestEventInfo(this, "StockQuoteService2",message,
contentIntent);
notification.flags = Notification.FLAG_NO_CLEAR;
notificationMgr.notify(R.id.app_notification_id, notification);
}
}

Listing 11.32. Ukad graficzny usugi StockQuoteService2


<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

390 Android 3. Tworzenie aplikacji


android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Tutaj usuga poprosi o pomoc."
/>
</LinearLayout>

Rnica pomidzy obecn a poprzedni implementacj polega na tym, e teraz ponownie


wprowadzamy powiadomienia oraz przypisujemy notowaniu warto string, a nie double.
Przekazywany uytkownikowi cig znakw zawiera uzyskane z obiektu Person informacje
o elemencie dajcym wyniku notowania, co ma na celu zademonstrowanie poprawnego odczytania wartoci wysanej przez klienta oraz poprawnego przekazania usudze obiektu Person.
Musimy wykona jeszcze kilka czynnoci, aby nasz przykadowy kod dziaa:
1. Znajd plik emo_im_happy.png z katalogu Android SDK/platforms/android-2.1/data/
res/drawable-mdpi i skopiuj go do folderu /res/drawable naszego projektu. Moemy
rwnie zmieni w kodzie nazw zasobu i wstawi dowolny obraz w katalogu drawable.
2. Dodaj nowy znacznik <item type="id" name="app_notification_id"/> w pliku
/res/values/strings.xml.
3. Musimy zmodyfikowa kod aplikacji w pliku AndroidManifest.xml, tak jak pokazano
na listingu 11.33.
Listing 11.33. Zmodyfikowany wze <application> w pliku AndroidManifest.xml usugi
StockQuoteService2
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.services.stock2"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<service android:name="StockQuoteService2">
<intent-filter>
<action android:name="com.androidbook.services.
stock2.IStockQuoteService" />
</intent-filter>
</service>
</application>
<uses-sdk android:minSdkVersion="7" />
</manifest>

Rozdzia 11 Tworzenie i uytkowanie usug

391

Podczas gdy mona wprowadza skrcon notacj kropkow w atrybucie android:name


=".MainActivity", takie postpowanie nie jest ju poprawne w przypadku notacji uywanej w ramach znacznika <action> umieszczonego w rodku znacznika <intent-filter>
usugi. Musimy wprowadzi pen notacj, w przeciwnym wypadku klient nie odnajdzie specyfikacji usugi.
Na koniec wykorzystamy domylny plik MainActivity.java, ktry wywietla prosty ukad graficzny z nieskomplikowan wiadomoci. Pokazalimy wczeniej, w jaki sposb mona uruchomi aktywno za pomoc powiadomienia. Aktywno speniaaby rwnie tak rol w standardowej aplikacji, w naszym przykadzie jednak nie bdziemy niczego komplikowa. Gdy
ju posiadamy implementacj naszej usugi, utwrzmy nowy projekt Androida, nazwany
StockQuoteClient2. Pakiet niech nosi nazw com.dave, a aktywno MainActivity.
Aby zaimplementowa klienta zdolnego do przekazywania obiektu Person usudze, musimy
skopiowa do jego projektu wszystkie elementy projektu usugi wymagane przez klienta. W poprzednim przykadzie potrzebowalimy jedynie pliku IStockQuoteService.aidl. Teraz musimy
skopiowa rwnie pliki Person.java i Person.aidl, poniewa obiekt Person jest czci interfejsu.
Po skopiowaniu tych trzech plikw do projektu klienta musimy zmodyfikowa pliki main.xml
tak jak na listingu 11.34 i MainActivity.java zgodnie z listingiem 11.35. Ewentualnie moemy
po prostu zaimportowa cay projekt z pliku umieszczonego na oficjalnej stronie ksiki.
Listing 11.34. Zaktualizowany plik main.xml aplikacji StockQuoteClient2
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ToggleButton android:id="@+id/bindBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="Podcz" android:textOn="Odcz"
android:onClick="doClick" />
<Button android:id="@+id/callBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wywoaj ponownie" android:enabled="false"
android:onClick="doClick" />
</LinearLayout>>

Listing 11.35. Wywoywanie usugi za pomoc obiektu Parcelable


package com.dave;

// Jest to plik MainActivity.java


import
import
import
import
import

android.app.Activity;
android.content.ComponentName;
android.content.Context;
android.content.Intent;
android.content.ServiceConnection;

392 Android 3. Tworzenie aplikacji


import
import
import
import
import
import
import
import

android.os.Bundle;
android.os.IBinder;
android.os.RemoteException;
android.util.Log;
android.view.View;
android.widget.Button;
android.widget.Toast;
android.widget.ToggleButton;

import com.androidbook.services.stock2.IStockQuoteService;
import com.androidbook.services.stock2.Person;
public class MainActivity extends Activity {
protected static final String TAG = "StockQuoteClient2";
private IStockQuoteService stockService = null;
private ToggleButton bindBtn;
private Button callBtn;

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bindBtn = (ToggleButton)findViewById(R.id.bindBtn);
callBtn = (Button)findViewById(R.id.callBtn);
}
public void doClick(View view) {
switch(view.getId()) {
case R.id.bindBtn:
if(((ToggleButton) view).isChecked()) {
bindService(new Intent(
IStockQuoteService.class.getName()),
serConn, Context.BIND_AUTO_CREATE);
}
else {
unbindService(serConn);
callBtn.setEnabled(false);
}
break;
case R.id.callBtn:
callService();
break;
}
}
private void callService() {
try {
Person person = new Person();
person.setAge(47);
person.setName("Dave");
String response = stockService.getQuote("ANDROID", person);
Toast.makeText(MainActivity.this,
"Warto z usugi wynosi "+response,

Rozdzia 11 Tworzenie i uytkowanie usug

393

Toast.LENGTH_SHORT).show();
} catch (RemoteException ee) {
Log.e("MainActivity", ee.getMessage(), ee);
}
}
private ServiceConnection serConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name,
IBinder service)
{
Log.v(TAG, "wywolana metoda onServiceConnected()");
stockService = IStockQuoteService.Stub.asInterface(service);
bindBtn.setChecked(true);
callBtn.setEnabled(true);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.v(TAG, "wywolana metoda onServiceDisconnected()");
bindBtn.setChecked(false);
callBtn.setEnabled(false);
stockService = null;
}
};
protected void onDestroy() {
if(callBtn.isEnabled())
unbindService(serConn);
super.onDestroy();
}
}

Nasza usuga jest ju gotowa do uruchomienia. Naley jeszcze przesa j do emulatora, zanim
uruchomimy klienta. Interfejs uytkownika powinien wyglda tak jak na rysunku 11.8.

Rysunek 11.8. Interfejs uytkownika w aplikacji StockQuoteClient2

Przyjrzyjmy si temu, co otrzymalimy. Podobnie jak poprzednio, tworzymy powizanie z usug,


a nastpnie moemy wywoa jej metod. Dziki metodzie onServiceConnected() dowiadujemy si, e nasza usuga jest uruchomiona, moemy wic aktywowa przycisk Wywoaj ponownie, dziki ktremu wywoujemy metod callService(). Jak wida, tworzymy nowy obiekt
Person i konfigurujemy jego waciwoci Age i Name. Nastpnie uruchamiamy usug i otrzymujemy wywietlony wynik jej wywoania. Zosta on zilustrowany na rysunku 11.9.

394 Android 3. Tworzenie aplikacji

Rysunek 11.9. Wynik wywoania usugi za pomoc interfejsu Parcelable

Zauwamy, e podczas wywoywania usugi zostaje wywietlone powiadomienie w pasku stanu.


Pochodzi ono z samej usugi. We wczeniejszej czci rozdziau poruszylimy temat powiadomie jako sposobu komunikacji usugi z uytkownikiem. Usugi pozostaj w tle i nie wywietlaj
interfejsu uytkownika w adnej postaci. Ale co w przypadku, gdy usuga musi nawiza komunikacj z uytkownikiem? Chocia kuszca jest myl, eby usuga wywoaa aktywno,
nie powinna nigdy tej czynnoci wykonywa bezporednio. Zamiast tego usuga powinna
utworzy powiadomienie, ktre informuje uytkownika, w jaki sposb moe si dosta do danej aktywnoci. Zostao to zademonstrowane w powyszym przykadzie. Zdefiniowalimy
prosty ukad graficzny i implementacj aktywnoci dla naszej usugi. Kiedy utworzylimy powiadomienie w usudze, skonfigurowalimy rwnie aktywno dla niego. Uytkownik moe klikn to powiadomienie i wtedy uruchomi aktywno bdc czci usugi. W ten sposb
bdzie mg komunikowa si z usug.
Powiadomienia s zapisywane, zatem moemy je odczyta, klikajc przycisk Menu na ekranie
startowym Androida i wybierajc opcj Notifications. Uytkownik moe rwnie przecign
ikon powiadomie z paska stanu, aby przejrze powiadomienia. Zwrmy uwag na sposb
wykorzystania wywoania metody setLatestEventInfo() oraz na fakt, e dla kadej wiadomoci uywamy tego samego identyfikatora. Dziki temu aktualizujemy przez cay czas tylko jedno
powiadomienie, zamiast tworzy nowe wpisy. Zatem jeli po kilkakrotnym klikniciu przyciskw Przycz, Wywoaj ponownie i Odcz przejdziemy do ekranu Notifications, ujrzymy tylko
jedno powiadomienie ostatnie wysane przez usug BackgroundService. Gdybymy
uywali kilku rnych identyfikatorw, moglibymy utworzy kilka wpisw powiadomie i kady
z nich mgby by aktualizowany oddzielnie. Do powiadomie mona doda rwnie dodatkowe zachty uytkownika, takie jak dwik, zmiana obrazu oraz (lub) wibracja.
Warto rwnie przejrze artefakty projektu usugi oraz projektu wywoujcego j programu
(rysunek 11.10).
Na rysunku 11.10 zostay ukazane artefakty projektu w rodowisku Eclipse dla usugi (z lewej
strony) oraz dla klienta (z prawej strony). Zauwamy, e na kontrakt pomidzy klientem a usug
skadaj si pliki jzyka AIDL oraz obiekty Parcelable, skopiowane pomidzy obydwoma
projektami. Dlatego po obydwu stronach widzimy pliki IStockQuoteService.aidl, Person.java
i Person.aidl. Poniewa kompilator AIDL generuje z artefaktw jzyka AIDL interfejs Java, klasy
poredniczce i tak dalej, podczas kopiowania tych artefaktw do projektu klienta tworzony
jest plik IStockQuoteService.java po jego stronie.
Wiemy ju, w jaki sposb wymienia zoone typy danych pomidzy usugami a klientami. Zajmijmy si pokrtce innym istotnym aspektem wywoywania usug: rnicami pomidzy synchronicznym a asynchronicznym wywoywaniem usugi.
Wszystkie wywoania usug s przeprowadzane w sposb synchroniczny. Nasuwa si oczywiste
pytanie, czy musimy implementowa wszystkie wywoania usug w wtku roboczym. Niekoniecznie. Dla wikszoci innych platform standardem jest wykorzystywanie przez klienta usugi,
ktra jest dla niego zupenie nieznanym obiektem, a zatem musi on przedsiwzi odpowiednie

Rozdzia 11 Tworzenie i uytkowanie usug

395

Rysunek 11.10. Artefakty usugi i klienta

rodki ostronoci przed wywoaniem usugi. W przypadku Androida najprawdopodobniej


bdziemy zna usugi (czsto sami jestemy ich twrcami), zatem moemy podj przemylan
decyzj. Jeli wiemy, e wywoywana metoda bdzie wymagaa duej mocy obliczeniowej, powinnimy si zastanowi nad umieszczeniem jej w wtku pobocznym. Jeli za jestemy przekonani o tym, e metoda nie bdzie powodowaa problemw z wydajnoci, moemy spokojnie
umieci j w wtku interfejsu uytkownika. W przypadku gdy stwierdzimy, e wywoanie usugi
najlepiej umieci w wtku roboczym, moemy utworzy wtek, a nastpnie wywoa usug.
Nastpnie mona przekazywa komunikaty do wtku interfejsu UI.

Przykad aplikacji uytkowej korzystajcej z usug


Prezentowalimy dotychczas rnorodne sposoby wywoywania usug HTTP oraz implementowania usug systemu Android. W tym podrozdziale pokaemy, w jaki sposb mona
przetumaczy tekst z jednego jzyka na inny za pomoc tych usug oraz interfejsu Tumacz
Google, ktry stanowi usug internetow opart na protokole HTTP. Najpierw jednak powicimy nieco czasu na omwienie tego interfejsu.

Interfejs Tumacz Google


Urzdzenia mobilne nie do koca si nadaj do tumaczenia tekstu. Jzyk polski skada si
z setek tysicy, moe nawet z ponad miliona wyrazw (w zalenoci od tego, jak definiujemy
sowo jzyk polski). Wczytanie wszystkich wyrazw i regu danego jzyka do pamici urzdzenia mobilnego w celu zapewnienia poprawnego przetumaczenia tekstu z jednego jzyka na
drugi jest na razie nieosigalne.

396 Android 3. Tworzenie aplikacji


Firma Google umiecia w internecie interfejs API sucy do tumaczenia tekstu. Pobiera on
cig znakw tekstowych oraz dwie specyfikacje jzykw: jedn dla jzyka rdowego i drug
dla jzyka docelowego. Istnieje jednak pewien haczyk. Pocztkowo intencja tej usugi miaa by
wywoywana z poziomu stron WWW, a nie urzdze mobilnych. Warunki korzystania z interfejsu Google AJAX Language API (taka jest jego oficjalna nazwa) nie zawieraj informacji na
temat urzdze uywajcych Androida, inaczej ni to ma miejsce w przypadku warunkw korzystania z interfejsu API Google Maps. Poniszy odnonik skieruje nas do informacji o warunkach korzystania z interfejsu AJAX Language API:
http://code.google.com/apis/ajaxlanguage/terms.html
Chocia nie jest do koca jasne, czy projektanci aplikacji dla systemu Android mog korzysta
z tego interfejsu, to jednak prezentacj tego interfejsu na konferencji Google I/O w maju 2009
roku przeprowadzono na programie uruchomionym w Androidzie! By moe do chwili wydania tej ksiki firma Google okreli oddzielne warunki korzystania z interfejsu AJAX Language
API dla systemw Android lub opublikuje aktualizacj tych warunkw, w ktrej sprecyzowane
zostan dopuszczalne formy stosowania tego interfejsu w Androidzie. Istnieje rwnie wersja 2
tego interfejsu, stworzona przez firm Google Labs, warto wic rwnie zwrci uwag na jej
rozwj1. Tymczasem mamy kilka moliwoci. Po pierwsze, moemy bezporednio zastosowa
interfejs AJAX Language API z poziomu naszej aplikacji, co te wkrtce zaprezentujemy. Po
drugie, moemy uzyska dostp do tego interfejsu za pomoc kontrolowanego przez siebie serwera sieciowego, na przykad serwera bdcego porednikiem dla interfejsu AJAX Language API.
Nasza aplikacja bdzie poczona z serwerem sieciowym, ktry bdzie wywoywa ten interfejs.
Jeeli moemy wykorzysta wasny serwer sieciowy, bdzie o wiele atwiej wyczy dostp
do interfejsu AJAX Language API z poziomu aplikacji, poniewa pilnujemy punktu kontrolnego znajdujcego si pomidzy nimi. Oczywicie, na niewiele zda si kontrola dostpu aplikacji
do interfejsu, jeeli nie bdzie mona duej wykorzystywa tej usugi. Powinnimy zapewni
chocia jak form odpowiedzi ze strony aplikacji w przypadku utraty cznoci z usug firmy
Google, aby powiadomi o tym uytkownika. Jeeli nastpi sytuacja, w ktrej firma Google zada od nas zaprzestania korzystania z interfejsu AJAX Language API, tak naprawd nie bdziemy mie wielkiego wyboru; aplikacja zostaa zainstalowana na wielu urzdzeniach i bdzie
prbowaa si poczy z serwerem, chyba e wprowadzilimy do niej jakie rozwizanie zatrzymujce korzystanie z tego interfejsu.
Firma Google ma prawo zabroni dostpu do tej usugi, jednak nie mona tego wykona w prosty
sposb. W warunkach korzystania z interfejsu AJAX Language API nie okrelono koniecznoci
stosowania klucza API, chocia w dokumentacji dla programistw (http://code.google.com/apis/
ajaxlanguage/documentation/) znalazo si stwierdzenie, e naley stosowa atrybut REFERER
oraz e powinnimy uywa klucza API. Bez tych elementw dania pochodzce z urzdze
uytkownikw bd pojawiay si anonimowo i firma Google nie bdzie posiadaa moliwoci
skontaktowania si z programist w przypadku wystpienia jakich problemw zwizanych ze
stosowaniem tego interfejsu API. W poniszym przykadzie wida, e wprowadzilimy warto
nagwka REFERER (w kodzie Translator.java), lecz ominlimy fragment zwizany z kluczem
API. Jeeli chcemy przesa warto klucza API do interfejsu AJAX Language, musimy najpierw
go otrzyma od firmy Google. Zwracamy uwag, e do omawianego interfejsu nie naley wprowadza stosowanych wczeniej kluczy API zwizanych z aplikacj Google Maps. Aby zare1

Przewidywania autorw okazay si mylne; w momencie wydania polskiej wersji ksiki caa rodzina
interfejsw Google Language zostaa zdeprecjonowana i przestanie by obsugiwana w grudniu
2011 roku. Wyjtkiem jest wersja 2 tego interfejsu, ktra staa si patn usug przyp. tum.

Rozdzia 11 Tworzenie i uytkowanie usug

397

jestrowa klucz API AJAX, wystarczy wysa adres URL swojej strony WWW (dokadnie taki
sam jak wprowadzony w nagwku REFERER) i zaakceptowa warunki korzystania z usug. Otrzymawszy klucz API, naley doda go do adresu URL interfejsu API AJAX w nastpujcy sposb:
&key=Your_API_key_goes_here_with_no_quotation_marks

Jeeli zdecydujemy si przekaza interfejsowi API AJAX klucz API, warto atrybutu REFERER
musi by adresem URL lub jakim jego podelementem za pomoc ktrego zosta utworzony ten klucz. W przeciwnym wypadku nie otrzymamy wynikw.

Stosowanie interfejsu Tumacz Google


W pozostaej czci rozdziau pokaemy, w jaki sposb utworzy aplikacj wywoujc bezporednio interfejs Google AJAX Language API. Do tej pory zaprezentowalimy pojedynczo wszystkie elementy potrzebne do tumaczenia. Teraz poczymy je ze sob. W poniszym przykadzie
zbudujemy program zawierajcy edytowalne pole EditText, kontrolki Spinner pozwalajce na
wybr jzyka rdowego i docelowego, a take drugie pole EditText (tylko do odczytu) wywietlajce przetumaczony wynik. Usug bdziemy wywoywa poprzez internet. W celu
odizolowania interfejsu UI od procesu przetwarzania, ktry moe by dosy czasochonny,
wykorzystamy usug. Jednym z zamieszczonych przez nas dodatkw w tej aplikacji jest projekt
Jakarta Commons Lang, umoliwiajcy przede wszystkim wykorzystanie funkcji unescape do
wywietlania kodw znakw XML w systemie Unicode. Omwimy rwnie ten temat. Na
rysunku 11.11 widzimy ukad graficzny tej aplikacji.

Rysunek 11.11. Interfejs uytkownika aplikacji demonstracyjnej sucej do tumacze

Na kod naszej aplikacji bd si skada nastpujce pliki:


/res/layout/main.xml (listing 11.36),
/res/values/strings.xml (listing 11.37),
/res/values/arrays.xml (listing 11.38),
ITranslate.aidl w katalogu /src (listing 11.39),
MainActivity.java (listing 11.40),
TranslateService.java (listing 11.41), zapewniajcy semantyk usugi,
Translator.java (listing 11.42), zawierajcy wywoanie waciwej usugi firmy Google,
AndroidManifest.xml (listing 11.43).
W przypadku tej aplikacji zastosowalimy klas HttpURLConnection, a nie HttpClient, zatem
Czytelnik moe si przekona, w jaki sposb jest ona wykorzystywana przez standardowy
program uytkowy.

398 Android 3. Tworzenie aplikacji


Listing 11.36. Ukad graficzny XML sucy do zaimplementowania wersji demonstracyjnej
aplikacji tumaczcej
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<EditText android:id="@+id/input"
android:hint="@string/input"
android:layout_height="wrap_content"
android:layout_width="fill_parent" />
<Spinner android:id="@+id/from"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/input"
android:prompt="@string/prompt" />
<Button android:id="@+id/translateBtn"
android:text="@string/translateBtn"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/input"
android:layout_toRightOf="@id/from"
android:enabled="false" />
<Spinner android:id="@+id/to"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/input"
android:layout_toRightOf="@id/translateBtn"
android:prompt="@string/prompt" />
<EditText android:id="@+id/translation"
android:hint="@string/translation"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:editable="false"
android:layout_below="@id/from" />
<TextView android:id="@+id/poweredBy"
android:text="powered by Google"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
</RelativeLayout>

Rozdzia 11 Tworzenie i uytkowanie usug

Listing 11.37. Plik zasobw zawierajcy cigi znakw


<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/values/strings.xml -->


<resources>
<string name="translateBtn">> Tumacz ></string>
<string name="input">Wprowad tekst do przetumaczenia</string>
<string name="translation">Tutaj pojawi si tumaczenie tekstu</string>
<string name="prompt">Wybierz jzyk</string>
</resources>

Listing 11.38. Plik zasobw zawierajcy tablice


<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/values/arrays.xml -->


<resources>
<string-array name="languages">
<item>Chiski</item>
<item>Polski</item>
<item>Francuski</item>
<item>Niemiecki</item>
<item>Japoski</item>
<item>Hiszpaski</item>
</string-array>
<string-array name="language_values">
<item>zh</item>
<item>pl</item>
<item>fr</item>
<item>de</item>
<item>ja</item>
<item>es</item>
</string-array>
</resources>

Listing 11.39. Plik AIDL usugi tumacza


// Jest to plik ITranslate.aidl umieszczony w katalogu /src
interface ITranslate {
String translate(in String text, in String from, in String to);
}

Listing 11.40. Gwny plik aplikacji MainActivity.java


// Jest to plik MainActivity.java
import
import
import
import
import
import
import
import

android.app.Activity;
android.content.ComponentName;
android.content.Context;
android.content.Intent;
android.content.ServiceConnection;
android.os.Bundle;
android.os.Handler;
android.os.IBinder;

399

400 Android 3. Tworzenie aplikacji


import
import
import
import
import
import
import
import

android.util.Log;
android.view.View;
android.view.View.OnClickListener;
android.widget.ArrayAdapter;
android.widget.Button;
android.widget.EditText;
android.widget.Spinner;
android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener {


static final String TAG = "Translator";
private EditText inputText = null;
private TextView outputText = null;
private Spinner fromLang = null;
private Spinner toLang = null;
private Button translateBtn = null;
private String[] langShortNames = null;
private Handler mHandler = new Handler();
private ITranslate mTranslateService;
private ServiceConnection mTranslateConn = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
mTranslateService = ITranslate.Stub.asInterface(service);
if (mTranslateService != null) {
translateBtn.setEnabled(true);
} else {
translateBtn.setEnabled(false);
Log.e(TAG, "Nie mona znale usugi TranslateService");
}
}
public void onServiceDisconnected(ComponentName name) {
translateBtn.setEnabled(false);
mTranslateService = null;
}
};
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
inputText = (EditText) findViewById(R.id.input);
outputText = (EditText) findViewById(R.id.translation);
fromLang = (Spinner) findViewById(R.id.from);
toLang = (Spinner) findViewById(R.id.to);
langShortNames = getResources().getStringArray(R.array.language_values);
translateBtn = (Button) findViewById(R.id.translateBtn);
translateBtn.setOnClickListener(this);
ArrayAdapter<?> fromAdapter = ArrayAdapter.createFromResource(this,
R.array.languages, android.R.layout.simple_spinner_item);
fromAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);

Rozdzia 11 Tworzenie i uytkowanie usug

401

fromLang.setAdapter(fromAdapter);
fromLang.setSelection(1); // Polski
ArrayAdapter<?> toAdapter = ArrayAdapter.createFromResource(this,
R.array.languages,android.R.layout.simple_spinner_item);
toAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
toLang.setAdapter(toAdapter);
toLang.setSelection(3); // Niemiecki
inputText.selectAll();
Intent intent = new Intent(Intent.ACTION_VIEW);
bindService(intent, mTranslateConn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mTranslateConn);
}
public void onClick(View v) {
if (inputText.getText().length() > 0) {
doTranslate();
}
}

private void doTranslate() {


mHandler.post(new Runnable() {
public void run() {
String result = "";
try {
int fromPosition = fromLang.getSelectedItemPosition();
int toPosition = toLang.getSelectedItemPosition();
String input = inputText.getText().toString();
if(input.length() > 5000)
input = input.substring(0,5000);
Log.v(TAG,"Tumaczenie z " + langShortNames[fromPosition] + " na " +
langShortNames[toPosition]);
result = mTranslateService.translate(input,
langShortNames[fromPosition],
langShortNames[toPosition]);
if (result == null) {
throw new Exception("Proces tumaczenia zakoczony niepowodzeniem");
}
outputText.setText(result);
inputText.selectAll();
} catch (Exception e) {
Log.e(TAG, "Bd: " + e.getMessage());
}
}
});
}

402 Android 3. Tworzenie aplikacji


Listing 11.41. Plik usugi tumaczenia TranslateService.java
// Jest to plik TranslateService.java
import
import
import
import

android.app.Service;
android.content.Intent;
android.os.IBinder;
android.util.Log;

public class TranslateService extends Service {


public static final String TAG = "TranslateService";
private final ITranslate.Stub mBinder = new ITranslate.Stub() {
public String translate(String text, String from, String to) {
try {
return Translator.translate(text, from, to);
} catch (Exception e) {
Log.e(TAG, "Nie udao si przetumaczy: " + e.getMessage());
return null;
}
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}

Listing 11.42. Plik zawierajcy funkcj tumaczenia


// Jest to plik Translator.java
import
import
import
import
import
import

java.io.BufferedReader;
java.io.InputStream;
java.io.InputStreamReader;
java.net.HttpURLConnection;
java.net.URL;
java.net.URLEncoder;

import org.apache.commons.lang.StringEscapeUtils;
import org.json.JSONObject;
import android.util.Log;
public class Translator {
private static final String ENCODING = "UTF-8";
private static final String URL_BASE =
"http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&langpair=";
private static final String INPUT_TEXT = "&q=";
private static final String MY_SITE = "http://my.website.com";
private static final String TAG = "Translator";
public static String translate(String text, String from, String to) throws Exception
{
try {
StringBuilder url = new StringBuilder();
url.append(URL_BASE).append(from).append("%7C").append(to);

Rozdzia 11 Tworzenie i uytkowanie usug

403

url.append(INPUT_TEXT).append(URLEncoder.encode(text, ENCODING));
HttpURLConnection conn = (HttpURLConnection) new URL(url.toString())
.openConnection();
conn.setRequestProperty("REFERER", MY_SITE);
conn.setDoInput(true);
conn.setDoOutput(true);
try {
InputStream is= conn.getInputStream();
String rawResult = makeResult(is);

JSONObject json = new JSONObject(rawResult);


String result = ((JSONObject)json.get("responseData"))
.getString("translatedText");
return (StringEscapeUtils.unescapeXml(result));
} finally {
conn.getInputStream().close();
if(conn.getErrorStream() != null)
conn.getErrorStream().close();
}
} catch (Exception ex) {
throw ex;
}

private static String makeResult(InputStream inputStream) throws Exception {


StringBuilder outputString = new StringBuilder();
try {
String string;
if (inputStream != null) {
BufferedReader reader =
new BufferedReader(new InputStreamReader(inputStream, ENCODING));
while (null != (string = reader.readLine())) {
outputString.append(string).append('\n');
}
}
} catch (Exception ex) {
Log.e(TAG, "Bd podczas odczytu strumienia tumaczenia.", ex);
}
return outputString.toString();
}
}

Listing 11.43. Plik AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik AndroidManifest.xml -->


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.translation"
android:versionName="1.0"
android:versionCode="1" >
<application android:label="Tumaczenie"
android:icon="@drawable/icon">
<activity android:name="MainActivity" android:label="Translate">

404 Android 3. Tworzenie aplikacji


<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="TranslateService" android:label="Translate">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

Przed poprawnym skompilowaniem przykadu musimy wprowadzi klas pomocnicz. W projekcie Jakarta Commons Lang znajduje si klasa StringEscapeUtils, ktr wykorzystamy do
konwersji wynikowego cigu znakw z interfejsu AJAX Language API na tekst zrozumiay dla
uytkownika. Interfejs ten odsya nam obiekty XML reprezentujce okrelone znaki specjalne.
Na przykad odpowiednikiem apostrofu jest tu warto &#39;. Te znaki specjalne musz by
wywietlane w sposb zrozumiay dla uytkownika. W tym celu zastosujemy projekt Jakarta
Commons Lang. Mona go znale pod adresem:
http://commons.apache.org/lang/
Naley otworzy stron projektu Jakarta Commons Lang i pobra plik commons-lang.zip lub
commons-lang.tar, w ktrym zawarte s pliki .jar. Nastpnie naley je rozpakowa. W rodowisku Eclipse wybieramy projekt, klikamy jego nazw prawym przyciskiem myszy i wskazujemy
opcje Build Path/Configure Build Path. Klikamy zakadk Libraries i wybieramy opcj Add
External JARs. Wyszukujemy pobrany plik commons-lang i dodajemy go. Aby zakoczy proces
dodawania pliku, klikamy przycisk OK. Caa aplikacja powinna zosta bezbdnie zbudowana.
Nic nie stoi na przeszkodzie, eby j teraz wyprbowa. Jeeli nie wyglda ona zbyt dobrze
w orientacji pionowej, moemy wyprbowa skrt klawiaturowy Ctrl+F11, aby przeczy
emulator w tryb orientacji poziomej. Jeeli wtpimy w poprawno generowanych wynikw,
moemy je porwna z tumaczeniem dostpnym na serwerze Google:
http://www.google.com/uds/samples/language/translate.html
Chcielibymy uczuli Czytelnika na kilka spraw. Z powodu okrelonych zapisw w warunkach
korzystania z usugi Google nasza przykadowa aplikacja zawiera w interfejsie uytkownika cig
znakw powered by Google. Te same warunki okrelaj maksymalny limit 5000 wprowadzanych
znakw, zatem po przekroczeniu tej liczby nadmiar znakw jest usuwany. Prawdopodobnie
chcemy zaprojektowa tu nieco inny model, na przykad umoliwiajcy dzielenie tekstu na
edytowalne fragmenty, ktre s nastpnie przesyane do interfejsu API. Celowo stworzylimy
krtk list dostpnych jzykw, aby nasza aplikacja bya atwiejsza do zarzdzania, mona
jednak bez problemu zamieci dowoln liczb jzykw w tablicy cigw znakw. Musimy
mie jednak wiadomo, e czcionki Droid mog nie posiada kompletu znakw dla niektrych
jzykw, ktre s dostpne w tumaczu. Jeeli tumaczenie wynikowe wyglda podejrzanie,
prawdopodobnie mamy problem z czcionk. Mona temu zapobiec poprzez wprowadzenie
dodatkowych czcionek, nie jest to jednak tematem tego rozdziau. Odpowiedzi interfejsu API
przybieraj posta formatu JSON. Zatem bdziemy za pomoc tego formatu poddawa analizie

Rozdzia 11 Tworzenie i uytkowanie usug

405

skadniowej zwracane wynikowe cigi znakw. Format JSON stanowi cz struktury Androida, zatem nie musielimy go pobiera jako osobnego pliku .jar.
Jedn z cech interfejsu AJAX Language API jest brak koniecznoci wskazania jzyka rdowego. Interfejs ten sprbuje samodzielnie ustali, jaki jzyk jest uywany. Jeeli chcemy skorzysta
z takiego rozwizania, nie zamieszczamy wartoci jzyka rdowego w przekazywanym adresie
URL, lecz zamiast tego w atrybucie langpair= zamieszczamy warto %7C. Jest to przydatna
funkcja, jeli nie jestemy pewni, jaki jzyk rdowy zosta uyty; jednak jeeli ilo wprowadzonego tekstu jest zbyt maa, interfejs API moe nie rozpozna jzyka.

Odnoniki
Poniej prezentujemy przydatne odnoniki, pomagajce zapozna si dokadniej z omawianymi tematami:
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw projektw
utworzonych specjalnie na potrzeby ksiki. Waciwy plik znajdziesz w katalogu
o nazwie ProAndroid3_R11_Usugi. Dostpny jest tu take plik Czytaj.TXT, stanowicy
dokadn instrukcj importowania projektw do rodowiska Eclipse.
http://hc.apache.org/httpcomponents-client-ga/tutorial/html/ strona ta zawiera
znakomite samouczki dotyczce klas HttpClient, w tym rwnie informacje
o uwierzytelnianiu i korzystaniu z plikw cookies.

Podsumowanie
Cay niniejszy rozdzia zosta powicony usugom. Omwilimy sposb uytkowania zewntrznych usug HTTP za pomoc moduu HttpClient firmy Apache, a take metody pisania
usug przetwarzanych w tle. Pod ktem moduu HttpClient zademonstrowalimy, w jaki sposb mona wywoywa metody HTTP GET oraz HTTP POST. Pokazalimy take zastosowanie
wieloczciowej metody POST.
Druga cz rozdziau dotyczya pisania usug dla systemu Android. W szczeglnoci zajlimy
si tworzeniem usug lokalnych i usug zdalnych. Stwierdzilimy, e usuga lokalna jest uytkowana przez skadniki (na przykad aktywnoci) tego samego procesu, w ktrym znajduje si ta
usuga. Klienty usug zdalnych znajduj si poza procesami, ktre wywoay te usugi.

406 Android 3. Tworzenie aplikacji

R OZDZIA

12
Analiza pakietw

We wszystkich dotychczasowych rozdziaach zajmowalimy si podstawowymi skadnikami systemu Android. Chcemy zauway, e to bya atwiejsza cz podry
przez Androida. Poczwszy od niniejszego, w kilku nastpnych rozdziaach (12., 13.,
14. i 15.) przyjrzymy si dokadniej kolejnemu poziomowi organizacji Androida.
Badanie rozpoczniemy od zajrzenia w gb pakietw, procesu ich podpisywania,
wspdzielenia danych pomidzy nimi oraz zapoznania si z projektami bibliotek.
Zrozumiemy take kontekst linuksowego procesu, w ktrym jest uruchomiony plik
.apk. Dowiemy si te, w jaki sposb wiele plikw .apk wspdzieli dane i zasoby za
pomoc tego kontekstu.
Chocia w rozdziale 10. dowiedzielimy si co nieco na temat podpisywania pakietw w Androidzie, dopiero teraz poznamy znaczenie, implikacje oraz zastosowania
podpisanych plikw JAR. W kontekcie wspdzielenia plikw zainteresujemy si
rwnie projektami bibliotek Androida, aby zrozumie, w jaki sposb dziaaj oraz
czy mona ich uywa do wspdzielenia zasobw i kodu.
Rozpocznijmy od przypomnienia podstawowych informacji na temat pliku .apk,
gdy to stanowi baz dalszych rozwaa na temat procesw Androida.

Pakiety i procesy
Jak ju widzielimy w poprzednich rozdziaach, proces tworzenia aplikacji koczy
si utworzeniem pliku .apk, ktry zostaje nastpnie podpisany i wdroony do uytkowania w urzdzeniu. Zobaczmy, czego jeszcze moemy si dowiedzie o pakietach systemu Android.

Szczegowa specyfikacja pakietu


Kady plik .apk posiada swj wasny, niepowtarzalny identyfikator, oparty na nazwie pakietu, ktry zostaje zdefiniowany w pliku manifecie. Poniej prezentujemy
przykadow definicj, ktra bdzie wykorzystywana w tym rozdziale (nazwa pakietu zostaa oznaczona pogrubionym drukiem):

408 Android 3. Tworzenie aplikacji


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.library.testlibraryapp"
...>

...pozostae wzy xml


</manifest>

Jeeli twrca tego pakietu podpisa go i zainstalowa, nikt inny nie bdzie mg go aktualizowa. Nazwa pakietu jest cile zwizana z sygnatur, za pomoc ktrej zosta podpisany. W wyniku tego programista posiadajcy inn sygnatur nie moe podpisywa i instalowa pakietu przy
uyciu nazwy wykorzystywanej ju przez kogo innego.

Przeksztacanie nazwy pakietu w nazw procesu


Android wykorzystuje nazw pakietu do utworzenia procesu, w ktrym bd dziaay skadniki
tego pakietu. Zostaje rwnie przydzielony unikatowy identyfikator uytkownika, okrelajcy
ten uruchomiony proces. Identyfikator ten jest w istocie wykorzystywany przez system operacyjny Linux, ktry stanowi podstaw Androida. Informacje te znajdziemy podczas przegldania szczegw na temat zainstalowanego pakietu.

Tworzenie listy zainstalowanych pakietw


Aby przejrze list pakietw zainstalowanych w emulatorze, naley uruchomi ekran startowy
i otworzy Dev Tools/Package Browser. Warto zauway, e moemy (cho nie musimy) posiada podobn przegldark pakietw na urzdzeniu fizycznym. Moe si to rwnie zmieni
w zalenoci od wersji Androida.
Po otwarciu takiej listy moemy zaznaczy pakiet jakiej aplikacji, dajmy na to przegldarki,
i klikn jego nazw. Zobaczymy okno zawierajce szczegowe informacje o pakiecie, takie jak
to widoczne na rysunku 12.1.

Rysunek 12.1. Szczegowe informacje dotyczce pakietu Androida

Rozdzia 12 Analiza pakietw

409

Na rysunku 12.1 wida nazw procesu, zdefiniowan przez nazw pakietu Java w pliku AndroidManifest.xml, oraz niepowtarzalny identyfikator uytkownika przydzielony do tego
pakietu. W przypadku aplikacji przegldarkowej nazwa pakietu wskazywana w pliku manifecie to com.android.browser (na rysunku 12.1 jako warto atrybutu Process).
Wszelkie zasoby utworzone przez ten proces lub pakiet zostan zabezpieczone za pomoc tego
linuksowego identyfikatora. Na tej licie wyszczeglniono rwnie poszczeglne skadniki pakietu. Przykadowymi skadnikami s aktywnoci, usugi oraz odbiorcy komunikatw.

Usuwanie pakietu za pomoc aplikacji Package Browser


Skoro zajmujemy si tematyk przegldarki pakietw, naleaoby zauway, e moemy rwnie usun pakiet z emulatora. W tym celu:
1. Zaznacz pakiet.
2. Wcinij przycisk Menu.
3. Wybierz opcj Delete package, aby usun pakiet.
Wspomnielimy wczeniej, e proces jest zwizany z nazw pakietu, z kolei nazwa pakietu jest
uzaleniona od podpisu cyfrowego. Podpis cyfrowy jest elementem mechanizmu zabezpieczajcego dane nalece do pakietu. Aby w peni zrozumie implikacje tej zalenoci, przeledmy
natur procesu podpisywania aplikacji.

Jeszcze raz o procesie podpisywania pakietw


W rozdziale 10. Czytelnik mia okazj zapozna si z mechanizmem podpisywania aplikacji.
Podkrelilimy, e jest to wymagana czynno przed zainstalowaniem aplikacji na urzdzeniu.
Jednak nie wyjanilimy, dlaczego podpisywanie aplikacji jest konieczne oraz jakie s tego
implikacje.
Nie musimy na przykad podpisywa aplikacji podczas jej pobierania i instalowania w systemie
Windows lub jakim innym. Dlaczego wic podpisywanie programw jest w przypadku Androida czynnoci obligatoryjn? Co ten proces tak naprawd oznacza? Co otrzymujemy dziki
niemu? Czy istniej jakie analogiczne przykady, do ktrych moemy go szybko porwna?
W niniejszym podrozdziale odpowiemy na powysze pytania.
W trakcie instalowania pakietw w urzdzeniu niezbdne jest zapewnienie unikatowej lub oddzielnej nazwy rodowiska Java kademu nowemu pakietowi. Jeeli sprbujemy wdroy aplikacj o takiej samej nazwie co ju jaka zainstalowana, urzdzenie nie pozwoli na to, dopki nie
usuniemy tego pierwszego pakietu. Jeeli chcemy umoliwi aktualizowanie aplikacji, nowy pakiet musi by powizany z tym samym wydawc programw co stary. Do tego celu su wanie podpisy cyfrowe. W czasie czytania nastpnych podrozdziaw Czytelnik przekona si, e
za pomoc procesu podpisywania gwarantujemy sobie zarezerwowanie nazwy pakietu.
Przyjrzyjmy si kilku scenariuszom, dziki ktrym w peni zrozumiemy i przyswoimy sobie
wiedz na temat podpisw cyfrowych.

410 Android 3. Tworzenie aplikacji

Zrozumienie koncepcji podpisw cyfrowych scenariusz 1.


Wyobramy sobie, e jestemy kolekcjonerami win mieszkajcymi w bardzo nieprzyjaznym dla
wina miejscu, powiedzmy, e na Saharze. Co wicej, winiarze z caego wiata przysyaj nam na
przechowanie lub na sprzeda beczuki z winem.
Jako profesjonalni kolekcjonerzy mamy pen wiadomo, e kada beczuka zawiera wino
posiadajce specyficzny bukiet oraz barw, niepowtarzalne wrd pozostaych rodzajw win.
Po dokadniejszym sprawdzeniu stwierdzimy, e jeli nawet w dwch beczukach znajd si wina,
ktre maj taki sam bukiet, to zawsze pochodz od tego samego producenta. Przygldajc si
tej sprawie jeszcze uwaniej, dowiemy si, e kady winiarz posiada sekretny przepis na swj
niepowtarzalny bukiet, pilnie strzeony i nieujawniany nikomu. W ten sposb staje si zrozumiae, dlaczego kade wino jest inne oraz dlaczego wina o tym samym bukiecie musz pochodzi od tego samego producenta. Oczywicie, tego rodzaju identyfikacja nie zdradza tosamoci winiarza upewnia nas jedynie, e jest on jedyny w swoim rodzaju.
Bukiet staje si sygnatur tego winiarza, podobnie jak piecz rodowa, a receptura pozwalajca
na jego otrzymanie staje si najskrztniej skrywan tajemnic.
Wanym spostrzeeniem jest fakt, e jako kolekcjonerzy nie mamy moliwoci dowiedzie si,
ktry winiarz wysa nam dan dostaw wina do sygnatury nie docza si nazwiska ani adresu.
A nawet jeli takie dane byyby doczone, cakiem moliwe, e w rzeczywistoci winiarz mg je
wysa z innego miejsca. W takim przypadku moemy zaoy, e dwie beczuki wina przysane
z tego samego adresu, lecz zawierajce wina o rnym bukiecie, pochodz od dwch rnych
winiarzy przebywajcych w tym samym miejscu.

Zrozumienie koncepcji podpisw cyfrowych scenariusz 2.


Zastanwmy si nad innym, bardziej praktycznym przykadem. Gdy przebywamy za granic
i wczymy radio, usyszymy wiele piosenek. Usyszymy wielu wokalistw i bdziemy mogli ich
rozrni, nie bdziemy jednak nawet znali ich nazwisk. Mamy wic do czynienia z podpisaniem utworu barwa gosu kadego czowieka jest niepowtarzalna. Jeli kto poda nam nazwisko piosenkarza i powiemy je z danym utworem, bdzie to analogiczne do podpisywania
przez niezalenego wydawc.
Jeden wokalista moe imitowa drugiego, odpowiednio modulujc gos, aby zaciekawi lub oszuka suchacza. W przypadku podpisw cyfrowych takie oszustwo jest o wiele trudniejsze do przeprowadzenia z powodu algorytmw matematycznych stosowanych do szyfrowania podpisu.

Wyjanienie koncepcji podpisw cyfrowych


Gdy mwimy o podpisywaniu pliku JAR, uzyskuje on osobny bukiet i staje si rozrnialny
w zbiorze innych plikw JAR. Jednak nie ma stuprocentowej moliwoci zidentyfikowania programisty lub firmy. S to tak zwane samopodpisane pliki JAR.
Aby zna rdo miejsce pochodzenia wina, kolekcjoner musi uzyska informacje od kogo
zaufanego, e dany bukiet pochodzi od Firma1. Jeli teraz kolekcjoner zobaczy wino o takim
bukiecie, bdzie wiedzia, e jego producentem jest Firma1. Mamy tu do czynienia z plikami
JAR podpisanymi przez trzeci stron. Dane te s wykorzystywane przez przegldarki, kiedy
wywietlaj informacje, e pobieramy plik od Firma1 lub instalujemy aplikacj napisan
przez t firm (autorytatywnie).

Rozdzia 12 Analiza pakietw

411

Jak zatem tworzymy cyfrowy podpis


Podpisy cyfrowe, wykorzystujce semantyk opisan w powyszych scenariuszach, s implementowane za pomoc tak zwanego szyfrowania par kluczy: publicznym i prywatnym.
Stosowane s tutaj algorytmy matematyczne, w wyniku ktrych powstaj dwie liczby. Jedna
z nich suy do szyfrowania podpisu (klucz prywatny), a jedynie za pomoc drugiej da si tak
zaszyfrowany plik (wiadomo) rozkodowa (klucz publiczny). S to klucze asymetryczne. Nawet jeli wszyscy znaj klucz publiczny, nie ma moliwoci zaszyfrowania pliku za jego pomoc.
Mona tego dokona jedynie za pomoc klucza prywatnego, powizanego z tym kluczem publicznym.
Rozwamy koncepcj kluczy publicznych i prywatnych na naszym przykadzie z winami.
Winiarz, ktry pragnie rozpoznawa wina nie za pomoc bukietw, lecz podpisw cyfrowych,
za pomoc klucza prywatnego generuje kod (bukiet) przeznaczony dla okrelonej beczuki.
Poniewa do utworzenia tego kodu zosta uyty klucz prywatny, mona go rozszyfrowa jedynie za pomoc klucza publicznego.
Producent wina odwanie teraz zapisuje na beczuce nazw klucza publicznego oraz zaszyfrowany kod, ewentualnie powierza klucz publiczny kurierowi.
Teraz my, jako kolekcjonerzy win, po odebraniu tego klucza i skutecznym rozszyfrowaniu kodu
wiemy ju, e klucz publiczny jest poprawny i jedynie producent tego wina mg go podpisa.
Jeeli w tym scenariuszu jaki oszust skopiuje taki klucz publiczny i umieci go na beczce ze
swoim winem, nie bdzie mg napisa ukrytej wiadomoci odblokowywanej przez ten klucz.
Klucz publiczny staje si w istocie podpisem winiarza. Nawet jeli kto inny uzyska do niego
dostp, nie bdzie mg zaszyfrowa za jego pomoc adnych informacji.
Dziki porwnaniu sygnatur stosowanych w fizycznym wiecie z podpisami cyfrowymi atwiej
nam teraz bdzie uchwyci i zrozumie koncepcj podpisywania plikw. Przypomnimy jeszcze,
e w rozdziale 10. omwilimy ju technik stosowania polece keytool i jarsigner w procesie
podpisywania pliku aplikacji.

Implikacje wynikajce z podpisywania plikw


Teraz ju rozumiemy, dlaczego nie moemy posiada dwch rnicych si podpisw dla jednej
nazwy pakietu. Podpisy tego typu s czasami nazywane certyfikatami infrastruktury klucza publicznego (ang. Public Key Infrastructure PKI). cilej rzecz biorc, za pomoc certyfikatu
PKI podpisujemy pakiety, pliki JAR, biblioteki DLL lub aplikacje.
Certyfikat PKI jest powizany z nazw pakietu w taki sposb, e instalacja dwch pakietw posiadajcych tak sam nazw, wydanych przez rnych producentw, jest niemoliwa. Jednak
mona uywa tego samego certyfikatu w przypadku wielu osobnych pakietw. Inaczej mwic,
jeden certyfikat PKI obsuguje wiele pakietw. Jest to relacja typu jeden do wielu. Jednak pojedynczy pakiet uzyskuje tylko i wycznie jeden certyfikat za porednictwem infrastruktury PKI.
Twrca zabezpiecza nastpnie klucz prywatny za pomoc hasa.
Powysze informacje s bardzo wane nie tylko ze wzgldu na moliwo aktualizowania aplikacji, lecz rwnie dla procesu wspdzielenia danych pomidzy pakietami, ktre s podpisane
za pomoc tej samej sygnatury.

412 Android 3. Tworzenie aplikacji

Wspdzielenie danych pomidzy pakietami


W poprzednich rozdziaach ustalilimy, e kady pakiet jest uruchomiony w osobnym procesie.
Wszystkie dodatkowe skadniki, ktre zostay zainstalowane lub utworzone w pakiecie, nale
do uytkownika, ktrego identyfikator jest przydzielony do tego pakietu. Wiemy take, e
Android przydziela unikatowy, linuksowy identyfikator, pozwalajcy na uruchomienie tego
pakietu. Na rysunku 12.1 widzimy konstrukcj takiego identyfikatora. Zgodnie z dokumentacj pakietu SDK:
Ten identyfikator uytkownika zostaje przypisany w momencie instalowania aplikacji
i pozostaje niezmienny przez cay okres istnienia aplikacji w urzdzeniu. Wszelkie
dane zachowane przez aplikacj otrzymaj taki identyfikator uytkownika, a nie
identyfikator dostpny dla pozostaych pakietw. Podczas tworzenia nowego pliku za
pomoc metod getSharedPreferences(String, int), openFileOutput(String, int)
lub openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)
moemy wykorzysta flagi MODE_WORLD_READABLE oraz (lub) MODE_WORLD_WRITEABLE
do umoliwienia odczytywania lub zapisywania pliku przez inne pakiety. Po ustanowieniu
tych flag plik cigle naley do aplikacji, jednak uprawnienia globalnego odczytu lub
zapisu zostay waciwie wprowadzone, wic kada inna aplikacja moe z nich korzysta.
Jeeli mamy zamiar umoliwi wspprac pomidzy aplikacjami korzystajcymi ze wsplnego
zestawu danych, moemy w sposb jawny zdefiniowa unikatowy identyfikator uytkownika,
ktry bdzie spenia nasze wymagania. Taki wspdzielony identyfikator naley zdefiniowa
w pliku manifecie. Przypomina on nieco definicj nazwy pakietu. Przykad zosta ukazany na
listingu 12.1.
Listing 12.1. Deklarowanie wspdzielonego identyfikatora uytkownika
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.jakispakiet"
sharedUserId="com.androidbook.mojwspoldzielonyiduzytkownika"

...
>

...pozostae wzy xml


</manifest>

Natura wspdzielonych identyfikatorw uytkownika


Wiele aplikacji moe posiada ten sam wspdzielony identyfikator uytkownika, pod warunkiem e zawieraj one t sam sygnatur (podpisan za pomoc certyfikatu PKI). Posiadanie
takiego identyfikatora pozwala aplikacjom na wspdzielenie danych, a nawet na dziaanie
w obrbie tego samego procesu. Jeeli nie chcemy zduplikowa wspdzielonego identyfikatora
uytkownika, stosujmy konwencj nazewnictwa analogiczn do nazewnictwa klas Java. Poniej prezentujemy dwa przykadowe wspdzielone identyfikatory uytkownika spotykane
w Androidzie:
android.uid.system
android.uid.phone

Natknlimy si na informacj, e taki wspdzielony identyfikator musi zosta


zdefiniowany jako nieprzetworzony cig znakw, a nie jako zasb typu String.

Rozdzia 12 Analiza pakietw

413

Chcielibymy w tym miejscu ostrzec Czytelnika: jeeli zechce korzysta ze wspdzielonego


identyfikatora, zalecamy jego uywanie od samego pocztku procesu pisania aplikacji. W przeciwnym wypadku mog si pojawia problemy, jeli aplikacja w wersji nieposiadajcej identyfikatora zostanie zaktualizowana do wersji zawierajcej ten identyfikator. Wynika to z tego, e
w takim przypadku Android wykona polecenie chown na starych zasobach z powodu zmiany
identyfikatora uytkownika. Dlatego usilnie zalecamy:
W razie potrzeby naley stosowa identyfikator uytkownika od samego pocztku
budowy aplikacji.
Nie naley zmienia ju ustanowionego identyfikatora uytkownika.

Schemat kodu wykorzystywanego


przy wspdzieleniu danych
W tym podrozdziale zajmiemy si moliwociami pyncymi ze wspdzielenia zasobw i danych pomidzy dwiema aplikacjami. Jak wiemy, zasoby i dane umieszczone w kadym pakiecie
s chronione i nale do kontekstu tego pakietu w trakcie jego dziaania. Nie powinno zatem
nas zaskoczy, e potrzebujemy dostpu do kontekstu zawierajcego dane i zasoby, ktre
chcemy wspdzieli.
Do tego celu pomocny bdzie interfejs API createPackageContext(). Moemy wykorzysta
go wobec dowolnego istniejcego obiektu kontekstu (na przykad aktywnoci), aby uzyska
odniesienie do docelowego kontekstu, z ktrym chcemy nawiza wspprac. Na listingu
12.2 zosta umieszczony przykadowy kod (jest jedynie pogldowy, nie jest przeznaczony do
kompilacji).
Listing 12.2. Zastosowanie interfejsu createPackageContext()
//Identyfikuje pakiet, ktry chcemy wykorzysta
String targetPackageName="com.androidbook.samplepackage1";

//Okrelamy odpowiedni flag kontekstu


int flag=Context.CONTEXT_RESTRICTED;

//Za pomoc jednej z aktywnoci pobieramy kontekst


Activity myContext = ;
Context targetContext =
myContext.createPackageContext(targetPackageName, flag);

//Wykorzystujemy kontekst do okrelenia cieek do plikw


Resources res = targetContext.getResources();
File path = targetContext.getFilesDir();

Zwrmy uwag, w jaki sposb moemy uzyska odniesienie do kontekstu danego pakietu, na
przykad com.androidbook.samplepackage1. Obiekt targetContext widoczny na listingu
12.2 jest taki sam jak kontekst przekazywany docelowej aplikacji w momencie jej uruchomienia. Jak sama nazwa metody wskazuje (przedrostek create), kade wywoanie odsya nowy
obiekt kontekstu. Jednak w dokumentacji znalazo si zapewnienie, e mechanizm ten zosta
zaprojektowany w taki sposb, aby jak najmniej obcia system.

414 Android 3. Tworzenie aplikacji


Interfejs ten jest dostpny bez wzgldu na to, czy korzystamy ze wspdzielonego interfejsu
uytkownika, czy nie. Jeeli stosujemy ten identyfikator, to bardzo dobrze. W przeciwnym wypadku w docelowej aplikacji musi si znale deklaracja zasobw jako dostpnych dla zewntrznych uytkownikw.
Interfejs createPackageContext() wykorzystuje jedn z trzech nastpujcych flag:
W przypadku flagi CONTEXT_INCLUDE_CODE Android pozwala zaadowa kod docelowej
aplikacji do biecego procesu. Kod ten bdzie wtedy dziaa jak nasz wasny. Dziaa
to jedynie wtedy, gdy obydwa pakiety bd posiaday wspln sygnatur oraz identyfikator
uytkownika. Jeeli identyfikatory uytkownika bd si rniy, wprowadzenie tej
flagi bdzie skutkowao wystpieniem wyjtku zabezpiecze.
Flaga CONTEXT_RESTRICTED oznacza, e wci istnieje moliwo uzyskania dostpu do
cieek zasobw i nie zachodzi skrajny przypadek, jakim jest danie wczytania kodu.
Dziki fladze CONTEXT_IGNORE_SECURITY certyfikaty s ignorowane i kod zostanie
wczytany, ale bdzie dziaa pod naszym identyfikatorem uytkownika. W dokumentacji
sugerowana jest wyjtkowa ostrono podczas korzystania z tej flagi.
Wiemy ju teraz, w jaki sposb pakiety, sygnatury i wspdzielone identyfikatory uytkownika
wsppracuj ze sob w procesie kontrolowania dostpu do elementw przechowywanych i tworzonych przez aplikacj.

Projekty bibliotek
Podczas omawiania koncepcji wspdzielenia kodu i zasobw nasuwa si jedno zasadnicze
pytanie: czy pomocna okae si idea projektu bibliotek? Aby si tego dowiedzie, najpierw musimy zrozumie, czym s te projekty, jak s tworzone oraz w jaki sposb s uywane.

Czym jest projekt bibliotek?


Poczwszy od wersji 0.9.7 wtyczki ADT, Android posiada funkcj obsugi projektw bibliotek
(ang. library projects). Projekt bibliotek stanowi zbir kodw Java oraz zasobw przypominajcych architektur zwyczajny projekt, ale nazwa pliku, w ktrym s zawarte, nigdy nie koczy
si rozszerzeniem .apk. Zamiast tego zawarto tego pliku moe zosta wczona do innego
projektu oraz skompilowana w gwnym pliku .apk aplikacji.

Twierdzenia dotyczce projektw bibliotek


Poniej prezentujemy pewne fakty zwizane z projektami bibliotek:
Projekt bibliotek moe posiada wasn nazw pakietu.
Projekt bibliotek nie zostanie skompilowany do osobnego pliku .apk, a jedynie moe
by wczony do pliku .apk projektu, ktry wykorzystuje dane zawarte w tym projekcie
bibliotek.
Projekt bibliotek moe korzysta z innych plikw JAR.
Nie mona przeksztaci samego projektu bibliotek w plik JAR.
Wtyczka ADT poczy projekt bibliotek z gwnym projektem i skompiluje je jako cz
gwnego projektu.

Rozdzia 12 Analiza pakietw

415

Zarwno projekt bibliotek, jak i gwny projekt mog uzyska dostp do zasobw
przechowywanych w tym pierwszym za pomoc odpowiednich plikw R.java.
Moemy posiada zduplikowane identyfikatory zasobw pomidzy projektem gwnym
a bibliotek. Identyfikatory zasobw w projekcie gwnym maj wyszy priorytet.
Jeeli chcemy rozrnia identyfikatory zasobw dwch projektw, moemy wprowadzi
odmienne przedrostki, na przykad lib_ dla zasobw projektu bibliotek.
Gwny projekt moe odnosi si do dowolnej liczby projektw bibliotek.
Moemy ustanowi pierwszestwo projektw bibliotek, aby si przekona, ktre
zasoby s waniejsze.
Takie skadniki projektu bibliotek jak aktywno naley zadeklarowa w pliku manifecie
gwnego projektu. Nazwa skadnika z pakietu bibliotek musi by cakowicie zgodna
z nazw pakietu bibliotek.
Nie ma potrzeby definiowania skadnikw w pliku manifecie projektu bibliotek,
chocia moe si to okaza przydatne do szybkiego rozpoznawania obsugiwanych
przez niego skadnikw.
Tworzenie projektu bibliotek rozpoczyna si od utworzenia standardowego projektu
Androida oraz ustawienia flagi Is Library w oknie waciwoci.
Moemy rwnie w oknie waciwoci projektu powiza gwny projekt z projektami
bibliotek.
Do wielu rnych gwnych projektw mona docza projekty bibliotek.
Funkcja projektw bibliotek zostaa wprowadzona w wersji 0.9.7 narzdzi ADT,
zestawie SDL w wersji 6 lub wyszej oraz od wersji 2.1 systemu Android.
W aktualnej wersji rodowiska jeden projekt bibliotek nie moe odnosi si do innego
projektu bibliotek, chocia w przyszych wersjach moe si pojawi taka moliwo.
Projekty bibliotek nie obsuguj plikw AIDL.
Projekt bibliotek nie obsuguje katalogu ze wspdzielonymi plikami dodatkowymi.

Przekonajmy si, do czego su projekty bibliotek, poprzez utworzenie jednego z nich oraz
projektu gwnego. Poniej prezentujemy cele naszego przykadowego projektu:
1. Utworzenie prostej aktywnoci w projekcie bibliotek.
2. Utworzenie menu dla aktywnoci z punktu 1. poprzez zdefiniowanie pewnych zasobw
menu.
3. Utworzenie w gwnym projekcie aktywnoci, ktra bdzie korzystaa z projektu
bibliotek.
4. Utworzenie aktywnoci w gwnym projekcie, wygenerowanym w punkcie 3.
5. Utworzenie menu dla gwnej aktywnoci z punktu 4.
6. Przywoanie aktywnoci projektu bibliotek za pomoc elementu menu gwnej aktywnoci.
Po utworzeniu obydwu projektw ujrzymy ekran gwnej aktywnoci (punkt 4.), zaprezentowany
na rysunku 12.2.
Po klikniciu elementu lib, widocznego w aktywnoci gwnego projektu, zostanie wywietlona
aktywno przedstawiona na rysunku 12.3, pochodzca z projektu bibliotek.

416 Android 3. Tworzenie aplikacji

Rysunek 12.2. Przykadowa aktywno projektu gwnego zawierajca elementy menu

Rysunek 12.3. Przykadowa aktywno projektu bibliotek

Rozdzia 12 Analiza pakietw

417

Menu widoczne w aktywnoci projektu bibliotek pochodz z zasobw tego projektu. Efektem
kliknicia poszczeglnych opcji menu jest wywietlony na ekranie komunikat o klikniciu danego elementu. Rozpocznijmy wiczenie od utworzenia projektu bibliotek.

Utworzenie projektu bibliotek


Nasz projekt bdzie si skada z nastpujcych plikw:
TestLibActivity.java (listing 12.3),
layout/lib_main.xml (listing 12.4),
menu/lib_main_menu.xml (listing 12.5),
AndroidManifest.xml (listing 12.6).
Te pliki, ukazane na poniszych listingach, powinny cakowicie wystarczy do utworzenia przykadowego projektu bibliotek.
Na kocu rozdziau zamieszczamy adres URL, pod ktrym s dostpne wszystkie
omawiane tu projekty. W ten sposb moemy je zaimportowa bezporednio do
rodowiska Eclipse.
Listing 12.3. Przykadowa aktywno projektu bibliotek plik TestLibActivity.java
package com.androidbook.library.testlibrary;

//...miejsce na podstawowe instrukcje importu


//po wciniciu kombinacji CTRL+SHIFT+O rodowisko Eclipse
//wygeneruje niezbdne instrukcje importu
public class TestLibActivity extends Activity
{
public static final String tag="TestLibActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.lib_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater(); //z aktywnoci
inflater.inflate(R.menu.lib_main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
appendMenuItemText(item);
if (item.getItemId() == R.id.menu_clear){
this.emptyText();
return true;
}
return true;
}
private TextView getTextView(){

418 Android 3. Tworzenie aplikacji


return (TextView)this.findViewById(R.id.text1);
}
public void appendText(String abc){
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + abc);
}
private void appendMenuItemText(MenuItem menuItem){
String title = menuItem.getTitle().toString();
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + title);
}
private void emptyText(){
TextView tv = getTextView();
tv.setText("");
}
}

Na listingu 12.4 widzimy plik ukadu graficznego obsugujcy powysz aktywno prosty
widok tekstowy stosowany do wywietlenia nazwy kliknitego elementu.
Listing 12.4. Przykadowy plik ukadu graficznego w projekcie bibliotek plik layout/lib_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Tutaj pojawi si informacje debugowania. "
/>
</LinearLayout>

Listing 12.5 prezentuje nam zawarto pliku opcji menu, ktre s widoczne w aktywnoci projektu bibliotek na rysunku 12.3.
Listing 12.5. Plik menu projektu bibliotek plik menu/lib_main_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa korzysta z domylnej kategorii. -->


<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/menu_clear"
android:title="wyczy" />
<item android:id="@+id/menu_testlib_1"
android:title="Bib Test Menu1" />
<item android:id="@+id/menu_testlib_2"
android:title="Bib Test Menu2" />
</group>
</menu>

Rozdzia 12 Analiza pakietw

419

Natomiast plik manifest projektu bibliotek zosta zaprezentowany na listingu 12.6.


Listing 12.6. Plik manifest projektu bibliotek AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.library.testlibrary"
android:versionCode="1"
android:versionName="1.0.0">
<uses-sdk android:minSdkVersion="3" />
<application android:icon="@drawable/icon"
android:label="Testowy projekt bibliotek">
<activity android:name=".TestLibActivity"
android:label="Testowa aktywno projektu bibliotek">
</activity>
</application>
</manifest>

Jak ju stwierdzilimy w punkcie, w ktrym zaprezentowalimy twierdzenia dotyczce projektw bibliotek, definicja aktywnoci w pliku manifecie projektu bibliotek pojawia si wycznie
w celach dokumentacyjnych oraz jest opcjonalna pod ktem mechanizmw dziaania kodu.
Po zapoznaniu si z plikami moemy utworzy standardowy projekt Androida. Po skonfigurowaniu projektu klikamy prawym przyciskiem myszy nazw projektu, a nastpnie menu
kontekstowe waciwoci, dziki czemu pojawi si okno dialogowe waciwoci, w ktrym moemy ustanowi projekt bibliotek. Omawiane okno dialogowe widzimy na rysunku 12.4 (w zalenoci od wersji zestawu SDK widoczne na rysunku wersje Androida mog by inne). Wystarczy zaznaczy opcj Is Library, aby przetworzy biecy projekt w projekt bibliotek.

Rysunek 12.4. Ustanawianie projektu bibliotek

W ten sposb zakoczylimy proces tworzenia projektu bibliotek. Dowiemy si teraz, w jaki sposb utworzy projekt aplikacji wykorzystujcy wygenerowany przed chwil projekt bibliotek.

420 Android 3. Tworzenie aplikacji

Tworzenie projektu testowego


wykorzystujcego projekt bibliotek
Do zbudowania aplikacji wykorzystamy podobny zestaw plikw, a nastpnie doczymy napisany powyej projekt bibliotek. Wygenerujemy teraz nastpujce pliki:
TestAppActivity.java (listing 12.7),
layout/main.xml (listing 12.8),
menu/main_menu.xml (listing 12.9),
AndroidManifest.xml (listing 12.10).
Na listingu 12.7 pokazalimy plik TestAppActivity.java.
Listing 12.7. Kod aktywnoci gwnego projektu plik TestAppActivity.java
package com.androidbook.library.testlibraryapp;
import com.androidbook.library.testlibrary.*;

//...inne instrukcje importu


public class TestAppActivity extends Activity
{
public static final String tag="TestAppActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater(); //z aktywnoci
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
appendMenuItemText(item);
if (item.getItemId() == R.id.menu_clear)
{
this.emptyText();
return true;
}
if (item.getItemId() == R.id.menu_library_activity){
this.invokeLibActivity(item.getItemId());
return true;
}
return true;
}
private void invokeLibActivity(int mid)
{
Intent intent = new Intent(this,TestLibActivity.class);
intent.putExtra("com.ai.menuid", mid);
startActivity(intent);
}

Rozdzia 12 Analiza pakietw

421

private TextView getTextView(){


return (TextView)this.findViewById(R.id.text1);
}
public void appendText(String abc){
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + abc);
}
private void appendMenuItemText(MenuItem menuItem){
String title = menuItem.getTitle().toString();
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + title);
}
private void emptyText(){
TextView tv = getTextView();
tv.setText("");
}
}

Zwrmy uwag, e po utworzeniu tego pliku moe si pojawi bd kompilacji zwizany z odniesieniem do aktywnoci umieszczonej w projekcie bibliotek. Nie pozbdziemy si go, dopki
nie przeczytamy dalszej czci rozdziau i nie odkryjemy, w jaki sposb zdefiniowa projekt bibliotek jako obiekt zaleny od gwnego projektu.
Kod pliku ukadu graficznego obsugujcego powysz aktywno jest widoczny na listingu 12.8.
Listing 12.8. Ukad graficzny gwnego projektu plik layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Tutaj pojawi si informacje debugowania"
/>
</LinearLayout>

Kod Java wystpujcy w aktywnoci gwnego projektu (listing 12.7) wykorzystuje element menu R.id.menu_library_activity do wywoania aktywnoci TestLibActivity. Poniej przedstawilimy fragment kodu z pliku Java (pokazanego wczeniej na listingu 12.7):
private void invokeLibActivity(int mid)
{
Intent intent = new Intent(this,TestLibActivity.class);

//Przekazuje identyfikator menu w postaci dodatkowej intencji


//na wypadek, gdyby wymagaa tego aktywno bibliotek.
intent.putExtra("com.androidbook.library.menuid", mid);
startActivity(intent);
}

422 Android 3. Tworzenie aplikacji


Zwrmy uwag, e klasa TestLibActivity.class jest wykorzystywana lokalnie, mimo e importowalimy klasy Java z pakietu bibliotek:
import com.androidbook.library.testlibrary.*;

Z kolei na listingu 12.9 widzimy kod menu.


Listing 12.9. Plik menu gwnego projektu menu/main_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa korzysta z domylnej kategorii. -->


<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/menu_clear"
android:title="wyczy" />
<item android:id="@+id/menu_library_activity"
android:title="invoke lib" />
</group>
</menu>

Do zakoczenia procesu tworzenia projektu potrzebujemy jeszcze pliku manifestu, zamieszczonego na listingu 12.10.
Listing 12.10. Plik manifest gwnego projektu AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.library.testlibraryapp"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="Aplikacja
testujca bibl.">
<activity android:name=".TestAppActivity"
android:label="Aplikacja testujca bibl.">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=
"com.androidbook.library.testlibrary.TestLibActivity"
android:label="Aktywno testujca bibl."/>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

Zwrmy uwag, w jaki sposb w tym pliku manifecie gwnej aplikacji zdefiniowano aktywno TestLibActivity, pochodzc z projektu bibliotek. Podczas definiowania aktywnoci
zastosowalimy rwnie pen nazw pakietu. Zauwamy te, e nazwy pakietu dla projektu
bibliotek mog si rni od tych zawartych w gwnej aplikacji.
Po zapenieniu projektu tymi plikami naley otworzy okno dialogowe waciwoci projektu
(rysunek 12.5), aby zaznaczy, e nasz gwny projekt zaley od utworzonego wczeniej projektu
bibliotek.

Rozdzia 12 Analiza pakietw

423

Rysunek 12.5. Deklarowanie zalenoci aplikacji od projektu bibliotek

Widzimy w tym oknie dialogowym przycisk Add. Moemy za jego pomoc doda odniesienie
do projektu bibliotek, pokazanego na rysunku 12.5. Inne czynnoci s niepotrzebne.
Po dodaniu projektu bibliotek ukazuje si on zazwyczaj w postaci dodatkowego wza drzewa
gwnej aplikacji (a take pozostaje jednoczenie osobnym projektem bibliotek). Ilustruje to
rysunek 12.6.

Rysunek 12.6. Doczony projekt bibliotek w widoku gwnego projektu

424 Android 3. Tworzenie aplikacji


Zwrmy uwag na wze z dopiskiem [Android Library] oraz na powielone (przekierowane)
pliki rdowe Java. Przyjrzyjmy si take strukturze tego wza. Jego nazwa powstaje poprzez
poczenie nazwy projektu bibliotek, podkrelnika oraz nazwy powizanego katalogu rdowego umieszczonego w projekcie bibliotek. Taki schemat nazewnictwa pozwala nam na umieszczanie dowolnej liczby wasnych katalogw rdowych w projekcie bibliotek. Jest to gwna
rnica pomidzy wersj 0.9.8 narzdzia ADT a nowszymi.
Jeeli zamierzamy w gwnym projekcie modyfikowa pliki rdowe nalece do projektu bibliotek, bd one zmieniane rwnie w samym projekcie bibliotek. Czasami nie wida tego wza.
W takim wypadku warto uruchomi ponownie rodowisko Eclipse. W kadym razie, jeli widzimy ten wze, oznacza to, e nasz projekt dziaa.
Wspomnijmy jeszcze, e Android traktuje pliki R.java w ciekawy sposb. Spjrzmy na rysunek 12.7.

Rysunek 12.7. Powielone zasoby w pliku R.java

Najpierw zostaje wygenerowany jeden plik R.java w projekcie bibliotek, odpowiedzialny za zasoby przechowywane w tym projekcie. Ponadto system tworzy rwnie plik R.java przechowujcy informacje o zasobach znajdujcych si w gwnym projekcie. Mona si byo tego spodziewa dwm projektom odpowiadaj dwa pliki R.java.
Jednak, co ciekawe, Android generuje identyfikatory zasobw mieszczcych si w projekcie bibliotek rwnie w pliku R.java gwnej aplikacji. Oznacza to, e programista moe stosowa
skadni R.id. zwizan z identyfikatorami znajdujcymi si w pliku R.java, ktry stanowi cz
gwnej aplikacji (nie zapominajmy, e plik R.java zostaje automatycznie wygenerowany,
wic wartoci z listingu 12.11, takie jak 0x7f02000, mog by zupenie inne).
Listing 12.11. Ponownie zdefiniowane identyfikatory wspdzielonych zasobw, przechowywane
w pliku R.java gwnego projektu
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int icon=0x7f020000;
public static final int robot=0x7f020001;
}
public static final class id {
public static final int menuGroup_Main=0x7f060001;

Rozdzia 12 Analiza pakietw

public
public
public
public
public

static
static
static
static
static

final
final
final
final
final

int
int
int
int
int

425

menu_clear=0x7f060002;
menu_library_activity=0x7f060005;
menu_testlib_1=0x7f060003;
menu_testlib_2=0x7f060004;
text1=0x7f060000;

}
public static final class layout {
public static final int lib_main=0x7f030000;
public static final int main=0x7f030001;
}
public static final class menu {
public static final int lib_main_menu=0x7f050000;
public static final int main_menu=0x7f050001;
}
public static final class string {
public static final int app_name=0x7f040001;
public static final int hello=0x7f040000;
}
}

Zauwamy, e w pliku R.java gwnej aplikacji zostay zdefiniowane rwnie zasoby rozpoczynajce si od przedrostka lib_. Oznacza to, e projekt bibliotek bdzie posiada swoje stae dla
zasobw lib_, a gwny projekt bdzie posiada inne stae dla tych samych zasobw.
Obydwa projekty mog si odnosi do tego samego zasobu za pomoc skadni

R.jakis

-identyfikator. Warto tej staej moe by identyczna, jednak identyfikator tego zasobu b-

dzie dostpny w obydwu przestrzeniach nazw Java: w przestrzeni nazw projektu bibliotek
oraz w przestrzeni nazw gwnej aplikacji.
Uwaajmy take na nazwy kontrolek menu: lib_main_menu oraz main_menu. Mogoby si to
okaza kopotliwe, gdyby w aplikacji znalazy si dwa menu wypenione rnymi elementami,
posiadajce jednak tak sam nazw zasobu. Reasumujc, zasoby zostaj zebrane i udostpnione
w jednym miejscu przeznaczonym dla gwnej aplikacji. Musimy by szczeglnie ostroni w przypadku zasobw zlokalizowanych na poziomie pliku, na przykad kontrolek menu lub ukadw
graficznych, a take identyfikatorw generowanych z tych obiektw dla wewntrznych elementw.
Skoro wiemy, czym s projekty bibliotek, czy potrafimy ju odpowiedzie na jakiekolwiek
wczeniej postawione pytanie na temat wspdzielonych danych?
Jak wida, projekty bibliotek s konstruktami czasu kompilacji. Jasne staje si, e wszelkie zasoby
nalece do tego projektu zostaj wchonite i przyczone do gwnego projektu. Nie moemy
zada pytania o wspdzielenie w czasie dziaania aplikacji, poniewa istnieje tylko jeden plik
pakietu zawierajcy nazw gwnego pakietu. Jedn z czsto wymienianych propozycji jest
moliwo utworzenia wersji darmowych i patnych aplikacji, wspdzielcych jeden projekt bibliotek.

Odnoniki
Dziki poniszym odnonikom Czytelnik atwiej zrozumie koncepcje zawarte w tym rozdziale:
http://developer.android.com/guide/publishing/app-signing.html bardzo przydatne
informacje na temat podpisywania plikw .apk.

426 Android 3. Tworzenie aplikacji

http://java.sun.com/j2se/1.3/docs/tooldocs/win32/keytool.html znakomita dokumentacja


dotyczca narzdzi keytool, jarsigner oraz samego procesu podpisywania.
http://www.androidbook.com/item/3493 notatki autorw, wcznie z modelem
pojciowym, wyjaniajcym znaczenie podpisywania plikw JAR.
http://www.androidbook.com/item/3279 na tej stronie zebralimy wszystkie dane
badawcze zwizane z pakietami Androida. Zawarte s tutaj informacje na temat
podpisywania plikw .apk, odnoniki do artykuw opisujcych proces wspdzielenia
danych pomidzy pakietami, dalsze wiadomoci na temat wspdzielonych
identyfikatorw uytkownika, a take instrukcje instalowania oraz
odinstalowywania pakietw.
http://developer.android.com/guide/developing/projects/projects-eclipse.html
znajdziemy tu midzy innymi informacje dotyczce projektw bibliotek.
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw
projektw do pobrania, opracowanych na podstawie informacji zawartych
w ksice. Projekty zwizane z tym rozdziaem zostay umieszczone w katalogu
ProAndroid3_R12_Biblioteki.

Podsumowanie
Rozdzia ten powicilimy zagadnieniom dotyczcym pracy z pakietami i procesami, wspdzielenia kodu i danych pomidzy pakietami, a take tworzenia projektw bibliotek Androida.
Dowiedzielimy si, e proces podpisywania odgrywa istotn rol w zabezpieczeniach, zwaszcza na etapie przydzielania uprawnie pakietom.
Niniejszy rozdzia stanowi wprowadzenie do nastpnego, w ktrym przeanalizujemy skadniki
przechowywane w procesie pakietu oraz (przede wszystkim) przebiegajce w jego gwnym wtku.
Dowiemy si, w jaki sposb mona optymalnie dostosowa gwny wtek za pomoc procedur
obsugi oraz wtkw podrzdnych, co pozwala na zapewnienie pynnego dziaania aplikacji.

R OZDZIA

13
Analiza procedur obsugi

W rozdziale 12. stwierdzilimy, e kady pakiet jest przetwarzany w osobnym procesie. Teraz zajmiemy si organizacj wtkw we wntrzu procesu. W ten sposb
odpowiemy sobie na pytanie, do czego s nam potrzebne procedury obsugi.
Wiksza cz kodu aplikacji w Androidzie jest przetwarzana w kontekcie takiego
skadnika, jak aktywno lub usuga. Zastanowimy si, w jaki sposb te skadniki
aplikacji oddziauj z wtkami. Przez wikszo czasu dziaania aplikacji uruchomiony jest tylko jeden wtek wewntrz procesu, znany jako wtek gwny. Wyjanimy skutki wspdzielenia takiego gwnego wtku przez rne skadniki. Przede
wszystkim prowadzi to do wywietlania komunikatw ANR (ang. Application Not
Responding aplikacja nie odpowiada; podkrelamy tylko, e A jest skrtem od
aplikacja, a nie od annoying, czyli irytujca). Pokaemy, w jaki sposb moemy
stosowa procedury obsugi, komunikaty oraz wtki do uniezalenienia si od gwnego wtku w przypadku koniecznoci uruchomienia operacji trwajcych duszy czas.
Rozpoczniemy ten rozdzia od przyjrzenia si skadnikom aplikacji oraz kontekstowi
wtku, w ktrym s one przetwarzane.

Skadniki Androida i wtkowanie


Po przeczytaniu wczeniejszych rozdziaw moglimy zdy ju wywnioskowa,
e proces w Androidzie zawiera cztery podstawowe elementy. S to klasy:
Activity (czyli aktywno),
Service (usuga),
ContentProvider (czsto zwana po prostu dostawc),
BroadcastReceiver (w skrcie odbiorca).
Wikszo kodu tworzonej aplikacji jest czci jednego z wymienionych skadnikw lub jest wywoywana przez jeden z nich. Kady komponent tego typu posiada
wasny znacznik XML w specyfikacji aplikacji, zdefiniowanej w pliku manifecie
projektu. Poniej przypominamy, jak wygldaj te wzy:
<application>
<activity/>

428 Android 3. Tworzenie aplikacji


<service/>
<receiver/>
<provider/>
</application>

Nie liczc pewnych wyjtkw, Android wykorzystuje ten sam wtek do przetwarzania (lub
przechowywania) kodu z tych skadnikw. Mamy tu do czynienia z gwnym wtkiem aplikacji.
Wywoanie tych skadnikw moe by synchroniczne, na przykad w przypadku dania danych
od dostawcy treci, lub opnione za pomoc kolejkowania wiadomoci, jak choby podczas
przywoywania funkcji za pomoc uruchomienia usugi.
Na rysunku 13.1 przedstawiono zwizki pomidzy wtkami a tymi czterema skadnikami. Celem
tego diagramu jest ukazanie, w jaki sposb wtki kr po strukturze Androida oraz jej skadnikach. Zagadnienia z tym zwizane omwilimy w kolejnych kilku podrozdziaach.

Rysunek 13.1. Struktura skadnikw i wtkowania w Androidzie

Aktywnoci dziaaj w gwnym wtku


Jak widzimy na rysunku 13.1, wszystkie operacje s wykonywane w gwnym wtku. Gwny
wtek oddziauje ze wszystkimi skadnikami. Naley doda, e dzieje si to poprzez kolejkowanie
wiadomoci. Na przykad podczas zaznaczania elementw menu lub przyciskw na ekranie urzdzenia przez uytkownika dziaania te zostan przeksztacone w komunikaty i przekazane do
gwnego wtku aktywnego procesu. Gwny wtek jest zaptlony i przetwarza kady nadesany
komunikat. Jeeli przetwarzanie jakiego komunikatu trwa duej ni 5 sekund, zostanie
wywietlony komunikat ANR.

Rozdzia 13 Analiza procedur obsugi

429

Odbiorcy komunikatw dziaaj w gwnym wtku


W podobny sposb, jeeli odpowiedzi na kliknicie elementu menu ma by przywoanie dostawcy komunikatw, Android umieszcza komunikat w gwnej kolejce procesu, z ktrego ma
zosta wywoany zarejestrowany odbiorca. Gwny wtek natrafi w pewnym momencie na ten
komunikat i wywoa odbiorc. Wtek ten moe rwnie obsuy odbiorc komunikatw. Jeeli
gwny wtek jest zajty odpowiadaniem na dziaanie zwizane z menu, odbiorca komunikatw
bdzie musia poczeka na zakoczenie tej czynnoci.

Usugi dziaaj w gwnym wtku


Taka sama zasada dotyczy usug. Jeli po klikniciu elementu menu nastpi uruchomienie usugi
lokalnej za pomoc metody startService, odpowiedni komunikat zostanie umieszczony
w gwnej kolejce, a gwny wtek przetworzy go za pomoc kodu usugi.

Dostawcy treci dziaaj w gwnym wtku


Wywoanie lokalnego dostawcy treci przebiega na nieco odmiennych zasadach. Dostawca treci
rwnie dziaa w obrbie gwnego wtku, ale wywoanie tego dostawcy jest synchroniczne
i nie zachodzi z wykorzystaniem kolejek wiadomoci.

Skutki posiadania pojedynczego gwnego wtku


By moe Czytelnik zastanawia si, dlaczego powicamy tyle uwagi temu, czy wikszo kodu
aplikacji dziaa w gwnym wtku, czy te nie. Odpowied na to pytanie brzmi: poniewa gwny
wtek ma powraca do kolejki wiadomoci w celu zapewnienia cigoci odpowiedzi na dziaania w interfejsie uytkownika. W konsekwencji nie naley przetrzymywa gwnego wtku.
Jeeli wiemy, e jaka czynno bdzie trwaa ponad pi sekund, powinnimy j umieci w osobnym wtku lub opni jej wykonanie, programujc powrt wtku gwnego do niej po wykonaniu innych operacji. Okazuje si jednak, e przeprowadzanie operacji w oddzielnym wtku
nie jest takie atwe, jak by si mogo na pocztku wydawa. Wrcimy do tego zagadnienia w dalszej
czci rozdziau, a take w nastpnym rozdziale, teraz jednak przyjrzyjmy si puli wtkw
przedstawionej na rysunku 13.1.

Pule wtkw, dostawcy treci, skadniki zewntrznych usug


Kiedy zewntrzne klienty lub skadniki spoza procesu daj danych od dostawcy treci, danie
to jest przydzielane do wtku z puli wtkw. To samo dotyczy zewntrznych klientw czcych si z usugami.

Narzdzia wtkowania poznaj swj wtek


Po tym do dugim omwieniu tematyki wtkw gwnych i roboczych warto byoby pokaza,
w jaki sposb wykorzysta ponisz klas, zaprezentowan na listingu 13.1, suc do
okrelania, ktry wtek obsuguje dany fragment kodu. Nastpnie, zagldajc do okna LogCat,
moemy porwna nasz wiedz teoretyczn z rzeczywistoci poprzez analizowanie wywietlanych identyfikatorw wtkw.

430 Android 3. Tworzenie aplikacji


Listing 13.1. Narzdzia wtkowania
//utils.java
public class Utils
{
public static long getThreadId() {
Thread t = Thread.currentThread();
return t.getId();
}
public static String getThreadSignature(){
Thread t = Thread.currentThread();
long l = t.getId();
String name = t.getName();
long p = t.getPriority();
String gname = t.getThreadGroup().getName();
return (name
+ ":(id)" + l
+ ":(priorytet)" + p
+ ":(grupa)" + gname);
}
public static void logThreadSignature(){
Log.d("ThreadUtils", getThreadSignature());
}
public static void sleepForInSecs(int secs){
try{
Thread.sleep(secs * 1000);
} catch(InterruptedException x){
throw new RuntimeException("przerwano",x);
}
}

//Ponisze dwie metody s uywane przez wtki robocze,


//ktre zostan omwione pniej.
public static Bundle getStringAsABundle(String message){
Bundle b = new Bundle();
b.putString("message", message);
return b;
}
public static String getStringFromABundle(Bundle b){
return b.getString("message");
}
}

Dziki wykorzystaniu metody logThreadSignature() dowiemy si, w ktrym wtku jest


wykonywany kod. Moemy rwnie sprawdzi, co si stanie po wstrzymaniu gwnego wtku
za pomoc metody sleep(), w wyniku czego przestanie on przetwarza kolejk komunikatw.
Wspomnielimy o moliwoci opnienia operacji w gwnym wtku, jeli zachodzi taka konieczno. Dokonujemy tego za pomoc procedur obsugi. S one powszechnie wykorzystywane w caym systemie Android po to, aby gwny wtek interfejsu uytkownika nie by wstrzymywany. Odgrywaj one rwnie istotn rol w komunikacji pomidzy gwnym wtkiem a innymi wtkami
roboczymi. W nastpnym podrozdziale przyjrzymy si budowie i funkcjom procedur obsugi.

Rozdzia 13 Analiza procedur obsugi

431

Procedury obsugi
Procedura obsugi (ang. handler) jest mechanizmem sucym do umieszczania komunikatu
w gwnej kolejce (dokadniej mwic, w kolejce doczonej do wtku, dla ktrego ta procedura
zostaa utworzona), dziki czemu komunikat ten zostanie pniej przetworzony przez gwny
wtek. Taki komunikat zawiera wewntrzne odniesienie wskazujce na procedur obsugi, ktra
go umiecia.
Gdy gwny wtek przechodzi do przetwarzania tego komunikatu, przywouje odpowiedzialn
za jego umieszczenie procedur obsugi za pomoc metody zwrotnej na obiekcie tej procedury.
Metoda ta nosi nazw handleMessage. Na rysunku 13.2 zostay ukazane powizania pomidzy
procedurami usug, komunikatami i gwnym wtkiem.

Rysunek 13.2. Zwizek pomidzy procedur obsugi, komunikatem i kolejk komunikatw

Podczas omawiania procedur obsugi przydatny okae si schemat pokazany na rysunku 13.2,
na ktrym przedstawiono podstawowe wsppracujce ze sob elementy. Tymi elementami s:
gwny wtek,
kolejka gwnego wtku,
procedura obsugi,
komunikat.

432 Android 3. Tworzenie aplikacji


Spord tych czterech elementw nie posiadamy bezporedniego dostpu do wtku ani do kolejki.
Moemy bezporednio oddziaywa przede wszystkim na obiekty typu Handler i Message. Spord tych dwch elementw to wanie ten pierwszy koordynuje wikszo dziaa.
Pomimo znaczenia obiektu Handler w tym oddziaywaniu, powinnimy pamita, e chocia
procedura obsugi pozwala nam na umieszczenie komunikatu w kolejce, to w rzeczywistoci
wanie obiekt Message przechowuje odwoanie do tej procedury. Obiekt ten przechowuje
rwnie struktur danych przekazywanych z powrotem do procedury obsugi. Na rysunku 13.2
powizanie to jest symbolizowane za pomoc odniesienia do obiektu Dane.
Z powodu tego pozornie odwrconego powizania pomidzy procedur obsugi a komunikatem, a take dlatego, e gwny wtek i kolejka s ukryte przed wzrokiem programisty, najatwiej nam bdzie zrozumie pojcie procedury obsugi na przykadzie.
Przykad ten bdzie reprezentowany przez element menu aktywujcy funkcj, ktra z kolei bdzie wykonywaa jak operacj piciokrotnie, co sekund, i za kadym razem bdzie si zgaszaa do wywoujcej j aktywnoci.

Skutki przetrzymywania gwnego wtku


Jeli nie mamy nic przeciwko wstrzymywaniu gwnego wtku, moglibymy zaprojektowa
wczeniej przedstawiony scenariusz za pomoc pseudokodu widocznego na listingu 13.2.
Listing 13.2. Wstrzymywanie gwnego wtku za pomoc metody sleep()
public class JakasAktywnosc
{
....inne metody
void respondToMenuItem()
{

//Dowd na to, e mamy do czynienia z gwnym wtkiem


Utils.logThreadSignature();
for (int i=0;i<5;i++)
{
sleepFor(1000);// gwny wtek zostaje wstrzymany na 1 sekund
robcos();
JakisWidokTekstowy.setText("co zrobia");
}
}
}

Taka konstrukcja speni nasze wymogi w omawianym przypadku. Jednak w rzeczywistoci w ten
sposb wstrzymujemy gwny wtek i gwarantujemy sobie wystpienie komunikatu ANR.

Zastosowanie procedury obsugi


do opnienia operacji w wtku gwnym
Aby unikn wywietlenia komunikatu ANR, nieuniknionego w efekcie wykonania czynnoci
pokazanych w poprzednim przykadzie, moemy wykorzysta procedur obsugi. Odpowiedzialny za to pseudokod jest widoczny na listingu 13.3.

Rozdzia 13 Analiza procedur obsugi

433

Listing 13.3. Utworzenie procedury obsugi z poziomu gwnego wtku


void respondToMenuItem()
{
JakasProceduraObslugiPochodzacaOdInnejProceduryObslugi myHandler =
new JakasProceduraObslugiPochodzacaOdInnejProceduryObslugi ();
myHandler.wykonujOpoznionaPrace(); //przywouje funkcj w jednosekundowych odstpach

Metoda respondToMenuItem() umoliwia gwnego wtkowi powrt do wykonywania ptli.


Utworzona procedura obsugi zostaa przywoana w gwnym wtku i podcza si do kolejki.
Metoda wykonujOpoznionaPrace() zapewni kontrol zaplanowanych zada, dziki czemu
gwny wtek w dogodnym momencie powrci do tej operacji. Teraz warto si zastanowi,
w jaki sposb ta funkcja dziaa. Poniej przedstawiamy etapy jej implementowania:
1. Utwrz obiekt komunikatu, ktry zostanie umieszczony w kolejce.
2. Wylij ten obiekt do kolejki w taki sposb, aby mg co sekund wywoywa metod
zwrotn.
3. Odpowiedz z poziomu gwnego wtku zwrotnym wywoaniem metody
handleMessage().
Aby zrozumie cay ten protok, przyjrzyjmy si kodowi rdowemu rzeczywistej procedury
obsugi. Zosta on umieszczony na listingu 13.4 i nosi nazw DeferWorkHandler.
W pseudokodzie z listingu 13.3 procedura obsugi JakasProceduraObslugiPochodzacaOd
jest odpowiednikiem procedury DeferWorkHandler. W kodzie z listingu 13.4 zaimplementowano take metod analogiczn do wykonujOpoznionaPrace().
InnejProceduryObslugi

Przykadowy kod rdowy procedury


obsugi opniajcej przeprowadzanie operacji
Zanim omwimy etapy wyszczeglnione w poprzednim podrozdziale, przyjrzymy si najpierw
kodowi procedury DeferWorkHandler na listingu 13.4. Pamitajmy, e kod rdowy gwnej
aktywnoci wywoujcej t procedur obsugi zosta umieszczony w dalszej czci rozdziau.
Na listingu 13.4 ta nadrzdna aktywno jest symbolizowana przez zmienn parentActivity.
Zmienna ta nie jest niezbdna do zrozumienia dziaania kodu i suy przede wszystkim do informowania nas o stanie pracy wykonywanej w procedurze obsugi.
Listing 13.4. Kod rdowy procedury DeferWorkHandler
public class DeferWorkHandler extends Handler
{
public static final String tag = "DeferWorkHandler";

//Zlicza wysane wiadomoci


private int count = 0;

//Nadrzdna aktywno, ktr moemy wykorzysta


//do informowania nas o stanie.
private TestHandlersDriverActivity parentActivity = null;

434 Android 3. Tworzenie aplikacji


//W trakcie konstruowania zagldamy do
//nadrzdnej aktywnoci.
public DeferWorkHandler(TestHandlersDriverActivity inParentActivity){
parentActivity = inParentActivity;
}
@Override
public void handleMessage(Message msg)
{
String pm = new String(
"komunikat wywoany:" + count + ":" +
msg.getData().getString("message"));
Log.d(tag,pm);
this.printMessage(pm);
if (count > 5)
{
return;
}
count++;
sendTestMessage(1);
}
public void sendTestMessage(long interval)
{
Message m = this.obtainMessage();
prepareMessage(m);
this.sendMessageDelayed(m, interval * 1000);
}
public void doDeferredWork()
{
count = 0;
sendTestMessage(1);
}
public void prepareMessage(Message m)
{
Bundle b = new Bundle();
b.putString("message", "Witaj, wiecie!");
m.setData(b);
return ;
}

//Ta metoda wywietla jedynie komunikat


//w polu tekstowym nadrzdnej aktywnoci.
//Metoda ta zostaa umieszczona na listingu 13.9
private void printMessage(String xyz)
{
parentActivity.appendText(xyz);
}
}

Przyjrzymy si podstawowym aspektom powyszego kodu rdowego.

Rozdzia 13 Analiza procedur obsugi

435

Konstruowanie odpowiedniego obiektu Message


Jak ju wspominalimy wczeniej, po utworzeniu procedury DeferWorkHandler potrafi ona samoistnie podczy si do gwnego wtku, poniewa odziedziczya tak waciwo z podstawowej klasy Handler. Podstawowa procedura obsugi zawiera zestaw metod sucych do wysyania do kolejki komunikatw, ktre posiadaj zdolno do pniejszej odpowiedzi.
Dwoma przykadami takich metod s sendMessage() i sendMessageDelayed(). Wykorzystywana w naszym przykadzie metoda sendMessageDelayed() pozwala nam na umieszczenie
komunikatu w gwnej kolejce przy z gry zaoonym opnieniu.
Podczas wywoywania metod sendMessage() lub sendMessageDelayed() potrzebne jest wystpienie obiektu Message. Najlepiej uzyska ten obiekt z procedury dostpu, poniewa po jego
odesaniu procedura ta zostaje ukryta w komunikacie. W ten sposb gwny wtek, dziki samej
zawartoci wiadomoci, uzyska informacj, ktra procedura obsugi ma zosta wywoana.
Na listingu 13.4 uzyskujemy komunikat za pomoc nastpujcego wiersza:
Message m = this.obtainMessage();

Zmienna this odnosi si do instancji obiektu Handler. Jak sama nazwa wskazuje, metoda ta
nie powoduje utworzenia nowego komunikatu, lecz pobiera go z globalnej puli komunikatw.
Nieco pniej, ju po przetwarzaniu komunikatu, bdzie on ponownie wykorzystywany. Na listingu 13.5 zaprezentowalimy rne odmiany metody obtainMessage().
Listing 13.5. Tworzenie komunikatu za pomoc procedury obsugi
obtainMessage();
obtainMessage(int
obtainMessage(int
obtainMessage(int
obtainMessage(int

what);
what, Object object);
what, int arg1, int arg2)
what, int arg1, int arg2, Object obj);

Kada odmiana tej metody ustanawia odpowiednie pola w obiekcie komunikatu. Istniej pewne
ograniczenia zwizane z argumentem Object object, czce si z przekraczaniem granicy
procesu przez komunikat. W takich przypadkach musi to by obiekt typu parcelable. O wiele
bezpieczniej i wygodniej jest stosowa jawnie metod setData() wobec obiektu komunikatu,
do czego wymagany jest typ bundle. Na listingu 13.4 zastosowalimy wanie metod setData().
Zachcamy rwnie do korzystania z argumentw arg1 oraz arg2, jeeli chcemy przekazywa
proste wskaniki, ktre mog by dostosowane do wartoci typu int.
Argument what pozwala na usunicie komunikatu z kolejki lub wysanie zapytania o obecno
komunikatw tego typu w kolejce. Wicej szczegw poznamy po zapoznaniu si z operacjami
klasy Handler. W podrozdziale Odnoniki umieszczonym na kocu tego rozdziau znajdziemy adres URL dokumentacji klasy Handler.

Wysyanie obiektw Message do kolejki


Gdy ju uzyskamy komunikat od procedury obsugi, moemy opcjonalnie zmodyfikowa tre
danych znajdujcych si w tym komunikacie. W naszym przykadzie wykorzystalimy funkcj
setData(), przekazujc jej obiekt bundle. Po sklasyfikowaniu lub zidentyfikowaniu danych zawartych w komunikacie moemy go przesa do kolejki za pomoc metod sendMessage() lub
sendMessageDelayed(). Po wywoaniu tych metod gwny wtek powrci do obsugi kolejki.

436 Android 3. Tworzenie aplikacji

Odpowied na metod zwrotn handleMessage


Klasa DeferWorkHandler wywodzi si od klasy Handler. Po dostarczeniu komunikatw do
kolejki procedura obsugi siada i czeka (mwic w przenoni) na ich odczytanie przez gwny wtek i wywoanie metody handleMessage() tej procedury.
Jeeli chcemy lepiej przyjrze si oddziaywaniu wtku gwnego z procedur obsugi, moemy
zaprogramowa wywietlanie si wiadomoci dziennika LogCat w momencie wysyania komunikatu oraz w chwili wywoywania zwrotnego metody handleMessage(). Znaczniki czasowe
bd si nieco rniy, poniewa gwny wtek potrzebuje kilku dodatkowych milisekund na
powrt do metody handleMessage().
Jest to rwnie dobry sposb na sprawdzenie, czy obydwie metody sendMessage() oraz
handleMessage() s uruchomione w gwnym wtku. Moemy tego dokona za pomoc
metody Utils.logThreadSignature() (listing 13.1).
W naszym przykadzie kada metoda handleMessage() po przetworzeniu jednego komunikatu
wysya kolejny komunikat do kolejki, dziki czemu moe zosta ponownie wywoana. Czynno
ta jest wykonywana piciokrotnie, a nastpnie proces wysyania komunikatw do kolejki zostaje
przerwany.
Jak ju wczeniej wspominalimy, procedura DeferWorkHandler rwnie pobiera nadrzdn
aktywno jako dane wejciowe, dziki czemu moe zwraca wszelkie informacje za pomoc
metod dostarczanych przez t aktywno.

Stosowanie wtkw roboczych


Kiedy korzystamy z procedury obsugi, takiej jak omwiona w poprzednim podrozdziale, kod
jest cigle przetwarzany w gwnym wtku. Kade wywoanie metody handleMessage() musi
mieci si w zastrzeonym czasie gwnego wtku (inaczej mwic, kade wywoanie komunikatu powinno zosta wykonane w cigu maksymalnie piciu sekund, aby wiadomo ANR
nie zostaa wywietlona). Jeeli naszym zadaniem jest wyduenie tego czasu przeznaczonego
na wykonanie operacji, bdziemy musieli rozpocz oddzielny wtek, utrzymywa go do czasu
zakoczenia czynnoci oraz pozwoli takiemu pobocznemu wtkowi na zgoszenie si do gwnej aktywnoci, uruchomionej w gwnym wtku. Taki rodzaj wtku jest czsto nazywany
wtkiem roboczym.
Rozpoczcie osobnego wtku podczas odpowiadania na nacinicie elementu menu nie jest wielkim wyzwaniem. Odrobiny wysiku wymaga jednak umoliwienie wtkowi roboczemu wstawienia komunikatu w kolejce wtku gwnego. Komunikat ten miaby na celu wskazanie, e
co si dzieje oraz e wtek gwny powinien to obsuy, gdy tylko napotka t wiadomo.
Rozsdne rozwizanie, polegajce na wykorzystaniu wtku roboczego, moe wyglda nastpujco:
1. Utwrz procedur obsugi w gwnym wtku podczas odpowiadania na element
menu. Trzymaj j pod rk. W przeciwiestwie do sytuacji z poprzedniego podrozdziau,
tutaj nie bdziemy jej uywa do wysyania komunikatw opniajcych dziaanie.
2. Utwrz osobny wtek (wtek roboczy), ktry bdzie wykonywa dan prac. Przeka
procedur obsugi z punktu 1. do tego wtku.
3. Wtek roboczy moe teraz przetwarza operacje trwajce duej ni 5 sekund,
a w midzyczasie moe wysya wiadomoci o stanie, umoliwiajce komunikacj
z wtkiem gwnym.

Rozdzia 13 Analiza procedur obsugi

437

4. Komunikaty o stanie s teraz przetwarzane przez gwny wtek, poniewa procedura


obsugi naleaa do gwnego wtku. Gwny wtek moe przetwarza te komunikaty,
podczas gdy wtek roboczy wykonuje dalej swoj prac.
Zajmiemy si teraz przykadowym kodem obsugi elementu menu, ktry uruchamia proces dla
wtku roboczego.

Przywoywanie wtku roboczego z poziomu menu


Kod na listingu 13.6 przedstawia funkcj nazwan testThread(), ktra moe zosta przywoana w odpowiedzi na nacinicie elementu menu w wtku gwnym.
Listing 13.6. Tworzenie wtku pobocznego z poziomu wtku gwnego
//Przechowujemy kilka zmiennych lokalnych,
//dziki czemu nie zostan one odtworzone za kadym razem, gdy
//menu zostanie kliknite w aktywnoci.
//Przechowuje wskanik do procedury obsugi.
Handler statusBackHandler = null;

//Wystpienie wtku.
Thread workerThread = null;

//Ta metoda zostanie przywoana przez menu.


private void testThread()
{
if (statusBackHandler == null)
{

//Element menu nie by wczeniej kliknity.


//Widoczne tu klasy zostan omwione w dalszej czci rozdziau.
statusBackHandler = new ReportStatusHandler(this);
workerThread = new Thread(new WorkerThreadRunnable(statusBackHandler));
workerThread.start();
return;
}

//Wtek ju tu jest.
if (workerThread.getState() != Thread.State.TERMINATED)
{
Log.d(tag, "watek jest nowy albo juz istniejacy, ale niezakonczony");
}
else
{
Log.d(tag, "watek jest prawdopodobnie zakonczony. uruchamianie");

//Musimy utworzy nowy wtek.


//Nie mona odtworzy zakoczonego wtku.
workerThread = new Thread(new WorkerThreadRunnable(statusBackHandler));
workerThread.start();
}
}

438 Android 3. Tworzenie aplikacji


Powyszy kod wydaje si nieco zoony, jednak jego sedno mieci si w nastpujcych wierszach:
statusBackHandler = new ReportStatusHandler(this);
workerThread = new Thread(new WorkerThreadRunnable(statusBackHandler));
workerThread.start();

Zasadniczo utworzylimy procedur obsugi (odpowiedzialn za przesyanie raportw o stanie),


przekazalimy j do wtku roboczego i uruchomilimy ten wtek. Dodatkowy kod widoczny na
listingu 13.6 jest po to, aby w przypadku dwukrotnego lub trzykrotnego kliknicia elementu
menu podczas dziaania wtku nie zosta utworzony nowy wtek ani procedura obsugi.

Komunikacja pomidzy wtkami gwnym i roboczym


Omwimy teraz klasy ReportStatusHandler i WorkerThreadRunnable. Nie prezentowalimy
ich wczeniej, poniewa chcielimy przedstawi zasad dziaania procedur obsugi oraz wtkw,
poczwszy od oglnego objanienia wymaga, a nastpnie przej do szczegowego omwienia
kadego z wykorzystywanych poj.

Implementacja klasy WorkerThreadRunnable


Zobaczmy teraz, jak dziaa wtek roboczy w klasie WorkerThreadRunnable. Kod rdowy tej
klasy zosta zamieszczony na listingu 13.7. Wystarczy szybkie spojrzenie na ten kod, zwaszcza
na zawarte w nim komentarze, aby mniej wicej zrozumie, do czego moe suy. Poniej
wyjaniamy rwnie podstawowe koncepcje dotyczce tego kodu.
Listing 13.7. Implementacja wtku roboczego
//Podstawowe zadania
//1. Wykonywanie operacji
//2. Informowanie nadrzdnej aktywnoci
public class WorkerThreadRunnable implements Runnable
{

//Procedura obsugi suca do komunikowania si z gwnym wtkiem


//Ustanawiana w konstruktorze
Handler statusBackMainThreadHandler = null;
public WorkerThreadRunnable(Handler h)
{
statusBackMainThreadHandler = h;
}

//Standardowy znacznik debugowania


public static String tag = "WorkerThreadRunnable";
public void run()
{
Log.d(tag,"rozpoczecie wykonywania");

//Sprawdza, ktry wtek jest uruchomiony w kodzie


//Ponisza metoda pochodzi z listingu 13.1
//Wywietla identyfikator i nazw wtku
Utils.logThreadSignature();

//Informuje wtek nadrzdny, e wtek roboczy


//rozpocz dziaanie

Rozdzia 13 Analiza procedur obsugi

439

informStart();
for(int i=1;i <= 5;i++)
{

//W rzeczywistej aplikacji byaby tu wykonywana praca


//zamiast wstrzymywania
Utils.sleepForInSecs(1);

//Informuje o postpach pracy


informMiddle(i);
}
informFinish();
}
public void informMiddle(int count)
{
Message m = this.statusBackMainThreadHandler.obtainMessage();
m.setData(Utils.getStringAsABundle("zrobiono:" + count));
this.statusBackMainThreadHandler.sendMessage(m);
}
public void informStart()
{
Message m = this.statusBackMainThreadHandler.obtainMessage();
m.setData(Utils.getStringAsABundle("Przebieg rozpoczynajcy"));
this.mainThreadHandler.sendMessage(m);
}
public void informFinish()
{
Message m = this.statusBackMainThreadHandler.obtainMessage();
m.setData(Utils.getStringAsABundle("Przebieg koczcy"));
this.statusBackMainThreadHandler.sendMessage(m);
}
}

Na listingu 13.7 widzimy dwie istotne rzeczy. W metodzie run() wstrzymujemy dziaanie wtku
na 1 sekund i wywoujemy metody informujce gwny wtek o stanie postpw wtku roboczego: czy jest na pocztku, w rodku, czy pod koniec procesu przetwarzania.
Doczylimy rwnie wywoanie metody
zidentyfikowanie wtku.

Utils.logThreadSignature(),

pozwalajcej na

Jednak w standardowej aplikacji zamiast metody sleep() powyszy kod wywoywaby jak
przydatn funkcj, aby dziaaa, dopki bdzie potrzebna. Moemy uzna t metod za symulowanie jakiej czci operacji, ktra zajmuje dokadnie tyle samo sekund.

Implementacja klasy ReportStatusHandler


Wszystkie metody informacyjne z listingu 13.7 generuj odpowiednie komunikaty i wysyaj
je do gwnego wtku za pomoc widocznej na listingu 13.8 klasy ReportStatusHandler.
Listing 13.8. Wysyanie informacji o stanie do gwnego wtku
public class ReportStatusHandler extends Handler
{
public static final String tag = "ReportStatusHandler";

440 Android 3. Tworzenie aplikacji


//Zapamituje nadrzdn aktywno, dziki czemu
//moemy j informowa o postpach.
private TestHandlersDriverActivity
parentTestHandlersDriverActivity = null;
public ReportStatusHandler(
TestHandlersDriverActivity inParentActivity){
parentTestHandlersDriverActivity = inParentActivity;
}
@Override
public void handleMessage(Message msg)
{

//Pobiera cigi znakw z komunikatu.


String pm = Utils.getStringFromABundle(msg.getData());
Log.d(tag,pm);

//Powiadamia nadrzdn aktywno, e co si stao.


this.printMessage(pm);

//Potwierdza, e jest uruchomione w gwnym wtku.


Utils.logThreadSignature();
}
private void printMessage(String xyz){
parentTestHandlersDriverActivity.appendText(xyz);
}
}

Kod zawarty w tej klasie jest oczywisty. Kiedy procedura obsugi otrzymuje metod handleMessage(), informuje nadrzdn aktywno, e wtek roboczy przesa komunikat o stanie
za pomoc metody appendText(). Z kolei nadrzdna aktywno po otrzymaniu komunikatu
wykonuje odpowiedni operacj. W naszym przykadzie zostaje jedynie wywietlona wiadomo na ekranie aktywnoci.
Do tej pory, posugujc si odpowiednimi przykadami, przedstawilimy do istotne zagadnienia:
Za pomoc procedury DeferWorkHandler pokazalimy, e gwny wtek moe
okrela moment przetworzenia komunikatu (czy komunikatw), moe te opni
jego (ich) przetworzenie. Ta sama technika moe by wykorzystywana do powtarzania
tej samej czynnoci bez koniecznoci korzystania z czasomierza lub menedera alarmw.
Za pomoc metod ReportStatusHandler i WorkerThread udowodnilimy, e moemy
rozpocz osobny wtek roboczy i umoliwi mu komunikacj z interfejsem uytkownika
za pomoc procedury obsugi.

Szybki przegld jak dziaa wtek?


Skoro w odpowiedzi na kliknicie elementu menu rozpoczlimy wtek, naturaln konsekwencj staje si konieczno jego zakoczenia. Wtek zostaje automatycznie zatrzymany, gdy metoda
run() zakoczy dziaanie. W rzeczywistoci zaleca si, eby nie zatrzymywa z zewntrz dziaajcego wtku, poniewa w ten sposb moemy przerwa operacj w trakcie przetwarzania.
Dobrym rozwizaniem jest ustanowienie flagi, dziki czemu wtek j rozpozna i elegancko
opuci metod run().

Rozdzia 13 Analiza procedur obsugi

441

Warto rwnie zwrci uwag na rne stany wtku, aby dobrze zrozumie jego zachowanie.
Wtek moe znajdowa si w jednym z nastpujcych stanw:
New thread zosta utworzony (alive=false);
Runnable zosta uruchomiony (alive=true);
Not runnable wstrzymany, zawieszony, oczekujcy, wywoany lub zablokowany
na wejciu-wyjciu (alive=true);
Dead gdy zostaje wywoana metoda stop() lub nastpi wyjcie z metody run()
(alive=false).
Metoda isAlive() w wtku informuje nas, czy wtek zosta uruchomiony, ale nie zatrzymany.
Oznacza to, e wtek moe znajdowa si w stanie runnable lub not-runnable. Jeeli zostanie
zwrcona warto false, to mamy do czynienia z nowym albo z zakoczonym wtkiem.
W trakcie pracy z wtkami powinnimy pamita o ich stanach.

Klasy przykadowego sterownika procedury obsugi


Do tej pory zaprezentowalimy kod rdowy nastpujcych klas:
DeferWorkHandler.java posiada moliwo opniania przetwarzania funkcji
(listing 13.4),
ReportStatusHandler.java jest to nonik komunikacyjny dla wtku roboczego
(listing 13.8),
WorkerThreadRunnable.java implementacja wtku roboczego (listing 13.7),
Utils.java umieszczono tu kilka narzdzi wtkowania (listing 13.1).
Nadszed czas, aby zaprezentowa peny kod rdowy aktywnoci sterujcej, odpowiadajcej
na kliknicie elementu menu i przywoujcej omawiane funkcje. Zaprezentujemy rwnie kod
rdowy zasobw menu i plikw manifestw, dziki czemu Czytelnik bdzie mia wszystkie
klasy wymagane do utworzenia projektu i przetestowania omawianych koncepcji.
Warto zauway, e w listingach brakuje nazwy pakietw lub instrukcji importowania. Te drugie
atwo odtworzy za pomoc rodowiska Eclipse. Gdy plik rdowy jest otwarty, wystarczy uy
skrtu klawiaturowego Ctrl+Shift+O, aby Eclipse wstawio wymagane instrukcje.
Jeli chodzi o nazw pakietu, moemy zajrze do pliku manifestu i dowiedzie si, jak ona wyglda dla naszego przykadu. Bdziemy musieli umieci t nazw na samym szczycie hierarchii
plikw rdowych Java. Poniewa wszystkie omawiane pliki powinny si znajdowa w obrbie
tego samego pakietu, moemy rwnie dostosowa jego nazw do swoich potrzeb, a nastpnie
wstawi j do pliku manifestu.
Mona rwnie pobra gotowy projekt, do ktrego adres zosta umieszczony
w podrozdziale Odnoniki na kocu rozdziau. Nazwa pliku, w ktrym jest
zawarty projekt, to ProAndroid3_R13_ProceduryObsugi.zip. Aby utworzy projekt,
naley wypakowa zawarto tego pliku oraz zaimportowa j do rodowiska ADT.

442 Android 3. Tworzenie aplikacji


Poniej przedstawiamy z kolei list dodatkowych plikw, ktre bd wymagane do skompilowania
projektu:
TestHandlersDriverActivity.java gwna aktywno sterujca (listing 13.9),
layout/main.xml plik ukadu graficznego dla klasy TestHandlersDriverActivity
(listing 13.10),
res/menu/main_menu.xml menu suce do przywoania procedur obsugi
(listing 13.11),
AndroidManifest.xml standardowy plik manifest (listing 13.12).
W nastpnych podrozdziaach zaprezentujemy te pliki jeden po drugim.

Plik aktywnoci sterujcej


Poniej prezentujemy pierwszy z wymienionych plikw TestHandlersDriverActivity.java.
Mamy tu do czynienia z prost aktywnoci zawierajc widok tekstowy. W widoku tym bd
umieszczone elementy menu. Za pomoc jednego elementu menu bdziemy testowa opnion procedur obsugi, a za pomoc drugiego wtek roboczy. W umieszczonym tu polu
tekstowym bd wywietlane komunikaty pochodzce z wtku roboczego.
Klasa ta zawiera rwnie metody cyklu ycia aktywnoci a do jej zakoczenia. Chcemy w ten
sposb sprawdzi zachowanie gwnego wtku i kolejki w odniesieniu do cyklu ycia aktywnoci.
Kod tej aktywnoci znajdziemy na listingu 13.9.
Listing 13.9. Aktywno suca do testowania procedur obsugi oraz wtkw roboczych
public class TestHandlersDriverActivity extends Activity
{
public static final String tag="TestHandlersDriverActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater(); //z aktywnoci
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
appendMenuItemText(item);
if (item.getItemId() == R.id.menu_clear)
{
this.emptyText();
return true;
}
if (item.getItemId() == R.id.menu_test_thread)
{
this.testThread();

Rozdzia 13 Analiza procedur obsugi

443

return true;
}
if (item.getItemId() == R.id.menu_test_defered_handler)
{
this.testDeferedHandler();
return true;
}
return true;
}
private TextView getTextView(){
return (TextView)this.findViewById(R.id.text1);
}
public void appendText(String abc){
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + abc);
}
private void appendMenuItemText(MenuItem menuItem){
String title = menuItem.getTitle().toString();
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + title);
}
private void emptyText(){
TextView tv = getTextView();
tv.setText("");
}
private DeferWorkHandler th = null;
private void testDeferedHandler()
{
if (th == null)
{
th = new DeferWorkHandler(this);
this.appendText("Tworzenie nowej procedury obsugi");
}
this.appendText(
"Rozpoczcie wykonywania opnionej pracy poprzez wysyanie komunikatw");
th.doDeferredWork();
}
Handler statusBackHandler = null;
Thread workerThread = null;
private void testThread()
{
if (statusBackHandler == null)
{
statusBackHandler = new ReportStatusHandler(this);
workerThread =
new Thread(
new WorkerThreadRunnable(statusBackHandler));
}
if (workerThread.getState() != Thread.State.TERMINATED)
{
Log.d(tag, "watek jest nowy lub istniejacy, ale niezakonczony");
}
else
{

444 Android 3. Tworzenie aplikacji


Log.d(tag, "watek jest prawdopodobnie zakonczony. uruchamianie");

//Musimy utworzy nowy wtek.


//Nie mona uruchomi zakoczonego wtku.
workerThread =
new Thread(
new WorkerThreadRunnable(statusBackHandler));
workerThread.start();
}
}

//Ponisze metody cyklu ycia zostay doczone w celu obserwacji zachowania


//opnionych komunikatw oraz natury wtku roboczego w trakcie
//przechodzenia aktywnoci przez rne etapy cyklu ycia.
@Override
protected void onPause() {
Log.d(tag,"onpause. Moge byc czesciowo lub calkowicie niewidoczna");
this.appendText("onpause");
super.onPause();
}
@Override
protected void onStop() {
Log.d(tag,"onstop. Jestem w pelni niewidoczna");
this.appendText("onstop");
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(tag,"ondestroy. Chwile przed usunieciem");
super.onDestroy();
}
@Override
protected void onRestart() {
Log.d(tag,"onRestart. Sa tu kontrolki interfejsu uzytkownika");
super.onRestart();
}
@Override
protected void onStart() {
Log.d(tag,"onStart. Interfejs uzytkownika moze byc czesciowo niewidoczny");
super.onStart();
}
@Override
protected void onResume() {
Log.d(tag,"onResume. Interfejs uzytkownika calkowicie widoczny");
super.onResume();
}
}

Plik ukadu graficznego


Listing 13.10 ukazuje plik ukadu graficznego (layout/main.xml). Mamy tu do czynienia z prostym ukadem graficznym obsugujcym aktywno zaprezentowan na listingu 13.9. Jak ju
wspomnielimy, zawarty jest w nim pojedynczy widok tekstowy oraz informacja, e kliknicie
elementu menu spowoduje uruchomienie procedury obsugi wtku roboczego.

Rozdzia 13 Analiza procedur obsugi

445

Listing 13.10. Plik ukadu graficznego


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Kliknij menu, aby wywietli dostpne opcje"
/>
</LinearLayout>

Plik menu
Wykorzystywany na potrzeby tego przykadu kod pliku menu, menu/main_menu.xml, zosta
umieszczony na listingu 13.11. Jest to menu obsugujce aktywno zdefiniowan na listingu
13.9. Jak wynika z aktywnoci, menu to zawiera trzy elementy. Jeden suy do czyszczenia
widoku tekstowego podczas pracy z opcjami menu. Dalej mamy dwa gwne elementy menu:
menu_test_defered_handler przywouje procedur obsugi DeferWorkHandler, natomiast
menu_test_thread tworzy wtek roboczy i wykorzystuje procedur ReportStatusHandler.
Listing 13.11. Elementy menu wywoujce kod procedury obsugi i wtku pobocznego
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa korzysta z domylnej kategorii. -->


<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/menu_clear"
android:title="Wyczy" />
<item android:id="@+id/menu_test_thread"
android:title="Testuj wtek roboczy" />
<item android:id="@+id/menu_test_defered_handler"
android:title="Opniona procedura obsugi" />
</group>
</menu>

Plik manifest
Listing 13.12 prezentuje plik manifest (AndroidManifest.xml), ktry zamyka list plikw rdowych. Jego zawarto jest bardzo nieskomplikowana, gdy wskazuje tylko pojedyncz aktywno, widoczn na listingu 13.9 (gwna aktywno sterujca).
Listing 13.12. Plik AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.handlers"
android:versionCode="1"

446 Android 3. Tworzenie aplikacji


android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="Testowe
procedury obsugi">
<activity android:name=".TestHandlersDriverActivity"
android:label="Testowe procedury obsugi">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

Plik manifest zawiera odniesienie do ikony aplikacji. Moemy wykorzysta plik ZIP projektu,
do ktrego odnonik znajduje si na kocu rozdziau, albo moemy umieci inn ikon z dowolnego innego projektu.

Czas ycia skadnika i procesu


Jeeli przyjrzelimy si uwanie testowej aktywnoci TestHandlersDriverActivity (listing
13.9), na pewno zauwaylimy, e zostay w niej zamieszczone rne metody dotyczce poszczeglnych etapw cyklu ycia aktywnoci. Wprowadzilimy je po to, aby pokaza, co si stanie,
gdy aktywno zostanie ukryta oraz ponownie wywietlona. Co si stanie z oczekujcymi komunikatami w gwnej kolejce? Co si dzieje z przetwarzanym wtkiem roboczym?
Wyjanimy zachodzce zjawiska poprzez omwienie cyklu ycia kadego skadnika Androida.
Chocia omwimy zaraz cykle ycia skadnikw, przypominamy, e nie zajmujemy si tu nimi
szczegowo. Cykl ycia aktywnoci zosta dokadnie omwiony (wraz z diagramem) w rozdziale 2. Z kolei dyskusj na temat cyklu yciu usugi odnajdziemy w rozdziale 11. Informacje
przedstawione w dalszej czci rozdziau dotycz wycznie aspektw wpywajcych na przetwarzanie komunikatw oraz wtki robocze.

Cykl ycia aktywnoci


Rozpoczniemy od skadnika Activity. Rysunek 13.3 prezentuje cykl ycia aktywnoci z uwzgldnieniem jej widocznoci oraz czasu ycia (zmiany stanu aktywnoci w zalenoci od jej metod
cyklu yciu zostay omwione w rozdziale 2.).
Po utworzeniu aktywnoci (w momencie uruchomienia aplikacji) moe by ona cakowicie
widoczna, czciowo widoczna lub zupenie niewidoczna. Moemy wykry kad tak granic
widocznoci za pomoc metod zwrotnych.
Aktywno wywouje metod onPause, gdy przechodzi do stanu czciowej widocznoci. Z tego
miejsca moe wywoa metod onStop, dziki ktrej staje si zupenie niewidoczna. Ostatecznie,
po zakoczeniu procesu, zostaje wywoana metoda onDestroy, co jest jednoznaczne z cakowitym
usuniciem stanu widocznoci. Przedtem stan ten jest cigle dostpny.

Rozdzia 13 Analiza procedur obsugi

447

Rysunek 13.3. Cykl ycia aktywnoci

Gdy aktywno przechodzi do stanu penej widocznoci, zostaje wywoana metoda onResume.
Jeeli przechodzi ze stanu zupenej niewidocznoci, najpierw zostaje wywoana metoda onStart,
a nastpnie dopiero onResume (albo onStop, jeli aktywno ma znowu sta si niewidoczna).
Pomidzy wywoaniami metod onResume i onPause aktywno jest w peni widoczna.
Chocia aplikacja moe by czciowo lub cakowicie niewidoczna, kolejka komunikatw bdzie
niezmiennie aktywna, tak samo jak wtek roboczy. Zauwaymy to, jeli bdziemy obserwowa
metody cyklu ycia aktywnoci pokazane na listingu 13.9. Stwierdzimy, e komunikaty z wtku
roboczego oraz z procedury obsugi s cigle aktywne po wywoaniu metod onPause i onStop.
Moemy przetestowa t hipotez, klikajc przycisk ekranu startowego w trakcie przebywania
w aktywnoci. W ten sposb przeniesiemy aktywno do ta i wywoamy metody onPause,
onStop, a moe nawet onDestroy. Przez cay czas bd generowane komunikaty, dopki nie
zostanie wywoana metoda onDestroy (pod warunkiem e wysalimy tyle komunikatw).
Jeeli proces nie jest aktywny w czasie dania wywoania aktywnoci, zostanie ona uruchomiona i zwrcona na pierwszy plan. W przypadku ograniczenia pamici lub wtedy, gdy aktywno jest ukryta i nic si nie dzieje w danym procesie, zostanie on usunity przez system.
Najwaniejsza jest wiadomo, e jeeli aktywno zostanie zatrzymana z ktrego
z wymienionych wyej powodw, nie zostanie pniej automatycznie przywrcona.
Uytkownik musi jawnie przywoa aktywno albo poprzez kliknicie elementu
menu, albo w jaki inny, poredni sposb, na przykad uruchomienie aktywnoci, ktrej
dziaanie przywoa t zatrzyman aktywno. Jedynym przypadkiem, w ktrym aktywno
zostanie zatrzymana i ponownie uruchomiona, jest zmiana konfiguracji urzdzenia
(na przykad zmiana orientacji ekranu). Moemy sobie wyobrazi, e takie obracanie
telefonu moe wystpowa do czsto.

448 Android 3. Tworzenie aplikacji

Cykl ycia usugi


Usuga zachowuje si inaczej od aktywnoci pod jednym podstawowym wzgldem jest ona
zasadniczo trwaa. Android gwarantuje nieprzerwane dziaanie usugi, jeli to tylko jest moliwe.
Nawet jeli proces usugi musi zosta zatrzymany z powodu ogranicze pamici, zostanie uruchomiony ponownie, jeli bd si w nim znajdowa komunikaty oczekujce na przetworzenie.
Zajmiemy si tym zagadnieniem o wiele dokadniej w nastpnym rozdziale, w trakcie omawiania odbiorcw komunikatw oraz usug dugoterminowych.
Jednak wspln cech aktywnoci i usugi jest moliwo ich zamknicia z powodu braku pamici. System bdzie si stara utrzyma usug w stanie dziaania, nie ma jednak adnych
gwarancji, e uda si utrzyma j do samego koca.
Aktywnoci i usugi powinny by pisane w taki sposb, aby umoliwia ich eleganckie
zatrzymywanie po wywoaniu metody onDestroy, nawet jeli przypisano im wtki
robocze, w ktrych zachodzi ich przetwarzanie.

Cykl ycia odbiorcw komunikatw


Odbiorcy komunikatw dziaaj zgodnie z zasad wywoaj i znikaj. Proces zwizany z odbiorc bdzie dostpny wycznie przez czas trwania tego odbiorcy i ani chwili duej. Poza tym
odbiorca komunikatw dziaa w gwnym wtku i ma bezwzgldnie ustalon, dziesiciosekundow ram czasow na przeprowadzenie operacji. Aby w odbiorcy komunikatw przeprowadza
bardziej skomplikowane, duej trwajce zadania, musimy postpowa zgodnie z do zoonym
protokoem. To bdzie, w rzeczy samej, tematem nastpnego rozdziau. W skrcie jednak, jeli
dany odbiorca komunikatw bdzie pracowa duej ni dziesi sekund, musimy zastosowa
nastpujcy protok:
1. Ustaw blokad przechodzenia urzdzenia w stan upienia (ang. wakelock) w kodzie
odbiorcy (nie pniej), dziki czemu urzdzenie bdzie przynajmniej czciowo aktywne.
2. Wylij wywoanie metody startService(), dziki czemu proces zostanie oznaczony
jako trway oraz, w razie potrzeby, ponownie uruchamialny. Dziki temu proces bdzie
atwiej dostpny.
3. Zwr uwag, e nie moesz wykonywa operacji bezporednio na usudze, bo zajmie to
wicej czasu ni dziesi sekund, a to spowodowaoby wstrzymanie gwnego wtku.
Wynika to z faktu, e usuga rwnie dziaa w gwnym wtku.
4. Rozpocznij wtek roboczy z poziomu usugi.
5. Zaprogramuj wtek roboczy, aby zamieszcza komunikaty za pomoc procedury
obsugi w usudze, albo wywoaj na usudze metod stopService().
Zgodnie z wczeniejsz obietnic w nastpnym rozdziale przeanalizujemy o wiele dokadniej
powyszy protok. W rzeczywistoci jest on silnie zaleny od procedur obsugi. Omawiane
koncepcje bdziemy wyjania za pomoc sporej iloci przykadowego kodu.

Cykl ycia dostawcy treci


Z dostawc treci jest zupenie inna historia. Zarwno wewntrzne, jak i zewntrzne klienty
oddziauj synchronicznie na dostawc treci. W przypadku klientw zewntrznych jest to
moliwe dziki puli wtkw. Podobnie jak odbiorcy komunikatw, dostawcy treci nie posiadaj

Rozdzia 13 Analiza procedur obsugi

449

jakiego szczeglnego cyklu ycia. Od momentu uruchomienia istniej, dopki trwa proces.
Nawet jeli wykazuj synchroniczno wobec zewntrznych klientw, nie bd przetwarzani
w gwnym wtku, lecz w puli wtkw przechowujcego ich procesu, podobnie jak ma to miejsce w przypadku zwizku klient sieciowy serwer sieciowy. Wtek kliencki bdzie oczekiwa,
dopki nie nadejdzie wywoanie zwrotne. Jeeli nie ma adnych klientw, proces zostaje odzyskany zgodnie z reguami odzyskiwania, w zalenoci od tego, jakie inne skadniki zostay zdefiniowane oraz aktywnie dziaaj w tym procesie.

Instrukcje dotyczce kompilowania kodu


W tym rozdziale utworzylimy osiem gwnych plikw projektu. Zalecamy pobranie pliku ZIP
dostpnego pod adresem umieszczonym na kocu rozdziau, w podrozdziale Odnoniki, chocia
moemy rwnie skompilowa projekt za pomoc listingw zamieszczonych w ksice.

Utworzenie projektu za pomoc pliku ZIP


Aby utworzy projekt za pomoc pliku ZIP, trzeba wykona nastpujce czynnoci:
1. Pobierz plik ZIP.
2. Wybierz opcje File/Import z menu rodowiska Eclipse.
3. Nastpnie kliknij opcje General/Existing Project into Workspace.
4. Wybierz teraz opcj Select Root Directory.
5. Zaznacz opcj Copy projects into workspace.
6. By moe zaistnieje potrzeba wyboru odpowiedniego poziomu interfejsu API
po zamieszczeniu projektu poprzez wybr opcji Project properties/Android oraz
zaznaczenie waciwego interfejsu.

Tworzenie projektu za pomoc listingw


Mona ewentualnie skonstruowa projekt z listingw umieszczonych w tym rozdziale. Potrzebne
pliki zostay wymienione w podrozdziale Klasy przykadowego sterownika procedury obsugi;
poniej prezentujemy wymagane czynnoci:
1. Utwrz nowy projekt, wybierajc opcje File/New Project/Android/Android Project.
2. Wybierz nazw i zaznacz opcj Create new project in workspace.
3. Nazwij aplikacj Testowe procedury obsugi.
4. Wybierz poziom interfejsu API.
5. Wykorzystaj nazw pakietu, na przykad com.androidbook.handlers.
6. Wybierz warto 3 parametru min SDK version.
7. Wprowad nazw aktywnoci TestHandlersDriverActivity i kliknij przycisk Finish.
8. Android wygeneruje pliki zasobw oraz najprawdopodobniej jeden plik rdowy
(w zalenoci od wersji rodowiska).
9. Utwrz lub zaktualizuj pliki na podstawie listingw zamieszczonych w tym rozdziale.
10. W przypadku plikw Java umie nazw pakietu na samym pocztku listingu, zanim
go skopiujesz. Nastpnie, po skopiowaniu i wklejeniu kodu, wcinij kombinacj klawiszy
Ctrl+Shift+O, aby uzupeni plik o instrukcje importu.

450 Android 3. Tworzenie aplikacji


Naley pamita, e w czasie tworzenia projektu trzeba dostosowa przedstawione tu pliki i wprowadzi pewne brakujce skadniki, aby projekt zosta skompilowany. Wszelkie braki atwo uzupeni, korzystajc z pliku ZIP.

Odnoniki
W czasie zapoznawania si z tematami omawianymi w tym rozdziale Czytelnik moe zechcie
zdoby wicej informacji. S one dostpne pod poniszymi adresami URL; przy kadym
odnoniku umiecilimy rwnie krtk notatk na temat prezentowanych zasobw.
http://developer.android.com/reference/android/os/Handler.html znajdziemy tu
odniesienie do interfejsu API procedur obsugi. Zostay tu omwione metody
pozwalajce na konstruowanie procedur obsugi, uzyskiwanie komunikatu,
przesonicie metod handleMessage() oraz sendMessage() i tak dalej.
http://developer.android.com/reference/android/os/Message.html pod tym adresem
zdobdziemy informacje na temat interfejsu API komunikatw. Chocia interfejs ten jest
stosunkowo rzadko stosowany, poniewa rwnowane funkcje dostpne s w interfejsie
API procedury obsugi, warto pozna fundamenty obiektu Message i dowiedzie si
co nieco na temat tego interfejsu. Zalecamy zapoznanie si z wiadomociami
umieszczonymi pod tym adresem.
http://developer.android.com/guide/topics/fundamentals.html#lcycles szczegowe
dane na temat cyklw ycia. Zosta tu pooony nacisk przede wszystkim na cykle
ycia aktywnoci i usug, wspomniano take o cyklach ycia odbiorcw komunikatw.
Waciwie nie znajdziemy tu informacji na temat dostawcw treci.
http://www.science.uva.nl/ict/ossdocs/java/tutorial/java/threads/states.html bardzo
merytoryczny i niezbdny do przeczytania artyku wprowadzajcy w tematyk wtkw.
http://www.netmite.com/android/mydroid/1.6/frameworks/base/core/java/android/ap
p/IntentService.java znakomity przykad wykorzystania procedur obsugi przez
bazowy kod systemu Android w implementacji klasy IntentService. Niniejszy
adres jest odniesieniem do kodu rdowego zawartego w pliku IntentService.java.
Bardzo zalecamy, by po przeczytaniu tego rozdziau, w celu utrwalenia wiadomoci
dotyczcych wtkw, przejrze kod rdowy klasy IntentService.
http://www.androidbook.com/item/3514 notatki jednego z autorw dotyczce
usug o duszym czasie trwania.
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw projektw
utworzonych na potrzeby ksiki. Waciwy plik znajdziesz w katalogu o nazwie
ProAndroid3_R13_ProceduryObsugi.

Podsumowanie
W tym rozdziale przeanalizowalimy rne skadniki procesu Androida, a take sposb koordynowania ich dziaania przez gwny wtek. Pokazalimy, w jaki sposb procedury obsugi
i wtki mog by wykorzystywane do poszerzania zasigu gwnego wtku, a take dlaczego
gwny wtek musi powraca do dziaania w przecigu piciu sekund, zanim pojawi si komunikat ANR. Taka sama zasada dotyczy odbiorcw komunikatw, przy czym w ich przypadku
rama czasowa zostaje zwikszona do dziesiciu sekund.

Rozdzia 13 Analiza procedur obsugi

451

Omwilimy cykle ycia poszczeglnych skadnikw oraz ich wpyw na wtek gwny i wtki
poboczne. Wiedza ta jest niezbdna do zrozumienia zawioci tych skadnikw oraz czynnoci
potrzebnych do wykonywania dugoterminowych operacji.
Nastpny rozdzia jest powicony pracy z odbiorcami komunikatw oraz przeprowadzaniu
dugoterminowych dziaa. Informacje zawarte w tym rozdziale pomog nam zrozumie
pojcia przedstawione w nastpnym.

452 Android 3. Tworzenie aplikacji

R OZDZIA

14
Odbiorcy komunikatw
i usugi dugoterminowe

W poprzednich rozdziaach zajmowalimy si w przewaajcej czci aktywnociami, dostawcami treci i usugami. Niezbyt dokadnie przedstawilimy zagadnienia
dotyczce odbiorcw komunikatw, dlatego teraz przyjrzymy im si uwaniej.
Zaprezentujemy najpierw sposb przywoania prostego odbiorcy komunikatw,
a nastpnie rozszerzymy ten mechanizm na procedur wywoania wielu takich
obiektw. Pokaemy rwnie, w jaki sposb odbiorcy komunikatw mog si znajdowa w zewntrznych procesach. Zademonstrujemy take sposb, w jaki odbiorcy
komunikatw wysyaj powiadomienia poprzez meneder powiadomie.
Przeanalizujemy dziesiciosekundowy limit, w ktrym musi si zmieci odbiorca
komunikatw, aby nie zostaa wywietlona informacja ANR (aplikacja nie odpowiada), a take sposoby pominicia tego ograniczenia czasowego. Utworzymy specjaln struktur, pozwalajc na obserwowanie dugoterminowej usugi, bdcej
specjaln wersj odbiorcy komunikatw, na kocu natomiast omwimy blokady
przechodzenia urzdzenia w stan zatrzymania (ang. wakelock) w kontekcie dugoterminowych usug.
Rozpocznijmy od obszernej analizy odbiorcw komunikatw na przykadzie utworzenia prostego obiektu tego typu.

Odbiorcy komunikatw
W rozdziale 13. zdefiniowalimy odbiorc komunikatw (ang. broadcast receiver)
jako jeden ze skadnikw procesu (innymi skadnikami s: aktywno, usuga oraz
dostawca treci). Jak sama nazwa wskazuje, zadaniem odbiorcy komunikatw jest
odpowiadanie na wiadomoci wysyane przez klienta. Sam taki komunikat jest intencj, ktra moe by odbierana przez wielu odbiorcw.
Taki skadnik, jak aktywno lub usuga (albo inny komponent implementujcy klas
Context), prbujcy wysa zdarzenie (intencj), korzysta z metody sendBroadcast()
dostpnej w klasie Context. Argumentem tej metody jest wysyana intencja.

454 Android 3. Tworzenie aplikacji


Skadniki odbierajce przesyan intencj musz dziedziczy po klasie Receiver, dostpnej
w zestawie Android SDK. Te odbierajce skadniki (odbiorcy komunikatw) musz zosta
nastpnie zarejestrowane w pliku manifecie jako obiekt receiver, ktry reaguje na nadawan intencj.
Moemy rwnie rejestrowa odbiorcw treci w trakcie dziaania kodu, bez koniecznoci
ich rejestrowania w pliku manifecie. Zwrmy uwag, e nie omawiamy tu tego
rozwizania, zalecamy natomiast przejrzenie dokumentacji interfejsu API, do ktrej
adres mona znale w podrozdziale Odnoniki.

Wysyanie komunikatu
Listing 14.1 przedstawia przykadowy kod bdcy czci klasy aktywnoci, sucy do przesyania komunikatu. Za pomoc tego kodu tworzymy intencj zawierajc unikatowe, specyficzne dla niej dziaanie, nastpnie wstawiamy do niej dodatkowy komunikat oraz wywoujemy
metod sendBroadcast(). Wstawienie dodatkowego komunikatu jest opcjonaln czynnoci;
czsto samo otrzymanie intencji cakowicie wystarczy odbiorcy, a dodatkowy komunikat
okazuje si zbdny.
Listing 14.1. Nadawanie intencji
private void testSendBroadcast(Activity activity)
{

//Tworzy intencj zawierajc okrelone dziaanie


String uniqueActionString = "com.androidbook.intents.testbc";
Intent broadcastIntent = new Intent(uniqueActionString);
broadcastIntent.putExtra("message", "Witaj, wiecie!");
activity.sendBroadcast(broadcastIntent);
}

W kodzie z listingu 14.1 dziaaniem jest niestandardowy identyfikator, dostosowany do naszych


potrzeb. eby ten cig znakw dziaania by niepowtarzalny, moemy wprowadzi przestrze
nazw podobn do stosowanej w klasie Java. Spjrzmy teraz, w jaki sposb moemy odpowiedzie na tak nadan intencj.

Tworzenie prostego odbiorcy przykadowy kod


Na listingu 14.2 zosta umieszczony kod odbiorcy odpowiadajcy na nadsyan intencj, utworzon z kodu na listingu 14.1.
Listing 14.2. Przykadowy kod odbiorcy
public class TestReceiver extends BroadcastReceiver
{
private static final String tag = "TestReceiver";
@Override
public void onReceive(Context context, Intent intent)
{
Utils.logThreadSignature(tag);
Log.d("TestReceiver", "intencja=" + intent);
String message = intent.getStringExtra("message");

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

455

Log.d(tag, message);
}
}

Utworzenie odbiorcy komunikatw jest do proste. Rozszerzamy po prostu klas Broadcast


i przesaniamy metod onReceive(). Moemy obejrze intencj wewntrz tego
odbiorcy i odczyta z niej komunikat. Jeeli nadawana intencja nie posiada dodatkowego
komunikatu, nazwanego tutaj message, zostanie zwrcona warto null. Poniewa wiemy,
e w naszym przykadzie ten komunikat zosta wstawiony do intencji, nie przeprowadzilimy
testu na warto null. Po odczytaniu tego komunikatu wypisujemy go w oknie dziennika.

Receiver

Umiecilimy w naszym testowym odbiorcy metod narzdziow, pozwalajc na zapisywanie


w dzienniku sygnatury wtku, w ktrym jest przetwarzany kod odbiorcy. Poniewa w tym rozdziale bdziemy czsto korzysta z klasy Utils, na listingu 14.3 zamieszczamy kod rdowy pliku
Utils.java.
Listing 14.3. Definicja klasy Utils
public class Utils
{
public static long getThreadId()
{
Thread t = Thread.currentThread();
return t.getId();
}
public static String getThreadSignature()
{
Thread t = Thread.currentThread();
long l = t.getId();
String name = t.getName();
long p = t.getPriority();
String gname = t.getThreadGroup().getName();
return (name + ":(id)" + l + ":(priorytet)" + p
+ ":(grupa)" + gname);
}
public static void logThreadSignature(String tag)
{
Log.d(tag, getThreadSignature());
}
public static void sleepForInSecs(int secs)
{
try
{
Thread.sleep(secs * 1000);
}
catch(InterruptedException x)
{
throw new RuntimeException("przerwano",x);
}
}
}

Po utworzeniu odbiorcy komunikatw z listingu 14.2 musimy zarejestrowa go w pliku manifecie.

456 Android 3. Tworzenie aplikacji

Rejestrowanie odbiorcy komunikatw w pliku manifecie


Na listingu 14.4 wida, w jaki sposb mona zadeklarowa odbiorc jako adresata intencji, ktrej
dziaaniem jest identyfikator com.androidbook.intents.testbc.
Listing 14.4. Definicja odbiorcy w pliku manifecie
<manifest>
<application>
...
<activity ..>
...
<receiver android:name=".TestReceiver">
<intent-filter>
<action android:name="com.androidbook.intents.testbc"/>
</intent-filter>
</receiver>
...
</application>
</manifest>

Podobnie jak w przypadku pozostaych rodzajw skadnikw, obiekt receiver jest wzem
potomnym elementu application. To nam wystarczy do przetestowania odbiorcy. W nastpnym punkcie zamieszczamy list plikw wymaganych do utworzenia projektu testowego.
Zanim zaczniemy z zapaem kopiowa (albo, co gorsza, przepisywa) kody zamieszczone na listingach, pamitajmy, e w podrozdziale Odnoniki zosta umieszczony adres URL, pod ktrym znajdziemy list wszystkich projektw moemy je pobra na dysk i zaimportowa do
rodowiska Eclipse.

Wysyanie komunikatu testowego


Poniej przedstawiamy list plikw wymaganych do utworzenia projektu testowego wraz z odniesieniami do odpowiednich listingw:
TestBCRActivity.java przykadowa aktywno uruchamiajca odbiorc
wiadomoci (w skrcie BCR; listing 14.5),
layout/main.xml prosty, tekstowy ukad graficzny wywietlajcy komunikaty;
ukad ten zostanie wykorzystany w aktywnoci TestBCRActivity (listing 14.6),
menu/main_menu.xml menu pozwalajce na ponown transmisj komunikatu,
wykorzystywane przez klas TestBCRActivity (listing 14.7),
TestReceiver.java przykadowy odbiorca komunikatw (zaprezentowany na
listingu 14.2),
Utils.java kilka narzdzi wtkowania (plik zaprezentowany na listingu 14.3),
AndroidManifest.xml plik manifest, w ktrym zostay zdefiniowane aktywno
oraz odbiorca komunikatw (listing 14.8).
Wczeniej ju zaprezentowalimy cz plikw wchodzcych w skad tego projektu, zamiecimy
wic teraz pozostae kody. Na listingu 14.5 widzimy aktywno TestBCRActivity przywoujc
menu, za pomoc ktrego moemy nada komunikat. Wywoanie menu zostao oznaczone
pogrubion czcionk.

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

Listing 14.5. Aktywno klienta nadawczego


public class TestBCRActivity extends Activity
{
public static final String tag="TestBCRActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu){
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater(); //z aktywnoci
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
appendMenuItemText(item);
if (item.getItemId() == R.id.menu_clear){
this.emptyText();
return true;
}
if (item.getItemId() == R.id.menu_send_broadcast){
this.testSendBroadcast();
return true;
}
return true;
}
private TextView getTextView(){
return (TextView)this.findViewById(R.id.text1);
}
private void appendMenuItemText(MenuItem menuItem){
String title = menuItem.getTitle().toString();
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + title);
}
private void emptyText(){
TextView tv = getTextView();
tv.setText("");
}
private void testSendBroadcast()
{

//Wywietla identyfikator dziaajcego wtku


Utils.logThreadSignature(tag);

//Tworzy intencj zawierajc dziaanie


Intent broadcastIntent = new Intent("com.androidbook.intents.testbc");

//Zapisuje w intencji komunikat,


//ktry chcemy nada
broadcastIntent.putExtra("message", "Witaj, wiecie!");

//Wysya komunikat

457

458 Android 3. Tworzenie aplikacji


//Moe go odczyta wielu odbiorcw
this.sendBroadcast(broadcastIntent);

//Wywietla komunikat po wysaniu go do odbiorcy


//Powinien on pojawi si najpierw w pliku dziennika
//jeszcze przed komunikatami pochodzcymi z transmisji,
//poniewa dziaaj one w tym samym wtku
Log.d(tag,"po wyslaniu komunikatu z poziomu glownego menu");
}
}

Ukad graficzny obsugujcy klas TestBCRActivity zosta zaprezentowany na listingu 14.6,


a generowany z niego widok zosta pokazany na rysunku 14.1.
Listing 14.6. Plik ukadu graficznego
<!-- layout/main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Tutaj bd wywietlane komunikaty debugowania"
/>
</LinearLayout>

Poniej na listingu 14.7 przedstawiamy plik menu.


Listing 14.7. Plik zasobw menu
<!-- menu/main_menu.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/menu_clear"
android:title="Wyczy" />
<item android:id="@+id/menu_send_broadcast"
android:title="Transmituj" />
</group>
</menu>

Peny kod rdowy pliku TestReceiver.java znajdziemy na listingu 14.2, natomiast pliku
Utils.java na listingu 14.3.

Listing 14.8 zawiera kod rdowy pliku manifestu.

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

459

Listing 14.8. Plik AndroidManifest.xml


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.bcr"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="Testowy odbiorca kom.">
<activity android:name=".TestBCRActivity"
android:label="Testowy odbiorca komunikatw">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".TestReceiver">
<intent-filter>
<action android:name="com.androidbook.intents.testbc"/>
</intent-filter>
</receiver>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

Po skompilowaniu i uruchomieniu tego projektu ujrzymy aktywno i menu przypominajce


ekran widoczny na rysunku 14.1.

Rysunek 14.1. Przykadowa aktywno zawierajca menu, pozwalajce na przetestowanie


odbiorcy komunikatw

Po klikniciu przycisku Transmituj zostanie wywoany odbiorca TestReceiver, zdefiniowany


na listingu 14.2, a w oknie LogCat pojawi si komunikat Witaj, wiecie!. Komunikat ten
zosta wczytany do wysyanej intencji przez aktywno.

460 Android 3. Tworzenie aplikacji

Wprowadzanie wielu odbiorcw komunikatw


Jednym z kluczowych punktw koncepcji nadawania komunikatw jest moliwo ich odczytywania przez wielu odbiorcw. Sprbujmy wic powieli klas TestReceiver (z listingu 14.2) jako
TestReceiver2 i sprawdzi, czy obydwie zostan przywoane. Kod odbiorcy TestReceiver2
zosta zaprezentowany na listingu 14.9.
Listing 14.9. Powielony odbiorca komunikatw
public class TestReceiver2 extends BroadcastReceiver
{
private static final String tag = "TestReceiver2";
@Override
public void onReceive(Context context, Intent intent)
{
Utils.logThreadSignature(tag);
Log.d(tag, "intencja=" + intent);
String message = intent.getStringExtra("message");
Log.d(tag, message);
}
}

Po utworzeniu powyszego kodu moemy doda tego odbiorc do pliku manifestu (listing 14.8),
zgodnie z ponisz definicj (listing 14.10).
Listing 14.10. Definicja odbiorcy TestReceiver2 w pliku manifecie
<receiver android:name=".TestReceiver2">
<intent-filter>
<action android:name="com.androidbook.intents.testbc"/>
</intent-filter>
</receiver>

Jeeli teraz ponownie przywoamy element menu widoczny na rysunku 14.1, w oknie LogCat
ujrzymy komunikat Witaj, wiecie! pochodzcy z obydwu odbiorcw.
Stwierdzimy rwnie, e odbiorcy s wywoywani zgodnie z kolejnoci ich definiowania w pliku manifecie. Moemy take sprawdzi, w jakim wtku ci odbiorcy s uruchomieni. Wywoanie
metody Utils.logThreadSignature(tag) spowoduje wywietlenie sygnatury dziaajcego
wtku. Okae si, e w istocie odbiorcy s przetwarzani w gwnym wtku.
Zauwaymy ponadto, e komunikaty dziennika umieszczone przed wywoaniem metody
sendBroadcast() w metodzie testSendBroadcast() oraz po jej wywoaniu (listing 14.5) bd

wywietlane w oknie dziennika przed wiadomociami z odbiorcw komunikatw oraz posiadaj t sam sygnatur wtku.
Mamy wic dowd, e gwny wtek pracuje przez cay czas i po wyjciu z kolejki wiadomoci
przetwarza odbiorcw komunikatw. Wida wyranie, e metoda sendBroadcast() jest asynchroniczn wiadomoci, umoliwiajc gwnemu wtkowi powrt do kolejki komunikatw.

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

461

Jeeli chcemy otrzyma mocniejsze dowody, moemy przetrzyma gwny wtek troch duej,
tak aby znaczniki czasowe zostay wyranie zaznaczone. Utwrzmy kolejnego odbiorc komunikatw, ktry tym razem bdzie nieco opnia wtek gwny za pomoc wstrzymania. Kod
rdowy takiego opniajcego odbiorcy zosta zaprezentowany na listingu 14.11.
Listing 14.11. Odbiorca generujcy opnienie czasowe
/*
* Odbiorca ten zosta wprowadzony w celu ukazania,
* w jaki sposb gwny wtek ustala kolejno przetwarzania odbiorcw komunikatw
*
* Przykad ten pomaga odpowiedzie na takie pytania, jak:
* 1. Czy s one przywoywane w kolejnoci ich definiowania w pliku manifecie?
* 2. Czy s one przetwarzane jeden po drugim, czy te rwnolegle?
*
* Widzimy, e za pomoc opnienia czasowego wprowadzamy gwny wtek
* w stan wstrzymania na tyle wanie sekund. Wida to
* w pliku wynikowym Log.d
*/
public class TestTimeDelayReceiver extends BroadcastReceiver
{
private static final String tag = "TestTimeDelayReceiver";
@Override
public void onReceive(Context context, Intent intent)
{
Utils.logThreadSignature(tag);
Log.d(tag, "intencja=" + intent);
Log.d(tag, "przechodzi w stan wstrzymania na 2 sekundy");
Utils.sleepForInSecs(2);
Log.d(tag, "wybudzanie");
String message = intent.getStringExtra("message");
Log.d(tag, message);
}
}

Jeeli teraz wstawimy definicj tego odbiorcy pomidzy definicje dwch poprzednio utworzonych, zauwaymy, e gwny wtek jest przegldany przez podstawow logik oraz logik odbiorcy komunikatu. Obserwujc okno LogCat, stwierdzimy, e najpierw jest przetwarzany
pierwszy odbiorca. Nastpnie zostaje przywoany drugi odbiorca, za gwny wtek zostaje wtedy
wstrzymany na dwie sekundy i przechodzi do trzeciego odbiorcy. Poza tym zauwaymy, e
wszyscy odbiorcy bd przywoywani dopiero po powrocie metody sendBroadcast().
Aby przetestowa odbiorc komunikatw z opnieniem czasowym, dodajmy jego definicj
z listingu 14.12 do pliku manifestu widocznego na listingu 14.8.
Listing 14.12. Definicja odbiorcy treci z opnieniem czasowym w pliku manifecie
<receiver android:name=".TestTimeDelayReceiver">
<intent-filter>
<action android:name="com.androidbook.intents.testbc"/>
</intent-filter>
</receiver>

462 Android 3. Tworzenie aplikacji

Projekt wykorzystujcy odbiorcw pozaprocesowych


Sednem caej koncepcji nadawania komunikatu jest umoliwienie jego odczytania i przetworzenia obcemu procesowi, ktry nie jest tosamy z procesem klienckim. Wobec tego sprbujmy utworzy kolejny plik .apk oraz zarejestrowa w tym pakiecie odbiorc, tak aby mg on
zareagowa na komunikat o zdarzeniu, nadawany przez kod z listingu 14.1.
Poniej zamieszczamy list plikw wymaganych do utworzenia takiego niezalenego projektu;
jak zwykle moemy skorzysta z adresu URL zamieszczonego na kocu rozdziau i pobra
niezbdne pliki:
StandaloneReceiver.java prosty odbiorca (listing 14.13),
AndroidManifest.xml plik manifest (listing 14.14),
Utils.java ten sam plik by wykorzystywany w poprzednim projekcie (listing 14.4).
Jest to okrojony projekt, niekomunikujcy si z adn aktywnoci, w wyniku czego jest on do
zrozumiay i nie wymaga wprowadzania aktywnoci ani ukadu graficznego. Na listingu 14.13
widzimy przykadowego odbiorc stanowicego cz tego niezalenego projektu. Nazwiemy
go StandaloneReceiver.
Listing 14.13. Przykad odbiorcy przebywajcego we wasnym procesie
public class StandaloneReceiver extends BroadcastReceiver
{
private static final String tag = "Niezaleny odbiorca";
@Override
public void onReceive(Context context, Intent intent)
{
Utils.logThreadSignature(tag);
Log.d(tag, "intencja=" + intent);
String message = intent.getStringExtra("message");
Log.d(tag, message);
}
}

Nie ma tutaj niczego wyjtkowego, utworzylimy jedynie standardowego odbiorc. Plik manifest
rejestrujcy go jest widoczny na listingu 14.14.
Listing 14.14. Plik AndroidManifest.xml, w ktrym zosta umieszczony jedynie odbiorca komunikatw
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.salbcr"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon"
android:label="Niezaleny odbiorca komunikatw">
<receiver android:name=".StandaloneReceiver">
<intent-filter>
<action android:name="com.androidbook.intents.testbc"/>
</intent-filter>
</receiver>

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

463

</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

Wraz z omwionym przy okazji poprzedniego projektu plikiem Utils.java moemy utworzy
i wdroy nowy projekt systemu Android. Jeeli teraz przejdziemy do ekranu widocznego
na rysunku 14.1 i klikniemy element menu Transmituj, przekonamy si, e nasz niezaleny
odbiorca wywietli komunikat w oknie dziennika podobnie do pozostaych odbiorcw.

Uywanie powiadomie pochodzcych


od odbiorcy komunikatw
Odbiorcy komunikatw musz czasami powiadomi uytkownika o jakim wydarzeniu lub
o biecym stanie, a w takim przypadku najlepszym rozwizaniem jest wykorzystanie ikony powiadomie dostpnej w systemowym pasku powiadomie. W tym podrozdziale wyjanimy,
jak mona utworzy powiadomienie za pomoc odbiorcy komunikatw, wysa je i przeglda
poprzez meneder powiadomie.

Monitorowanie powiadomie
za pomoc menedera powiadomie
Ikony powiadomie w Androidzie s wywietlane jako alerty umieszczone w obszarze powiadomie. Obszar powiadomie jest wskim paskiem mieszczcym si w szczytowej czci wywietlacza. Zosta pokazany na rysunku 14.2. Wygld oraz pooenie obszaru powiadomie moe
si rni w zalenoci od tego, czy urzdzenie jest tabletem, czy telefonem, a take czasami zaley
od wersji Androida.

Rysunek 14.2. Pasek powiadomie w Androidzie

Po wprowadzeniu powiadomienia bdzie ono wywietlane w postaci ikony w obszarze zaprezentowanym na rysunku 14.2. Ikona ta zostaa ukazana na rysunku 14.3.

Rysunek 14.3. Pasek stanu z widoczn ikon powiadomie

Na rysunku 14.3 widzimy obszar powiadomie, aktywno, a take ikon powiadomienia.


W tym przypadku nasz aktywnoci jest aplikacja rozsyajca komunikaty. Moe to by rwnie
dobrze inna aktywno, a nawet ekran startowy.

464 Android 3. Tworzenie aplikacji


Ikona powiadomie informuje uytkownika, e dzieje si co, na co naley zwrci uwag. Aby
ujrze waciwe powiadomienie, przytrzymujemy palec na ikonie powiadomienia i rozsuwamy
pasek powiadomie (widoczny na rysunku 14.2) jak kurtyn. Zostanie rozwinity obszar powiadomie, widoczny na rysunku 14.4.

Rysunek 14.4. Rozwinity widok powiadomie

W rozwinitym ekranie powiadomie, widocznym na rysunku 14.4, odnajdziemy szczegowe


informacje danego powiadomienia. Moemy rwnie klikn dane powiadomienie, aby uruchomi intencj przywoujc aplikacj, ktrej to powiadomienie dotyczy. W nastpnym przykadzie wykorzystamy intencj uruchamiajc przegldark.
Na rysunku 14.4 widzimy rwnie, e w tym widoku moemy usuwa powiadomienia.
Ten sam widok szczegw powiadomie moemy uruchomi z poziomu menu ekranu startowego. Rysunek 14.5 prezentuje menu ekranu startowego, dostpne na emulatorze. W zalenoci
od urzdzenia oraz wersji Androida menu ekranu startowego moe wyglda inaczej.
Kliknicie ikony Powiadomienia, widocznej na rysunku 14.5, spowoduje wywietlenie ekranu
powiadomie, znanego z rysunku 14.4.
Przekonajmy si teraz, w jaki sposb moemy wygenerowa widoczn na rysunkach 14.3 i 14.4
ikon powiadomie.

Wysyanie powiadomienia
Zaczynajmy. Proces wysyania powiadomienia skada si z trzech nastpujcych etapw:
1. Utworzenie odpowiedniego powiadomienia.
2. Uzyskanie dostpu do menedera powiadomie.
3. Przesanie powiadomienia do menedera powiadomie.

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

465

Rysunek 14.5. Element menu Powiadomienia, dostpny z poziomu ekranu startowego

Tworzone powiadomienie musi zawiera nastpujce elementy:


wywietlan ikon,
gwny tekst, na przykad Witaj, wiecie!,
czas, w ktrym ma zosta dostarczone.
Po zapenieniu obiektu powiadomienia tymi elementami uzyskujemy dostp do kontekstu
poprzez zadanie od kontekstu dostarczenia usugi systemowej, noszcej nazw Context.
NOTIFICATION_SERVICE. Po uzyskaniu dostpu do menedera powiadomie wywoujemy
odpowiedni metod wobec tego obiektu, aby wysa komunikat.
Na listingu 14.15 zosta zamieszczony kod odbiorcy komunikatw, przesyajcego powiadomienie widoczne na rysunkach 14.4 i 14.5.
Listing 14.15. Odbiorca przesyajcy powiadomienia
public class NotificationReceiver extends BroadcastReceiver
{
private static final String tag = "Odbiorca powiadomie";
@Override
public void onReceive(Context context, Intent intent)
{
Utils.logThreadSignature(tag);
Log.d(tag, "intencja=" + intent);
String message = intent.getStringExtra("message");
Log.d(tag, message);
this.sendNotification(context, message);
}
private void sendNotification(Context ctx, String message)
{

466 Android 3. Tworzenie aplikacji


//Uzyskuje dostp do menedera powiadomie
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager nm =
(NotificationManager)ctx.getSystemService(ns);

//Tworzy obiekt powiadomienia


int icon = R.drawable.robot;
CharSequence tickerText = "Witaj";
long when = System.currentTimeMillis();
Notification notification =
new Notification(icon, tickerText, when);

//Ustanawia widok ContentView za pomoc metody setLatestEventInfo


Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.google.com"));
PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, 0);
notification.setLatestEventInfo(ctx, "tytu", "tekst", pi);

//Wysya powiadomienie.
//Pierwszym argumentem jest unikatowy identyfikator tego powiadomienia.
//Identyfikator ten pozwala na pniejsze anulowanie powiadomienia.
nm.notify(1, notification);
}
}

W kodzie rdowym z listingu 14.15 wprowadzilimy odniesienie do ikony alertu nazwanej


R.drawable.robot. Moemy utworzy wasn ikon alertu i wstawi j do podkatalogu
res/drawable, przy czym musimy nazwa j robot i wstawi odpowiednie rozszerzenie pliku.
Ewentualnie moemy rwnie skorzysta z pliku ZIP zawierajcego gotowy projekt (adres
URL zosta podany w podrozdziale Odnoniki).
Podczas tworzenia powiadomienia zawierajcego podstawowe parametry (ikona, tekst, czas)
oraz wysyania go do menedera powiadomie moemy odnie wraenie, e s one niewystarczajce (pierwszy segment listingu 14.15 dotyczy tworzenia powiadomienia). Musimy take
zdefiniowa dla powiadomienia element zwany widokiem treci za pomoc metody:
setLatestEventInfo()

Widok treci powiadomienia jest wywietlany po rozwiniciu paska powiadomie. Jest on


widoczny na rysunku 14.4. Przewanie musi on by obiektem RemoteViews. Nie przekazujemy
jednak tego widoku bezporednio do metody setLatestEventInfo. Metoda jest wykorzystywana jako skrt pozwalajcy na zdefiniowanie standardowego, domylnego widoku treci,
wywietlajcego tytu i tekst.
Metoda setLatestEventInfo() pobiera take argument w postaci oczekujcej intencji (zwanej
intencj treci), ktra zostaje uruchomiona po rozwiniciu widoku powiadomie. Spjrzmy
jeszcze raz na listing 14.15, aby zobaczy, jakie parametry zostay przekazane tej metodzie.
Moemy take sami stworzy zdalny widok i przetworzy go na widok treci, bez koniecznoci
stosowania metody setLatestEventInfo().

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

467

Aby przetworzy zdalny widok na widok treci powiadomienia, naley wykona nastpujce
czynnoci:
1. Utwrz plik ukadu graficznego.
2. Utwrz obiekt RemoteViews za pomoc nazwy pakietu oraz identyfikatora pliku
ukadu graficznego.
3. Wywoaj wobec tego obiektu metody ustanawiajce tekst, ikony itd.
4. Wywoaj metod setContentView() na obiekcie powiadomienia przed jego
wysaniem do menedera powiadomie.
Nie zapominajmy, e w przypadku wersji 2.2 Androida zdalny widok posiada ograniczony zestaw kontrolek:
FrameLayout,
LinearLayout,
RelativeLayout,
AnalogClock,
Button,
Chronometer,
ImageButton,
ImageView,
ProgressBar,
TextView.
W rozdziale 22. dokadnie omwilimy proces tworzenia takich zdalnych widokw, poniewa
widety ekranu startowego zasadniczo s widokami tego typu. Z kolei rozdzia 31. zawiera list
obiektw RemoteViews zaktualizowan dla wersji 2.3 i 3.0 Androida.
Kod z listingu 14.15 generuje powiadomienie i wykorzystuje metod setLatestEventInfo()
ustanowienia niejawnego widoku treci (za pomoc tytuu i tekstu) oraz uruchamianej intencji
(w naszym przypadku jest to intencja przegldarki).

Dugoterminowi odbiorcy komunikatw i usugi


Dotychczas rozwaalimy optymistyczn wersj wydarze, wedle ktrej przetwarzanie odbiorcw komunikatw zajmuje nie duej ni dziesi sekund. Okazuje si, e zadanie nieco si
komplikuje, gdy chcemy wykonywa zadania zajmujce wicej czasu.
Aby zrozumie, dlaczego tak si dzieje, przyjrzyjmy si pokrtce kilku faktom dotyczcym odbiorcw komunikatw:
Podobnie jak pozostae skadniki procesu w Androidzie, odbiorcy komunikatw
dziaaj w gwnym wtku.
Wstrzymywanie kodu w odbiorcy komunikatw powoduje rwnie wstrzymanie
gwnego wtku i wystpienie komunikatu ANR.
Limit czasu w odbiorcy komunikatw wynosi 10 sekund w porwnaniu do limitu
wynoszcego 5 sekund dla aktywnoci. Restrykcje s, jak wida, nieco agodniejsze,
co nie zmienia faktu, e nadal mamy do czynienia z ramami czasowymi.

468 Android 3. Tworzenie aplikacji

Proces przechowujcy odbiorc komunikatw bdzie istnia jedynie przez czas


dziaania tego odbiorcy. Inaczej mwic, proces nie bdzie ju obecny po powrocie
metody onReceive() odbiorcy komunikatw. Oczywicie, zakadamy w tym momencie,
e proces bdzie zawiera tylko odbiorc komunikatw. Jeeli zawiera on rwnie
inne uruchomione skadniki, takie jak aktywnoci lub usugi, brane s rwnie pod
uwag ich cykle ycia.
W przeciwiestwie do procesu usugi, proces odbiorcy komunikatw nie jest
ponownie uruchamiany.
Jeeli odbiorca komunikatw rozpocz oddzielny wtek i wraca do wtku gwnego,
Android automatycznie okreli, e operacja zostaa zakoczona i zamknie proces,
nawet jeli znajduj si tam inne dziaajce wtki, ktre w konsekwencji zostan
gwatowanie zamknite.
Podczas przywoywania usugi wysyajcej komunikat Android wchodzi czciowo
w stan blokady przechodzenia w stan zatrzymania i wychodzi z tej blokady w momencie
zakoczenia usugi wysyajcej komunikat w wtku gwnym. Blokada przechodzenia
w stan zatrzymania (ang. wakelock) jest mechanizmem oraz interfejsem API
uniemoliwiajcym urzdzeniu przechodzenie w stan wstrzymania (upienia)
lub jeli urzdzenie znajduje si w stanie wstrzymania przechodzenie w stan
aktywnoci (wybudzenia).

Znajc ju te fakty, zastanwmy si, w jaki sposb mona doprowadzi do wykonywania duszych operacji w odpowiedzi na nadany komunikat.

Protok dugoterminowego odbiorcy komunikatw


Odpowied ley w rozwizaniu nastpujcych problemw:
Bdziemy koniecznie musieli uruchomi oddzielny wtek, aby wtek gwny mg
dziaa bez ryzyka wywietlenia wiadomoci ANR.
Aby uniemoliwi systemowi zamknicie procesu, a w konsekwencji wtku roboczego,
musimy przekaza Androidowi informacj, e proces ten zawiera skadnik posiadajcy
cykl ycia, na przykad usug. Musimy wic j utworzy i uruchomi. Sama usuga
nie moe wykonywa operacji trwajcych duej ni 5 sekund, poniewa bdzie si
znajdowaa w gwnym wtku, a zatem bdzie musiaa uruchomi wtek roboczy
i pozwoli wtkowi gwnemu dalej dziaa.
Przez okres przetwarzania wtku roboczego musimy wykorzysta czciow blokad
przechodzenia w stan upienia, aby urzdzenie nie przeszo w stan wstrzymania.
Taka czciowa blokada umoliwia urzdzeniu przetwarzanie kodu bez koniecznoci
wczania wywietlacza itd., co pozwala na oszczdno energii baterii.
Wspomniana czciowa blokada musi zosta umieszczona w gwnej czci kodu
odbiorcy; w przeciwnym wypadku nie zacznie dziaa w odpowiednim momencie.
Na przykad nie moemy tego kodu umieci w usudze, poniewa czas pomidzy
wywoaniem metody startService() przez odbiorc komunikatw a wywoaniem
metody onStartCommand() usugi rozpoczynajcej dziaanie moe si okaza zbyt krtki.
Poniewa tworzymy usug, z powodu ogranicze pamici moe ona zosta zamknita
i ponownie przywrcona. Jeeli tak si stanie, bdzie trzeba znowu wprowadzi
blokad przechodzenia w stan zatrzymania.

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

469

Kiedy wtek roboczy rozpoczty przez metod onStartCommand() wykona swoj


prac, musi wymusi zatrzymanie usugi w taki sposb, eby zostaa zamknita przez
system i nie musiaa ju si pojawia.
Moliwe jest rwnie wystpienie wikszej liczby nadawanych komunikatw. Majc
to na uwadze, musimy ostronie kontrolowa liczb powstajcych wtkw roboczych.

W zgodzie z powyszymi informacjami zalecany protok przeduenia czasu trwania odbiorcy


komunikatw wyglda nastpujco:
1. Wprowad (statyczn) czciow blokad przechodzenia w stan upienia w metodzie
onReceive() odbiorcy komunikatw. Musi by ona statyczna, gdy w ten sposb
umoliwia komunikacj pomidzy odbiorc a usug. Nie ma innej moliwoci
przekazania usudze odniesienia do tej blokady, poniewa jest ona przywoywana
za pomoc domylnego konstruktora, ktry nie przyjmuje adnych parametrw.
2. Uruchom usug lokaln, dziki czemu proces nie zostanie zamknity.
3. Aktywuj w usudze wtek roboczy, aby wykonywa ca prac. Nie wykonuj operacji
w metodzie onStart() usugi. Jeeli tak zrobisz, po prostu wstrzymasz wtek gwny.
4. Po zakoczeniu dziaania wtku roboczego zatrzymaj usug albo bezporednio,
albo za pomoc procedury obsugi.
5. Zapewnij uruchomienie przez usug statycznej blokady przechodzenia w stan
zatrzymania. Przypominamy, e ta statyczna blokada stanowi jedyny sposb
komunikowania si usugi z obiektem j wywoujcym, w tym przypadku usug
nadajc komunikaty, poniewa nie ma innej moliwoci przekazania usudze
odniesienia do blokady.

Klasa IntentService
Aby umoliwi tworzenie usug, ktre nie bd zatrzymyway gwnego wtku, system Android
zapewnia implementacj lokalnej usugi zwanej IntentService. Usuga ta przenosi operacje do
wtku roboczego, dziki czemu gwny wtek zostaje odciony po okreleniu zakresu zada
wtku pobocznego. Po wywoaniu metody startService() wobec usugi IntentService klasa
ta zakolejkuje danie w wtku pobocznym za pomoc zaptlenia oraz procedury obsugi,
dziki czemu rzeczywist prac wykonuje metoda wywodzca si z tej usugi.
Dokumentacja interfejsu API definiuje klas IntentService w nastpujcy sposb:
IntentService jest bazow klas usug przetwarzajcych zapytania asynchroniczne
(wyraane w postaci intencji) wysyane na danie. Klienty wysyaj zapytania za pomoc
wywoa metody startService(Intent). W razie potrzeby zostaje uruchomiona
usuga, ktra z kolei przetwarza kad intencj w wtku roboczym. Gdy ten wtek
zakoczy dziaanie, usuga zostaje zatrzymana. Taki wzorzec procesora kolejki operacji
jest powszechnie stosowany do odcienia gwnego wtku aplikacji z rnorodnych
zada. Klasa IntentService zostaa stworzona w celu uproszczenia tego wzorca.
Zapewnia obsug wykorzystywanych w tym przypadku mechanizmw. Aby z niej
skorzysta, naley rozszerzy klas IntentService i zaimplementowa metod
onHandleIntent(). Klasa ta bdzie otrzymywaa intencje, uruchomi wtek roboczy
oraz w odpowiednim momencie zatrzyma usug. Wszystkie dania s przetwarzane
w pojedynczym wtku roboczym mog one trwa dowolnie dugo (i nie bd blokowa
gwnej ptli aplikacji), ale w danym momencie bdzie obsugiwane tylko jedno danie.

470 Android 3. Tworzenie aplikacji


Moemy w prosty sposb zademonstrowa koncepcj klasy IntentService za pomoc przykadu widocznego na listingu 14.16. Rozszerzamy klas IntentService i wprowadzamy wszelkie
wymagane zmiany w metodzie onHandleIntent().
Listing 14.16. Stosowanie klasy IntentService
public class MyService extends IntentService
{
protected abstract void onHandleIntent(Intent intent)
{
Utils.logThreadSignature("MyService");

//wykonuje prac w tym wtku pobocznym


//i powraca
}
}

Po utworzeniu usugi tego typu moemy j zarejestrowa w pliku manifecie i wykorzysta kod
klienta do przywoania usugi w postaci context.startService(new Intent(MyService.
class)). Tego typu przywoanie poskutkuje wywoaniem metody onHandleIntent(), widocznej na listingu 14.16.
Warto zauway, e metoda logThreadSignature() wywietli identyfikator wtku roboczego,
a nie gwnego (pamitajmy, e mamy tu do czynienia wycznie z pseudokodem; wkrtce jednak zaprezentujemy rzeczywisty przykad).

Kod rdowy klasy IntentService


W rozdziale 13. zajmowalimy si zagadnieniami wtku gwnego i procedur obsugi. W tym
kontekcie warto przestudiowa kod rdowy klasy IntentService, aby zrozumie, w jaki
sposb wspomniane elementy wspdziaaj wraz z dugoterminow usug wykorzystujc
wtek roboczy. Przyjrzyjmy si teraz kodowi rdowemu klasy IntentService (powielonemu
z kodu rdowego Androida), zamieszczonemu na listingu 14.17.
Listing 14.17. Kod rdowy klasy IntentService
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
public IntentService(String name) {
super();

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

471

mName = name;
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread =
new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
protected abstract void onHandleIntent(Intent intent);
}

Poniej objanilimy dziaanie powyszego kodu:


1. Tworzymy osobny wtek roboczy w metodzie onCreate() usugi. Zazwyczaj
uruchamiamy wtki robocze w metodzie onStartCommand() usugi. W wyniku tego
powstaoby jednak wiele wtkw roboczych, po jednym dla kadej metody
startService. W przypadku klasy IntentService wystarczy jeden wtek roboczy
obsugujcy wszystkie wywoania metody startService, zatem konfigurujemy
wtek roboczy w metodzie onCreate, ktra jest wywoywana tylko raz.
2. Konfigurujemy zaptlenie (a tym samym kolejk otrzymywanych i wysyanych
komunikatw) w wtku roboczym. W ten sposb jeden wtek roboczy moe
odpowiada na wiele komunikatw jeden po drugim, nie musimy wic tworzy
osobnego wtku dla kadego dania.
3. Ustanawiamy uchwyt wobec wtku roboczego, dziki czemu gwny wtek usugi
moe przekaza komunikat za pomoc procedury obsugi. Wtek roboczy jest nam
potrzebny, poniewa za kadym razem, gdy klient stosuje metod startService(),
jej wywoanie przechodzi do gwnego wtku klasy IntentService, a nie naley
przetrzymywa gwnego wtku tej klasy. Potrzebny jest nam mechanizm kolejkowania
tych da, dziki czemu wtek roboczy bdzie mg je przetwarza, gdy tylko bdzie
mia tak sposobno. Moemy tego dokona poprzez umieszczenie w gwnym
wtku procedury obsugi dla wtku roboczego. Zwrmy uwag na metod onStart()
uruchomion w gwnym wtku. Jeeli chcemy to sprawdzi, wystarczy przesoni t

472 Android 3. Tworzenie aplikacji


metod i wywoa jej nadrzdn klas przy jednoczesnym wywietlaniu w dzienniku
sygnatury wtku. Przekonamy si, e metoda onStart() dziaa w gwnym wtku,
natomiast metoda onHandleMessage() w drugoplanowym wtku roboczym.
4. W kocu, po powrocie metody onHandleIntent(), procedura obsugi wywouje
metod stopSelf() wobec usugi. W przypadku braku oczekujcych komunikatw
metoda ta skutecznie zatrzyma usug. Metoda stopSelf() jest zliczana referencyjnie.
Oznacza to, e jeli nawet wywoamy j wielokrotnie, musi istnie taka sama liczba
wywoa metody startService. Dlatego wanie moemy wywoywa metod
stopSelf() za kadym razem, gdy zostanie obsuone wywoanie metody startService.

Rozszerzanie klasy IntentService


na odbiorc komunikatw
Z perspektywy odbiorcy komunikatw klasa IntentService stanowi wietne rozwizanie.
Pozwala nam uruchamia dugo wykonujcy si kod bez obawy wstrzymywania gwnego wtku.
Czy moemy wic wykorzystywa klas IntentService na potrzeby takich duej trwajcych
operacji? I tak, i nie.
Tak, poniewa klasa IntentService wykonuje dwie czynnoci: po pierwsze, przechowuje
dziaajcy proces, poniewa jest usug. Po drugie, pozwala dziaa gwnemu wtkowi, dziki
czemu unikamy komunikatu ANR.
Aby zrozumie odpowied nie, musimy dokadniej zastanowi si nad pojciem blokady przechodzenia w stan zatrzymania. Przy przywoaniu odbiorcy komunikatw, zwaszcza za pomoc
menedera alarmw, urzdzenie nie musi by wczone. Zatem meneder ten czciowo wcza
urzdzenie (jedynie na tyle, aby mc przetworzy kod bez uycia interfejsu uytkownika) poprzez
wywoanie menedera zasilania i zadanie blokady. Blokada ta zostaje zwolniona w momencie
powrotu odbiorcy komunikatw.
W ten sposb wywoanie usugi IntentService pozostaje bez blokady przechodzenia w stan
zatrzymania, wic urzdzenie moe przej w stan upienia jeszcze przed uruchomieniem waciwego kodu. Jednak klasa IntentService, bdc oglnym rozszerzeniem usugi, nie otrzymuje dostpu do blokady.
Potrzebujemy wic dodatkowego rozszerzenia moliwoci klasy IntentService. Potrzebujemy
abstrakcji.
Mark Murphy stworzy odmian klasy IntentService, nazwan WakefulIntentService, ktra
utrzymujc semantyk dziaania pierwotnej wersji usugi, dodatkowo otrzymuje dostp do blokady
przechodzenia w stan zatrzymania i poprawnie j zwalnia po spenieniu pewnych warunkw. Implementacj tej klasy znajdziemy pod adresem http://github.com/commonsguy/cwac-wakeful.

Abstrakcja dugoterminowej usugi wysyajcej komunikaty


Usuga WakefulIntentService jest wietn klas abstrakcyjn. Chcemy jednak pj krok dalej
i sprawi, eby klasa ta bya rwnie rozszerzalna jak usuga IntentService na listingu 14.14
oraz eby posiadaa wszystkie funkcje tej usugi, ale dodatkowo miaa nastpujce zalety:
1. Uzyskiwanie i zwalnianie blokady przechodzenia w stan zatrzymania (podobnie jak
w przypadku standardowej klasy WakefulIntentService).

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

473

2. Przekazanie oryginalnej intencji (ktra bya pierwotnie przekazana odbiorcy


komunikatw) przesonitej metodzie onHandleIntent. Pozwalaoby nam to
w duym stopniu ukry dostawc komunikatw.
3. Przetworzenie ponownie uruchomionej usugi.
4. Ujednolicony sposb przetwarzania blokady przechodzenia w stan zatrzymania
w przypadku wielu odbiorcw komunikatw oraz wielu usug w tym samym procesie
Nazwiemy t abstrakcyjn klas ALongRunningNonStickyBroadcastService. Jak sama nazwa
wskazuje, chcemy, aby ta usuga pozwalaa na przetwarzanie duej trwajcych operacji. Bdzie
ona rwnie specyficznie dostosowana do odbiorcw komunikatw. Usuga ta bdzie take
nietrwaa (omwimy t koncepcj pniej, w skrcie jednak pojcie to oznacza, e system nie
uruchomi usugi, jeli nie bdzie adnego komunikatu w kolejce). Usuga ta rozszerzy klas
IntentService, aby uzyska jej wasnoci, a take przesoni metod onHandleIntent.
Poczywszy powysze wymagania, kod abstrakcyjnej usugi ALongRunningNonStickyBroadcast
Service przybierze wygld zaprezentowany na listingu 14.18.
Listing 14.18. Koncepcja abstrakcyjnej, dugoterminowej usugi
public abstract class ALongRunningNonStickyBroadcastService
extends IntentService
{
...inne szczegy implementacji
protected abstract void
handleBroadcastIntent(Intent broadcastIntent);
...inne szczegy implementacji
}

Szczegy implementacyjne dotyczce tej usugi s do zaawansowane i zajmiemy si nimi


w dalszej czci rozdziau, najpierw musimy jednak wyjani, dlaczego wybralimy taki rodzaj
usugi. Chcemy najpierw udowodni uyteczno i prostot tego rozwizania.
Po utworzeniu tej abstrakcyjnej klasy mona pozmienia przykadowy kod usugi MyService
z listingu 14.16 i uzyska kod przedstawiony na listingu 14.19.
Listing 14.19. Przykadowe zastosowanie dugoterminowej usugi
public class MyService extends ALongRunningNonStickyBroadcastService
{
protected abstract void handleBroadcastIntent(Intent broadcastIntent)
{
Utils.logThreadSignature("MyService");

//tutaj s przeprowadzane operacje


//i nastpuje powrt
}
}

Jak wida, moemy rozszerzy t now, dugoterminow klas usugi (podobnie jak klasy
IntentService oraz WakefulIntentService) oraz przesoni pojedyncz metod, a take nic
nie zmienia lub wprowadza bardzo niewielkie zmiany w odbiorcy komunikatw. Wszystkie
zadania zostan wykonane w wtku roboczym (dziki klasie IntentService) bez obawy o zablokowanie gwnego wtku.

474 Android 3. Tworzenie aplikacji


Listing 14.19 stanowi prosty przykad demonstrujcy omawian koncepcj. Przejdmy teraz do
bardziej zoonej implementacji, uwzgldniajcej dugoterminow usug, ktra dziaa przez 60
sekund w odpowiedzi na nadawany komunikat (chcemy da dowd, e usuga moe dziaa duej
ni 10 sekund bez wywoywania komunikatu ANR). Nazwiemy t usug Test60SecBCRService
(BCR jest skrtem wyraenia Broadcast Receiver, czyli odbiorca komunikatw), a jej implementacja jest widoczna na listingu 14.20.
Listing 14.20. Usuga Test60SecBCRService
public class Test60SecBCRService
extends ALongRunningNonStickyBroadcastService
{
public static String tag = "Test60SecBCRService";

//Wymagane przez klas IntentService do przekazania nazwy klasy.


public Test60SecBCRService(){
super("com.androidbook.service.Test60SecBCRService");
}

/*
* Wykonuje dugoterminowe operacje wewntrz tej metody.
* S one przeprowadzane w oddzielnym wtku.
*/
@Override
protected void handleBroadcastIntent(Intent broadcastIntent)
{
Utils.logThreadSignature(tag);
Log.d(tag,"Wstrzymanie na 60 sekund");
Utils.sleepForInSecs(60);
String message =
broadcastIntent.getStringExtra("message");
Log.d(tag,"Praca zakonczona");
Log.d(tag,message);
}
}

Jak wida, powyszy kod skutecznie symuluje prac wykonywan przez 60 sekund i jednoczenie nie powoduje wywietlenia komunikatu ANR. By moe Czytelnik si teraz zastanawia,
dlaczego nie moemy skompilowa tej klasy i dlaczego nie pokazalimy jeszcze implementacji
abstrakcyjnej klasy dugoterminowej usugi. Warto jednak poczeka i najpierw dokadnie zrozumie wszystkie elementy tego przykadu. W trakcie objaniania zaprezentujemy kod implementacji wszystkich niezbdnych klas. Poza tym w dalszej czci rozdziau umiecilimy dokadne
informacje dotyczce kompilacji tego przykadu, a na jego kocu zamiecilimy adres URL, z ktrego moemy pobra cay projekt.

Dugoterminowy odbiorca komunikatw


Po utworzeniu dugoterminowej usugi widocznej na listingu 14.20 niezbdne okae si wprowadzenie moliwoci jej przywoania z poziomu odbiorcy komunikatw.
Pierwszym celem dugoterminowego odbiorcy komunikatw jest przekazanie pracy dugoterminowej usudze. W tym celu odbiorca ten potrzebuje nazwy klasy tej usugi, aby mg j przywoa.

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

475

Drugim celem dugoterminowego odbiorcy jest wprowadzenie blokady przechodzenia w stan


zatrzymania, gdy w ten sposb zapewnimy bezustanne przetwarzanie kodu po powrocie
odbiorcy.
Trzecim celem jest przeniesienie do usugi oryginalnej intencji, przywoanej dla tego odbiorcy.
Dokonamy tego poprzez zdefiniowanie oryginalnej intencji jako typ Parcelable w jej dodatkowej wartoci. Warto ta bdzie nosia nazw original_intent. Nastpnie dugoterminowa
usuga wydobywa warto original_intent i przekazuje j swojej przesonitej metodzie
(zobaczymy to pniej, podczas implementacji dugoterminowej usugi). Dziki takiemu rozwizaniu odnosimy wic wraenie, e dugoterminowa usuga jest istotnie rozszerzeniem odbiorcy komunikatw.
Chocia moemy zaprogramowa kadego dugoterminowego odbiorc, eby za kadym razem wykonywa te czynnoci, bdzie lepiej, jeli je wyodrbnimy i utworzymy klas bazow.
Taki abstrakcyjny, dugoterminowy odbiorca bdzie wykorzystywa wtedy pochodn klas do
zapewnienia nazwy dugoterminowej usugi (ang. Long-Running Service LRS) poprzez abstrakcyjn metod, nazwan getLRSClass().
Zanim przejdziemy do implementacji tej abstrakcyjnej klasy, musimy przez chwil zastanowi
si nad obranym kierunkiem w przypadku blokad przechodzenia w stan zatrzymania. Blokady
te musz zosta skoordynowane pomidzy odbiorc komunikatw a odpowiadajc mu usug, ktra jest przez nie wywoywana. Chocia sam pomys jest banalny, w rzeczywistej implementacji musimy zatroszczy si o wiele obszarw oraz warunkw, ktre musz zosta spenione, aby ten proces nastpi. Wyodrbnilimy wic blokad za pomoc pojcia nazwanego
LightedGreenRoom. Zaprezentujemy pniej t klas, na razie jednak moemy j traktowa
jako blokad przechodzenia w stan zatrzymania, ktr moemy wcza i wycza.
Po uwzgldnieniu tych wszystkich uwarunkowa kod rdowy implementacji abstrakcyjnej
klasy ALongRunningReceiver bdzie wyglda tak jak przedstawiono na listingu 14.21.
Listing 14.21. Klasa ALongRunningReceiver
public abstract class ALongRunningReceiver
extends BroadcastReceiver
{
private static final String tag = "ALongRunningReceiver";
@Override
public void onReceive(Context context, Intent intent)
{
Log.d(tag,"Odbiorca uruchomiony");

//Klasa LightedGreenRoom wyodrbnia blokad


//przechodzenia w stan zatrzymania w Androidzie,
//aby zatrzymywa urzdzenie czciowo uruchomione.
//W skrcie mamy tu do czynienia z czynnoci rwnowan wczeniu
//lub nabyciu blokady.
LightedGreenRoom.setup(context);
startService(context,intent);
Log.d(tag,"Odbiorca zakonczony");
}
private void startService(Context context, Intent intent)
{
Intent serviceIntent = new Intent(context,getLRSClass());
serviceIntent.putExtra("original_intent", intent);

476 Android 3. Tworzenie aplikacji


context.startService(serviceIntent);
}

/*
* Przesaniamy t metod, aby powrci do
* obiektu klasy nalecego do
* nietrwaej usugi.
*/
public abstract Class getLRSClass();
}

Po utworzeniu tej abstrakcyjnej klasy potrzebny bdzie jeszcze odbiorca cile wsppracujcy
z dugoterminow (60-sekundow) usug, widoczn na listingu 14.16. Kod takiego typu
odbiorcy jest widoczny na listingu 14.22.
Listing 14.22. Przykadowy dugoterminowy odbiorca komunikatw, nazwany Test60SecBCR
public class Test60SecBCR
extends ALongRunningReceiver
{
@Override
public Class getLRSClass()
{
Utils.logThreadSignature("Test60SecBCR");
return Test60SecBCRService.class;
}
}

Podobnie jak abstrakcja usugi z listingw 14.19 i 14.20, kod na listingu 14.22 stanowi klas
abstrakcyjn pozwalajc na utworzenie odbiorcy komunikatw. Klasa ta rozpoczyna usug
wskazywan przez warto zwracan w metodzie getLRSClass().
Do tej pory wyjanilimy, dlaczego potrzebne s nam dwie abstrakcyjne klasy do zaimplementowania dugoterminowych usug przywoywanych przez odbiorc komunikatw, mianowicie:
ALongRunningNonStickyBroadcastService,
ALongRunningReceiver.
Odkadalimy jednak moment zademonstrowania implementacji kadej z tych klas z powodu
ich zoonoci. Nie pokazalimy te jeszcze implementacji wsplnej klasy, LightedGreenRoom,
wykorzystywanej przez obydwie wymienione klasy. Dotarlimy w kocu do miejsca, z ktrego
moemy zaprezentowa kody rdowe tych dwch wspomnianych klas. Rozpoczniemy
jednak od ich wsplnej klasy LightedGreenRoom.

Wyodrbnianie blokady przechodzenia


w stan zatrzymania za pomoc klasy LightedGreenRoom
Jak ju wczeniej wspomnielimy, gwnym zadaniem abstrakcyjnej klasy LightedGreenRoom
jest uproszczenie interakcji z blokad przechodzenia w stan zatrzymania. Blokada ta z kolei pozwala na przetrzymywanie urzdzenia w stanie aktywnoci. Na listingu 14.23 pokazujemy,
w jaki sposb zgodnie z zestawem SDK jest zazwyczaj wykorzystywana typowa blokada
przechodzenia w stan zatrzymania.

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

477

Listing 14.23. Interfejs API blokady przechodzenia w stan zatrzymania


//Uzyskuje dostp do usugi menedera zasilania
PowerManager pm =
(PowerManager)inCtx.getSystemService(Context.POWER_SERVICE);

//Kontaktuje si z blokad
PowerManager.WakeLock wl =
pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);

//Uzyskuje blokad
wl.acquire();

//Wykonuje jakie operacje


//W trakcie wykonywania operacji urzdzenie bdzie czciowo wczone
//Zwalnia blokad
wl.release();

Za pomoc tego rodzaju oddziaywania odbiorca komunikatw powinien mie moliwo uzyskania blokady, a po zakoczeniu dziaania dugoterminowej usugi blokada ta musi zosta
zwolniona. Jednak nie istnieje skuteczna metoda przekazania usudze tej zmiennej blokady
przechodzenia w stan zatrzymania z poziomu odbiorcy komunikatw. Jedynym rozwizaniem,
dziki ktremu usuga ta uzyska informacj o obecnoci blokady budzenia, jest uycie zmiennej
statycznej lub zmiennej na poziomie aplikacji.
Kolejn trudnoci pojawiajc si podczas uzyskiwania i zwalniania blokady przechodzenia
w stan zatrzymania jest zliczanie referencji. Jeli wic odbiorca komunikatw zostanie kilkakrotnie przywoany, to jeeli te wywoania bd si na siebie nakaday, pojawi si jednoczenie
kilka wywoa dajcych naoenia blokady. Analogicznie pojawi si te kilka wywoa dajcych zwolnienia tej blokady. Jeeli liczba wywoa nakadania i zwalniania blokady nie
bdzie taka sama, skoczy si na tym, e w najgorszym wypadku urzdzenie bdzie wczone
o wiele duej, ni potrzeba. Ponadto, jeeli usuga nie bdzie ju nam potrzebna i bdzie uruchomiony proces oczyszczania pamici, to w przypadku rnicy w liczbie blokad nastpi wywietlenie wyjtku wykonawczego w oknie LogCat.
Problemy te zachciy nas do moliwie jak najskuteczniejszego wyodrbnienia blokady przechodzenia w stan zatrzymania w celu zapewnienia jej waciwego dziaania.
Skoro ju jestemy wiadomi problemw oraz potrzeby posiadania takich blokad,
zachcamy do eksperymentowania z klas LightedGreenRoom oraz zastpowania jej
inn klas, jeli takie rozwizanie okae si prostsze. Powysze zadanie ma nas upewni,
e klasa LightedGreenRoom nie jest magicznym bytem i w swej istocie jest bardzo
nieskomplikowana.
Wyjanimy teraz, co nas skonio do uznania klasy LightedGreenRoom za odpowiedni
blokad przechodzenia w stan zatrzymania.

478 Android 3. Tworzenie aplikacji

Owietlony zielony pokj1


Rozpocznijmy od zielonego pokoju, ktry moe by odwiedzany przez goci. Na pocztku
w pokoju jest ciemno, a pierwszy go, ktry wejdzie, zapala wiato. Kolejni gocie nie maj ju
wpywu na wiato, jeli zostao zapalone. Ostatni go przy wyjciu zgasi wiato. Pokj jest nazywany zielonym, poniewa w wydajny sposb gospodaruje energi. Metody pozwalajce na
wchodzenie i wychodzenie z pokoju musz by zsynchronizowane, aby kontrolowa swoje stany,
poniewa mog by wywoywane pomidzy wieloma wtkami.
Czym wic jest owietlony zielony pokj? W przeciwiestwie do zwykego zielonego pokoju,
w ktrym pocztkowo wiata s zgaszone, w owietlonym pokoju od pocztku jest jasno,
jeszcze przed przybyciem pierwszego gocia. Moemy zaoy, e gdyby wiato byo zgaszone,
odwiedzajca osoba nie mogaby odnale drogi do pokoju. Jest to zwizane z faktem, e
w wyczonym urzdzeniu adna usuga nie moe zosta uruchomiona. Cigle jednak ostatni
wychodzcy go bdzie gasi wiato przy wyjciu. Taki mechanizm okazuje si przydatny dla
odbiorcy komunikatw, poniewa musi najpierw zapali wiato, a nastpnie przenie usug.
Uruchomienie usugi jest rwnowane wejciu pierwszego gocia. Zatrzymanie usugi to wyjcie gocia z pomieszczenia. Zwrmy uwag, e musimy rozrnia pojcia utworzenia usugi
od jej uruchomienia. Utworzenie i zamknicie usugi wystpuje tylko raz w jej cyklu ycia,
natomiast jej uruchamianie i zatrzymywanie moe wystpowa wielokrotnie.
Moe si pojawi, i zazwyczaj si pojawia, opnienie pomidzy konfiguracj blokady przechodzenia urzdzenia w stan zatrzymania (owietlonego zielonego pokoju) w odbiorcy a uruchomieniem usugi, a dokadniej wywoaniem metody onStartCommand (wkroczeniem pierwszego gocia do pokoju).
Poniewa obiekt wakelock jest zliczany referencyjnie, w przypadku gdy usuga zostanie zamknita z powodu ograniczenia pamici, chcielibymy jawnie zwolni blokady. Gdybymy chcieli
wykorzysta ten sam owietlony zielony pokj do obsugi wielu usug, moemy zechcie wyledzi ostatni usug, ktra zostanie zamknita, i zwolni blokady tu po jej zakoczeniu.
W tym celu utworzymy klienta. Kada usuga bdzie rejestrowana z owietlonym zielonym pokojem jako jej klient, dziki czemu bdzie dziaaa metoda jej zamykania.
A przede wszystkim musimy ledzi wejcia i wyjcia kadej metody startService.

Implementacja owietlonego zielonego pokoju


Po poczeniu wszystkich koncepcji wymienionych w poprzednim punkcie implementacja
owietlonego zielonego pokoju bdzie wygldaa jak na listingu 14.24. Pragniemy zauway, e
spisywaa si ona dobrze w naszym ograniczonym rodowisku testowym. Warto jednak troch
poeksperymentowa i dostosowa do wasnych potrzeb, poniewa nie potrafimy przewidzie
kadej okolicznoci, jaka moe wystpi w rodowisku projektowym Czytelnika (inaczej
mwic, uznajmy ten przykad za eksperymentalny).

Tytu podrozdziau odnosi si do nazwy omawianej klasy, ktra w dosownym tumaczeniu oznacza
wanie owietlony zielony pokj przyp. tum.

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

479

Listing 14.24. Implementacja owietlonego zielonego pomieszczenia


public class LightedGreenRoom
{

//Znacznik debugowania
private static String tag="LightedGreenRoom";

//Zlicza liczb goci, aby wiedzie, ktry jest ostatni.


//Podczas zamykania usugi zeruje licznik w celu wyczyszczenia pokoju.
private int count;

//Potrzebna do utworzenia blokady budzenia


private Context ctx = null;

//Nasz przecznik
PowerManager.WakeLock wl = null;

//Obsuga wielu klientw


private int clientCount = 0;

/*
* Oczekujemy, e bdzie to klasa singletonowa.
* Potencjalnie mona zrobi prywatny
* konstruktor.
*/
public LightedGreenRoom(Context inCtx)
{
ctx = inCtx;
wl = this.createWakeLock(inCtx);
}

/*
* Konfigurowanie zielonego pokoju za pomoc statycznej metody.
* Musi by wywoana przed wywoaniem innych metod.
* Jej zadania:
* 1. Tworzenie wystpienia obiektu.
* 2. Wprowadzenie blokady wczajcej wiata.
* Zaoenie:
* Nie musi by synchronizowana,
* poniewa bdzie wywoywana z gwnego wtku.
* (Moe by bdne. Naley to sprawdzi!!)
*/
private static LightedGreenRoom s_self = null;
public static void setup(Context inCtx)
{
if (s_self == null)
{
Log.d(LightedGreenRoom.tag,"Tworzenie zielonego pokoju i oswietlanie go");
s_self = new LightedGreenRoom(inCtx);
s_self.turnOnLights();
}
}

480 Android 3. Tworzenie aplikacji


public static boolean isSetup()
{
return (s_self != null) ? true: false;
}

/*
* Spodziewamy si, e metody wchodzenia i wychodzenia
* bd wsplnie wywoywane.
*
* Przy wejciu licznik jest zwikszany.
*
* Nie wczamy ani nie wyczamy wiate,
* poniewa s one ju zapalone.
*
* Zwikszamy warto licznika tylko po to,
* aby wiedzie, kiedy wyjdzie ostatni go.
*
* Jest to synchronizowana metoda, poniewa
* bdzie wchodzio i wychodzio wiele wtkw.
*
*/
synchronized public int enter()
{
count++;
Log.d(tag,"Nowy gosc: licznik:" + count);
return count;
}

/*
* Spodziewamy si, e metody wchodzenia i wychodzenia
* bd wsplnie wywoywane.
*
* Przy wyjciu zmniejszamy licznik.
*
* Jeeli licznik osignie warto 0, gasimy wiata.
*
* Jest to metoda synchroniczna, poniewa
* wiele wtkw bdzie wchodzio i wychodzio.
*
*/
synchronized public int leave()
{
Log.d(tag,"Opuszczanie pokoju: licznik w momencie wywolania:" + count);

//Jeeli warto licznika ju wynosi 0,


//po prostu wychodzimy.
if (count == 0)
{
Log.w(tag,"Wartosc licznika wynosi zero.");
return count;
}
count--;
if (count == 0)

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

//Ostatni go
//gasi wiata.
turnOffLights();
}
return count;
}
synchronized public int getCount()
{
return count;
}

/*
* Wprowadzamy blokad, aby zapali wiata.
* Od innych synchronizowanych metod zaley, czy zostanie ona
* wywoana we waciwym momencie.
*/
private void turnOnLights()
{
Log.d(tag, "Zapalanie swiatla. Licznik:" + count);
this.wl.acquire();
}

/*
* Zwalnia blokad, aby zgasi wiata.
* Od innych synchronizowanych metod zaley, czy zostanie ona
* wywoana we waciwym momencie.
*/
private void turnOffLights()
{
if (this.wl.isHeld())
{
Log.d(tag,"Zwalnianie blokady. Nie ma juz gosci");
this.wl.release();
}
}

/*
* Standardowy kod sucy do utworzenia czciowej blokady budzenia.
*/
private PowerManager.WakeLock createWakeLock(Context inCtx)
{
PowerManager pm =
(PowerManager)inCtx.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock
(PowerManager.PARTIAL_WAKE_LOCK, tag);
return wl;
}
private int registerClient()
{
Utils.logThreadSignature(tag);
this.clientCount++;
Log.d(tag,"rejestrowanie nowego klienta:licznik:" + clientCount);

481

482 Android 3. Tworzenie aplikacji


return clientCount;
}
private int unRegisterClient()
{
Utils.logThreadSignature(tag);
Log.d(tag,"wyrejestrowanie nowego klienta:licznik:" + clientCount);
if (clientCount == 0)
{
Log.w(tag,"Brak klientow do wyrejestrowania.");
return 0;
}

//Warto clientCount nie jest rwna 0.


clientCount--;
if (clientCount == 0)
{
emptyTheRoom();
}
return clientCount;
}
synchronized public void emptyTheRoom()
{
Log.d(tag, "Wywoluje do wyczyszczenia pokoju");
count = 0;
this.turnOffLights();
}

//*************************************************
//* Statyczni czonkowie: same metody pomocnicze
//* Delegowanie do ukrytego obiektu singletonowego
//*************************************************
public static int s_enter()
{
assertSetup();
return s_self.enter();
}
public static int s_leave()
{
assertSetup();
return s_self.leave();
}

//Nie wywoujmy tej metody bezporednio,


//prawdopodobnie stanie si przestarzaa.
//Zamiast tego wywoujmy klienckie metody rejestrowania/wyrejestrowania.
public static void ds_emptyTheRoom()
{
assertSetup();
s_self.emptyTheRoom();
return;
}
public static void s_registerClient()
{
assertSetup();
s_self.registerClient();
return;
}

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

483

public static void s_unRegisterClient()


{
assertSetup();
s_self.unRegisterClient();
return;
}
private static void assertSetup()
{
if (LightedGreenRoom.s_self == null)
{
Log.w(LightedGreenRoom.tag,"Musimy najpierw wywolac konfigurator");
throw new RuntimeException("Musimy najpierw skonfigurowac obiekt GreenRoom");
}
}
}

Rozsdnym rozwizaniem w celu umoliwiania komunikacji pomidzy odbiorc komunikatw


i usug jest zastosowanie zmiennej statycznej. W naszym przykadowym kodzie obiekt wakelock
nie jest statyczny, ale statyczne jest cae wystpienie klasy LightedGreenRoom, jednak kada inna
zmienna umieszczona wewntrz klasy LightedGreenRoom pozostaje lokalna i niestatyczna.
Kada publiczna metoda klasy LightedGreenRoom dla naszej wygody staje si rwnie statyczna.
Moemy zamiast tego pozby si statycznych metod i bezporednio wywoa pojedyncze wystpienie klasy LightedGreenRoom.

Implementacja dugoterminowej usugi


Po zaimplementowaniu klasy LightedGreenRoom jestemy niemal gotowi do zaprezentowania
abstrakcyjnej, dugoterminowej usugi. Musimy jednak najpierw objani zagadnienia czasu
ycia usugi oraz jego zwizek z implementacj metody onStartCommand. To wanie ta metoda
ostatecznie obsuguje rozpoczcie wtku roboczego i semantyk usugi.
Wiemy, e odbiorca komunikatw przywouje usug za pomoc metody startService, w wyniku ktrej z kolei zostaje wywoana metoda onStartCommand usugi. Czas ycia usugi jest
zaleny od wartoci zwracanych przez t metod.
Aby zrozumie, co si dzieje we wntrzu tej metody, musimy zna szczegowe informacje
dotyczce natury usug lokalnych. W rozdziale 11. podalimy podstawowe informacje na temat
usug lokalnych, obecnie musimy przyjrze si im nieco uwaniej.
Aby uruchomi usug, musi ona zosta najpierw utworzona, a nastpnie naley wywoa jej
metod onStartCommand. Android posiada wystarczajco wiele rezerw, aby przechowywa ten
proces w pamici, dziki czemu usuga moe obsugiwa przychodzce dania klienta.
Istnieje rnica pomidzy procesem usugi pozostajcej w pamici a uruchomionej. Usuga zostaje
uruchomiona jedynie w odpowiedzi na metod startService, ktra z kolei wywouje metod
onStartCommand. To, e metoda nie jest wykonywana, wcale nie oznacza, e proces usugi nie
znajduje si w pamici. Czasami programici odnosz si do niej jak do uruchomionej usugi,
nawet jeeli ona jedynie istnieje w pamici, pochania zasoby, ale nie wykonuje adnych innych zada. Zazwyczaj to ma si na myli, gdy si twierdzi, e system przechowuje uruchomion usug.

484 Android 3. Tworzenie aplikacji


W rzeczywistoci w wyniku wywoania metody startService zostanie wywoana metoda
onStartCommand i jeli zajmie to wicej czasu ni 5 10 sekund, moe to spowodowa wystpienie ostrzeenia ANR i zamknicie procesu przechowujcego usug. Usuga nie moe bez
wtku roboczego dziaa duej ni 10 sekund. Powinnimy zatem odrnia usugi dostpne
od usug uruchomionych.
Android utrzymuje usug dostpn w pamici, jeli jest to tylko moliwe. Jednak w przypadku
rygorystycznych ogranicze pamici system moe zadecydowa o odzyskaniu procesu i wywoaniu metody onDestroy() wobec usugi. Dzieje si tak wtedy, gdy nie s wywoywane metody
onCreate(), onStart() lub onDestroy() usugi.
W przeciwiestwie jednak do zamknitej aktywnoci, usuga moe zosta ponownie uruchomiona, gdy znw zasoby stan si dostpne oraz jeli w kolejce znajduj si oczekujce intencje startService. System uruchomi usug ponownie, a nastpnie za pomoc metody
onStartCommand() dostarczy do niej intencj. Oczywicie, w momencie przywracania usugi
zostanie wywoana metoda onCreate(). Poniewa usugi s bez przerwy ponownie uruchamiane, rozsdnie jest uzna, e w przeciwiestwie do aktywnoci i innych skadnikw, s one
fundamentalnie trwaymi skadnikami.

Szczegowe informacje na temat usugi nietrwaej


Czym wic jest nietrwaa usuga?
Zastanwmy si nad sytuacj, w ktrej usuga nie jest automatycznie uruchamiana ponownie.
Po wywoaniu metody startService przez klienta zostaje utworzona usuga, a nastpnie jest
wywoana metoda onStartCommand. Usuga taka nie zostanie ponownie uruchomiona, jeeli
klient jawnie wywoa metod stopService.
Metoda ta, w zalenoci od liczby stale podczonych klientw, moe przenosi usug w stan
zatrzymania, w czasie ktrego zostaje wywoana metoda onDestroy usugi, i cykl ycia usugi
dobiega koca. Po zatrzymaniu w taki sposb usugi przez jej ostatniego klienta nie zostanie ona
ponownie uruchomiona.
Protok ten wietnie sprawdza si w przypadku, gdy wszystko przebiega zgodnie z planem, gdy
metody rozpoczynania i zatrzymywania s wywoywane i przetwarzane we waciwej kolejnoci,
bez adnych uchybie.
Zanim wydano wersj 2.0 Androida, bardzo czsto urzdzenia przechowyway w pamici mnstwo uruchomionych usug, ktre pochaniay zasoby, chocia nie miay adnych zada do wykonania. Oznaczao to, e system przywraca je pomimo braku komunikatw w kolejce. Tak
si mogo dzia w przypadku braku wywoania usugi stopService albo z powodu wystpienia wyjtku, albo poniewa proces zosta zamknity pomidzy wywoaniami metod
onStartCommand i stopService.
W wersji 2.0 Androida wprowadzono rozwizanie, dziki ktremu moemy zadecydowa, e
przy braku oczekujcych intencji usugi nie bd uruchamiane ponownie. Jest to dobre rozwizanie, poniewa kady obiekt, ktry uruchomi usug, na przykad meneder alarmu, wywoa
j ponownie. Dokonujemy tego poprzez zwrot flagi nietrwaoci (Service.START_NOT_STICKY)
z metody onStartCommand.
Jednak nietrwaa usuga wcale nie jest taka nietrwaa. Pamitajmy, e nawet jeli oznaczymy
usug jako nietrwa, w przypadku obecnoci oczekujcych intencji system uruchomi j ponownie. Flagi okazuj si przydatne jedynie wtedy, gdy intencje oczekujce s niedostpne.

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

485

Informacje dotyczce trwaej usugi


Zatem co to znaczy, e usuga jest trwaa?
Flaga trwaoci (Service.START_STICKY) oznacza, e Android powinien ponownie uruchomi
usug, nawet jeli nie ma oczekujcych intencji. Podczas ponownego uruchamiania usugi
system wywouje metody onCreate i onStartCommand, zawierajce argument w postaci pustej intencji. W razie potrzeby usuga uzyska w ten sposb moliwo wywoania metody
stopSelf, jeli takie rozwizanie okae si waciwe. Wynika z tego wniosek, e trwaa usuga
podczas ponownego uruchamiania musi przetwarza puste intencje.

Odmiana nietrwaej usugi ponownie dostarczane intencje


Usugi lokalne pracuj przede wszystkim zgodnie z wzorcem, wedle ktrego metody onStart
i stopSelf s wywoywane parami. Klient wywouje metod onStart; usuga, po zakoczeniu zadania, sama wywouje metod stopSelf. Widzimy to wyranie w implementacji klasy
IntentService, omawianej przy okazji listingu 14.15.
Jeeli usuga wymaga, powiedzmy, 30 minut na wypenienie zadania, przez ten czas nie wywoa
metody stopSelf. W midzyczasie jest ona odzyskiwana. Jeeli wprowadzimy flag nietrwaoci, usuga nie zostanie przywrcona i metoda stopSelf nigdy nie zostanie wywoana.
W wielu przypadkach taka sytuacja nikomu nie przeszkadza. Jeeli jednak chcemy upewni si,
e pojawi si wywoania obydwu wspomnianych metod, Android nie powinien pozbywa si
z kolejki zdarzenia start, dopki nie zostanie wywoana metoda stopSelf. W ten sposb, gdy
usuga zostanie odzyskana, bdzie zawsze istniao oczekujce zdarzenie, chyba e zostanie wywoana metoda stopSelf. Jest to tak zwany tryb ponownego dostarczania (redeliver) i moemy
go zdefiniowa w metodzie onStartCommand za pomoc flagi Service.START_REDELIVER.

Definiowanie flag usugi w metodzie onStartCommand


Co ciekawe, trwao usugi jest powizana z metod onStartCommand, a nie onCreate. Jest to
nieco dziwne, poniewa dotychczas mwilimy o usugach znajdujcych si w trybach sticky,
nonsticky lub redeliver, jakby byy atrybutami umieszczanymi na poziomie usugi. Jednak
takie okrelanie natury usugi jest oparte na wartoci otrzymywanej z metody onStartCommand.
Jaki jest w tym cel? Sami si zastanawiamy, poniewa w przypadku tego samego wystpienia danej
usugi metoda onStartCommand jest wywoywana wielokrotnie, osobno dla kadej metody
startService. Co zatem stanie si w przypadku, gdy metoda zwraca rne flagi wskazujce
na rnice si tryby dziaania usugi? Najprawdopodobniej ostatnia zwracana warto jest
decydujca.

Wybieranie odpowiedniego trybu usugi


Skoro znamy ju rne tryby zachowania usugi, ktry z nich bdzie nadawa si do dugoterminowego odbiorcy komunikatw? Uwaamy, e powinna wystarczy prosta, nietrwaa usuga,
ktra zostanie zatrzymana w przypadku braku oczekujcych komunikatw w kolejce. Trudno
nam sobie wyobrazi zastosowanie trwaych, dugoterminowych odbiorcw komunikatw,
zwaszcza jeli chcemy wprowadzi obiekt IntentService, zatrzymujcy usug, jeli nie bdzie adnych oczekujcych intencji.

486 Android 3. Tworzenie aplikacji


Rezultat ujrzymy w implementacji naszej dugoterminowej, abstrakcyjnej usugi, zamieszczonej
na listingu 14.19, w ktrej otrzymalimy flag nietrwaoci.

Kontrolowanie blokady przechodzenia


w stan zatrzymania z dwch miejsc jednoczenie
Zanim zaprezentujemy kod rdowy dugoterminowej usugi, zastanwmy si nad zadaniami
usugi w zakresie utrzymywania urzdzenia w stanie aktywnoci.
Po uruchomieniu kodu usugi naley zaoy czciow blokad przechodzenia w stan zatrzymania. W tym celu na etapie generowania usugi musimy wprowadzi t blokad poprzez utworzenie klasy LightedGreenRoom. Stwierdzimy, e dokonuje tego odbiorca komunikatw, i to
bdzie prawda. Jednak usuga moe zosta uruchomiona sama z siebie, co spowodowaoby
ominicie etapu budowania owietlonego pokoju. Musimy wic w obydwu tych miejscach
kontrolowa blokad przechodzenia w stan zatrzymania.
Kod dugoterminowego odbiorcy komunikatw z listingu 14.18 uruchamia blokad budzenia
za pomoc metody LightedGreenRoom.setup(). Tak sam czynno wykonamy w wywoaniu metody tworzcej usug.
Oprcz konfigurowania owietlonego zielonego pokoju nasza usuga musi zosta zarejestrowana jako jego klient. Pozwoli to na wyczyszczenie pamici po zamkniciu usugi za pomoc
metody onDestroy().

Implementacja dugoterminowej usugi


Skoro wiemy ju co nieco na temat klasy IntentService, flag trybu usugi oraz owietlonego
zielonego pokoju, jestemy gotowi przyjrze si dugoterminowej usudze, zamieszczonej na
listingu 14.25.
Listing 14.25. Dugoterminowa usuga
public abstract class ALongRunningNonStickyBroadcastService
extends IntentService
{
public static String tag = "ALongRunningBroadcastService";
protected abstract void
handleBroadcastIntent(Intent broadcastIntent);
public ALongRunningNonStickyBroadcastService(String name){
super(name);
}

/*
* Metoda ta moe zosta wywoana w dwch przypadkach:
* 1. Gdy odbiorca komunikatw dostarcza metod startService.
* 2. Gdy Android ponownie j uruchamia z powodu oczekujcych intencji.
*
* W pierwszym przypadku odbiorca komunikatw zdy
* ju skonfigurowa owietlony zielony pokj.
*
* W drugim przypadku musimy zrobi to samo.
*/

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

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

//Konfiguruje zielony pokj.


//Konfigurator ten mona wywoywa wiele razy.
LightedGreenRoom.setup(this.getApplicationContext());

//Istnieje spore prawdopodobiestwo, e istnieje wicej uruchomionych


//usug tego typu.
//Znajomo ich liczby pozwoli nam usun blokady
//w trakcie wywoywania metody onDestroy.
LightedGreenRoom.s_registerClient();
}
@Override
public int onStartCommand(Intent intent, int flag, int startId)
{

//Wywouje metod onStart klasy IntentService.


super.onStart(intent, startId);

//Informuje zielony pokj o obecnoci gocia.


LightedGreenRoom.s_enter();

//Zaznaczamy usug jako nietrwa.


//Znaczenie: usuga nie jest ponownie uruchamiana, jeli nie ma
//oczekujcych intencji.
return Service.START_NOT_STICKY;
}

/*
* Zwrmy uwag, e wywoanie tej metody przebiega
* w wtku drugoplanowym, skonfigurowanym przez klas IntentService.
*
* Przesaniamy t metod z poziomu klasy IntentService.
* Odczytujemy oryginaln, nadawan intencj.
* Wywoujemy pochodn klas, zajmujc si intencj nadawanego komunikatu.
* Na kocu owietlony zielony pokj zostaje poinformowany, e go wychodzi.
* W przypadku ostatniego gocia blokada
* zostanie zwolniona.
*/
@Override
final protected void onHandleIntent(Intent intent)
{
try
{
Intent broadcastIntent
= intent.getParcelableExtra("original_intent");
handleBroadcastIntent(broadcastIntent);
}
finally
{
LightedGreenRoom.s_leave();
}

487

488 Android 3. Tworzenie aplikacji


}

/*
* Jeeli Android odzyska proces,
* metoda ta zwolni blokad
* niezalenie od liczby obecnych goci.
*/
@Override
public void onDestroy() {
super.onDestroy();
LightedGreenRoom.s_unRegisterClient();
}
}

Wida wyranie, e rozszerzamy tutaj klas IntentService i wykorzystujemy wszystkie zalety


wtku roboczego, konfigurowanego przez t klas. Dodatkowo klasa ta staje si jeszcze bardziej
wyspecjalizowana, dziki czemu zostaje skonfigurowana jako nietrwaa usuga. Z perspektywy
programisty gwn metod, na jakiej naley si skupi, jest handleBroadcastIntent().

Testowanie dugoterminowych usug


Aby sprawdzi ten kod w akcji, musimy doda do projektu nastpujce pliki:
LightedGreenRoom.java (listing 14.24),
ALongRunningNonStickyBroadcastService.java (listing 14.25),
ALongRunningReceiver.java (listing 14.21),
Test60SecBCR.java (listing 14.22),
Test60SecBCRService.java (listing 14.20),
zaktualizowany plik manifest, zawierajcy 60-sekundowego odbiorc oraz usug
(listing 14.14).
Kody rdowe wymienionych plikw znajdziemy we wczeniejszej czci rozdziau, obecnie
natomiast zapoznamy si z dodatkowymi wpisami pliku manifestu, ktre znajdziemy na listingu 14.26.
Listing 14.26. Definicja dugoterminowych odbiorcy i usugi
<manifest>

<application.>
<receiver android:name=".Test60SecBCR">
<intent-filter>
<action android:name="com.androidbook.intents.testbc"/>
</intent-filter>
</receiver>
<service android:name=".Test60SecBCRService"/>
</application>
..
<uses-permission android:name="android.permission.WAKE_LOCK"/>
</manifest>

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

489

Zwrmy rwnie uwag, e do uruchomienia tego projektu bd jeszcze potrzebne uprawnienia do korzystania z blokady przechodzenia w stan zatrzymania.

Instrukcje dotyczce kompilowania kodu


W niniejszym rozdziale zawarlimy dwa projekty. Jeden z nich pozwala nam na testowanie odbiorcw komunikatw (nazwijmy go TestBCR), w drugim natomiast badamy samodzielnych
odbiorcw, w tym take dugoterminowych: odbiorc i usug (StandaloneBCR). Obydwa
projekty s skompresowane i dostpne do pobrania; adres URL mona znale w podrozdziale Odnoniki. Proponujemy pobra ten plik ZIP i rozpakowa go, aby przejrze kady
z tych projektw.

Utworzenie projektw za pomoc pliku ZIP


Aby utworzy projekty za pomoc danych umieszczonych w pliku ZIP, naley wykona ponisze czynnoci:
1. Pobierz plik ZIP.
2. Rozpakuj ten plik; powinny si ukaza dwa katalogi gwne, po jednym dla kadego
projektu. Dla kadego z tych projektw:
a) W rodowisku Eclipse wybierz z menu File/Import opcje General/Existing Project
into Workspace.
b) Wybierz ciek w polu Select Root Directory.
c) Wybierz opcj Copy Projects Into workspace.
d) By moe zaistnieje potrzeba wybrania waciwego poziomu interfejsu API,
ju po wstawieniu projektu poprzez wybranie opcji Project Properties/Android
i wybranie waciwej wartoci.
Po skompilowaniu projektw wdraamy je do emulatora. Samodzielny projekt zawiera wycznie odbiorcw i usugi. W projekcie TestBCR zamiecilimy prost aktywno, uruchamiajc
pojedyncz intencj nadawania komunikatu, oddziaujc na odbiorcw uwzgldnionych w projekcie TestBCR, a take na odbiorcw pochodzcych ze wspomnianego, samodzielnego projektu.

Utworzenie projektw za pomoc listingw


Zamiecilimy tutaj list plikw wymaganych do utworzenia kadego projektu oraz informacje
dotyczce sposobu przeksztacenia listingw dostpnych w tym rozdziale w dziaajce projekty.

Pliki projektu TestBCR


Oto pliki, ktre bd nam potrzebne do utworzenia projektu TestBCR:
TestBCRActivity.java (listing 14.5),
TestReceiver.java (listing 14.2),
TestReceiver2.java (listing 14.9),
TestTimeDelayReceiver.java (listing 14.11),
Utils.java (listing 14.3),

490 Android 3. Tworzenie aplikacji

/res/layout/main.xml (listing 14.6),


/res/menu/main_menu.xml (listing 14.7),
AndroidManifest.xml (listing 14.8).

Jeeli bd potrzebne jakie inne pliki, moemy zajrze do projektu umieszczonego w pliku .zip
lub sami je utworzy. Mog to by tak proste elementy, jak domylne ikony lub wartoci w postaci cigw znakw. Gdy ju utworzymy odbiorcw, bdziemy musieli zarejestrowa ich w pliku
manifecie, widocznym na listingu 14.8.
Aby z powyszej listy plikw utworzy dziaajcy projekt, naley wykona ponisze czynnoci:
1. Utwrz nowy projekt poprzez wybr opcji File/New Project/Android/Android Project.
2. Wybierz nazw, a nastpnie zaznacz opcj Create New Project in Workspace.
3. Wprowad nazw aplikacji, na przykad TestBCR. Nazwa aplikacji nie ma wikszego
znaczenia, o wiele waniejsza jest nazwa pakietu.
4. Wybierz poziom interfejsu API.
5. Wprowad nazw pakietu com.androidbook.bcr.
6. Wybierz dowoln, minimaln wersj pakietu SDK, np. 3.
7. Wybierz aktywno o nazwie TestBCRActivity i kliknij przycisk Finish.
8. Android utworzy spor liczb plikw zasobw oraz, prawdopodobnie (w zalenoci
od wersji systemu), pojedynczy plik rdowy.
9. Utwrz, zaktualizuj lub usu te pliki na podstawie listingw 14.2 14.11.
10. W przypadku plikw Java podczas kopiowania treci listingw zamie na samej grze
pliku nazw pakietu. Nastpnie wcinij skrt klawiaturowy Ctrl+Shift+O, aby instrukcje
importu zostay automatycznie wprowadzone.
Naley zwrci uwag, e w trakcie przeprowadzania tego procesu bdzie trzeba zmodyfikowa
kod, aby mg zosta skompilowany oraz aby uzupeni brakujce fragmenty. Wszelkie brakujce elementy moemy skopiowa z pliku ZIP zawierajcego projekt.

Pliki projektu zawierajcego samodzielnego odbiorc komunikatw


Poniej wymienilimy pliki bdce czci projektu samodzielnego odbiorcy komunikatw:
ALongRunningNonStickyBroadcastService.java (listing 14.25),
ALongRunningReceiver.java (listing 14.21),
LightedGreenRoom.java (listing 14.24),
NotificationReceiver.java (listing 14.15),
StandaloneReceiver.java (listing 14.13),
Test60SecBCR.java (listing 14.22),
Test60SecBCRService.java (listing 14.20),
Utils.java (listing 14.3),
AndroidManifest.xml (listingi 14.14, 14.26).
Poniewa mamy tu do czynienia z okrojonym projektem, nie bdzie potrzeby tworzenia pliku
ukadu graficznego ani pliku menu. Moemy z powyszej listy plikw utworzy dziaajcy projekt w nastpujcy sposb:

Rozdzia 14 Odbiorcy komunikatw i usugi dugoterminowe

491

1. Utwrz nowy projekt poprzez wybr opcji File/New Project/Android/Android Project.


2. Wybierz nazw, a nastpnie zaznacz opcj Create New Project in Workspace.
3. Wprowad nazw aplikacji, na przykad TestStandaloneBCR. Nazwa aplikacji nie
ma wikszego znaczenia, o wiele waniejsza jest nazwa pakietu.
4. Wybierz poziom interfejsu API.
5. Wprowad nazw pakietu com.androidbook.salbcr.
6. Wybierz dowoln, minimaln wersj pakietu SDK, np. 3.
7. Nie wybieraj adnej aktywnoci.
8. Android stworzy spor liczb plikw zasobw, ale, prawdopodobnie (w zalenoci
od wersji systemu) wszelkie pliki rdowe zostan pominite. Zostanie natomiast
utworzony pakiet Java.
9. Utwrz, zaktualizuj lub usu te pliki, opierajc si na listingach wymienionych na
pocztku punktu.
10. W przypadku plikw Java podczas kopiowania treci listingw zamie na samej
grze pliku nazw pakietu. Nastpnie wcinij skrt klawiaturowy Ctrl+Shift+O,
aby zostay automatycznie wprowadzone instrukcje importu.
Naley zwrci uwag, e w trakcie przeprowadzania tego procesu bdzie trzeba zmodyfikowa
kod, aby go skompilowa oraz aby uzupeni brakujce fragmenty. Wszelkie brakujce elementy
moemy skopiowa z pliku ZIP zawierajcego projekt.

Odnoniki
Poniej prezentujemy pomocne odnoniki dla Czytelnikw, ktrzy zechc poszerzy wiedz
zawart w tym rozdziale o nowe informacje:
http://developer.android.com/reference/android/content/BroadcastReceiver.html
jest to odnonik kierujcy do interfejsu BroadcastReceiver. W niniejszym
rozdziale omwilimy najbardziej podstawowy rodzaj odbiorcy komunikatw.
Pod tym adresem znajdziemy informacje na temat zamawianych komunikatw oraz
nieco wicej informacji na temat ich cyklu ycia.
http://developer.android.com/reference/android/app/Service.html cze to pozwala
uzyska informacje na temat interfejsu Service. Jest to szczeglnie przydatne rdo
podczas pracy z dugoterminowymi usugami.
http://developer.android.com/reference/android/app/NotificationManager.html
odnonik do interfejsu menedera powiadomie.
http://developer.android.com/reference/android/app/Notification.html adres URL
kierujcy nas do interfejsu Notification. Poznamy tu rnorodne opcje dostpne
podczas korzystania z powiadomie, na przykad takie jak widoki treci oraz efekty
dwikowe.
http://developer.android.com/reference/android/widget/RemoteViews.html tu znajdziemy
informacje o interfejsie RemoteViews. Obiekty tego typu s wykorzystywane do tworzenia
wasnych, szczegowych widokw powiadomie.
http://www.androidbook.com/item/3514 znajdziemy tutaj notatki autorw dotyczce
bada nad dugoterminowymi usugami.

492 Android 3. Tworzenie aplikacji

ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw projektw do


pobrania, stworzonych na potrzeby ksiki. Katalog przechowujcy projekty z tego
rozdziau, nosi nazw ProAndroid3_Ch14_TestReceivers.

Podsumowanie
W tym rozdziale omwilimy bardzo wane zagadnienia: odbiorcw komunikatw, powiadomienia, blokady przechodzenia w stan zatrzymania oraz dugoterminowe usugi. Zebralimy tu
rwnie najistotniejsze zagadnienia omwione w rozdziaach 12. i 13.
Zademonstrowalimy podstawy stosowania odbiorcy komunikatw, jego czas ycia oraz sposb
dziaania zarwno w procesie, jak i poza nim. Pokazalimy, jak mona doczy do niego usugi,
dziki czemu czas ycia odbiorcy komunikatw zostaje przeduony. Na koniec poeksperymentowalimy z klas IntentService i pokazalimy, w jaki sposb mona j dalej dostosowywa do
wasnych potrzeb przy okazji uywania dugoterminowych usug.
W rozdziale 15. dowiemy si, w jaki sposb mona skorzysta z menedera alarmw do przywoania odbiorcy komunikatw.

R OZDZIA

15
Badanie menedera alarmw

Za pomoc menedera alarmw mona uruchamia zdarzenia w Androidzie. Zdarzenia te mog wystpowa o okrelonej porze lub w regularnych odstpach czasowych. Rozpoczniemy ten rozdzia od omwienia podstaw menedera alarmw, mianowicie od skonfigurowania prostego alarmu. Nastpnie zwrcimy uwag na sposb
konfiguracji powtarzalnego alarmu, anulowania alarmu, roli intencji oczekujcych
(zwaszcza roli, jak odgrywa ich unikatowo) oraz ustanawiania wielu alarmw
naraz. Po ukoczeniu lektury rozdziau Czytelnik zdy zapozna si z podstawami
menedera alarmw w Androidzie oraz jego praktycznymi zastosowaniami.

Podstawy menedera alarmw


konfiguracja prostego alarmu
Rozpoczniemy od skonfigurowania alarmu uruchamianego o okrelonej porze i wywoujcego odbiorc komunikatw. Po wywoaniu odbiorcy moemy wykorzysta
informacje zawarte w rozdziale 14. do przeprowadzania w nim zarwno krtkich,
jak i duej trwajcych operacji.
Aby wykona wiczenie, naley wykona nastpujce czynnoci:
1. Uzyskaj dostp do menedera alarmu.
2. Skonfiguruj czas uruchomienia alarmu.
3. Utwrz odbiorc, ktry zostanie pniej wywoany.
4. Utwrz oczekujc intencj, aby pniej przekaza j menederowi
alarmu w celu przywoania odbiorcy.
5. Ustaw alarm z wykorzystaniem czasu okrelonego w punkcie 2.
oraz oczekujcej intencji z punktu 4.
6. Obserwuj okno LogCat, w ktrym pojawi si komunikaty wywoanego
odbiorcy, utworzonego w punkcie 3.

494 Android 3. Tworzenie aplikacji

Uzyskanie dostpu do menedera alarmw


Dostp do menedera alarmw uzyskujemy w prosty sposb, uwidoczniony na listingu 15.1.
Listing 15.1. Uzyskanie menedera alarmw
AlarmManager am =
(AlarmManager)
mContext.getSystemService(Context.ALARM_SERVICE);

Na listingu 15.1 zmienna mContext odnosi si do obiektu kontekstu. Jeli na przykad przywoamy ten kod z poziomu menu aktywnoci, zmienna kontekstu jest t aktywnoci.

Definiowanie czasu uruchomienia alarmu


Aby ustawi alarm na okrelon dat i godzin, bdzie nam potrzebna instancja obiektu Calendar.
Na listingu 15.2 widzimy plik Java (bdziemy go potrzebowa do utworzenia projektu), w ktrym wprowadzilimy pewne funkcje wsppracujce z obiektem Calendar.
Listing 15.2. Kilka przydatnych funkcji kalendarza
public class Utils {
public static Calendar getTimeAfterInSecs(int secs) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND,secs);
return cal;
}
public static Calendar getCurrentTime(){
Calendar cal = Calendar.getInstance();
return cal;
}
public static Calendar getTodayAt(int hours){
Calendar today = Calendar.getInstance();
Calendar cal = Calendar.getInstance();
cal.clear();
int year = today.get(Calendar.YEAR);
int month = today.get(Calendar.MONTH);

//reprezentuje dzie miesica


int day = today.get(Calendar.DATE);
cal.set(year,month,day,hours,0,0);
return cal;
}
public static String getDateTimeString(Calendar cal){
SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss");
df.setLenient(false);
String s = df.format(cal.getTime());
return s;
}
}

Rozdzia 15 Badanie menedera alarmw

495

Z zestawu funkcji pokazanych w powyszym kodzie, bdziemy korzysta z funkcji getTime


AfterInSecs(), widocznej na listingu 15.3, aby okreli zdarzenie, ktre odbdzie si 30 sekund
pniej.
Listing 15.3. Uzyskiwanie wystpienia czasu
Calendar cal = Utils.getTimeAfterInSecs(30);

Konfigurowanie odbiorcy dla alarmu


Potrzebny jest nam teraz odbiorca umoliwiajcy odpowied na alarm. Przykadowy prosty
odbiorca zosta zaprezentowany na listingu 15.4.
Listing 15.4. Testowy odbiorca pozwalajcy na analiz komunikatw alarmu
public class TestReceiver extends BroadcastReceiver
{
private static final String tag = "TestReceiver";
@Override
public void onReceive(Context context, Intent intent)
{
Log.d("TestReceiver", "intencja=" + intent);
String message = intent.getStringExtra("message");
Log.d(tag, message);
}
}

Musimy zarejestrowa tego odbiorc w pliku manifecie za pomoc odpowiedniego znacznika


<receiver>, co zostao pokazane na listingu 15.5.
Listing 15.5. Rejestrowanie odbiorcy komunikatw
<receiver android:name=".TestReceiver"/>

Utworzenie oczekujcej intencji dostosowanej do alarmu


Po utworzeniu odbiorcy mona utworzy intencj PendingIntent, ktra jest niezbdna do skonfigurowania alarmu. Rozpoczniemy od utworzenia intencji wywoujcej odbiorc TestReceiver
zdefiniowanego na listingu 15.4. Proces tworzenia tej intencji zosta ukazany na listingu 15.6.
Listing 15.6. Utworzenie intencji wskazujcej odbiorc TestReceiver
Intent intent =
new Intent(mContext, TestReceiver.class);
intent.putExtra("message", "Jednokrotny alarm");

Zmienna mContext stanowi kontekst aktywnoci, z ktrego poziomu bdziemy wywoywa


omawian funkcj. Skorzystalimy bezporednio z klasy TestReceiver (odmienn metod byo
zastosowanie filtru wobec dziaania intencji, co pokazalimy w rozdziale 14.). Mamy rwnie
moliwo utworzenia intencji zawierajcej dodatkowe dane.

496 Android 3. Tworzenie aplikacji


Po utworzeniu standardowej intencji wskazujcej danego odbiorc musimy utworzy intencj
oczekujc, ktr trzeba przekaza menederowi intencji. Na listingu 15.7 widzimy przykadow
intencj oczekujc PendingIntent, utworzon na podstawie intencji z listingu 15.6.
Listing 15.7. Utworzenie intencji oczekujcej
PendingIntent pi =
PendingIntent.getBroadcast(
mContext, //kontekst
1, //identyfikator dania, stosowany do odrnienia tej intencji od innych
intent, //dostarczana intencja
0);

//flagi oczekujcej intencji

Zwrmy uwag, e klasa PendingIntent skonstruuje oczekujc intencj, ktra ma by w jawny


sposb dostosowana do nadawanego komunikatu. Mamy rwnie do dyspozycji nastpujce
odmiany tej intencji:
PendingIntent.getActivity()
PendingIntent.getService()

//przydatna do rozpoczcia aktywnoci


//przydatna do rozpoczcia usugi

W dalszej czci rozdziau omwimy dokadniej argument identyfikatora dania, posiadajcy


w tym przypadku warto 1. W skrcie suy on do rozrniania dwch podobnych do siebie
obiektw intencji.
Flagi oczekujcych intencji waciwie nie posiadaj wpywu na meneder alarmw. Zalecamy,
aby w ogle z nich nie korzysta i ustawia je na warto 0. Flagi te znajduj przewanie zastosowanie w kontroli czasu yciu oczekujcej intencji. Jednak w tym przypadku jest on zarzdzany przez meneder alarmw. Jeeli chcemy na przykad zakoczy intencj oczekujc, dokonujemy tego za pomoc menedera.

Ustawianie alarmu
Po zdefiniowaniu czasu w postaci obiektu Calendar (czas jest wyraany w milisekundach) oraz
oczekujcej intencji wskazujcej na odbiorc moemy ustawi alarm za pomoc metody set()
menedera alarmw, co zostao zaprezentowane na listingu 15.8.
Listing 15.8. Metoda suca do ustanawiania menedera alarmw
alarmManager.set(AlarmManager.RTC_WAKEUP,
calendarObject.getTimeInMillis(),
pendingIntent);

Jeeli wprowadzimy atrybut RTC_WAKEUP, alarm spowoduje wyjcie urzdzenia ze stanu wstrzymania. Moemy zamiast tego ustawienia wprowadzi atrybut RTC, ktry spowoduje dostarczenie
intencji w momencie wyjcia urzdzenia z tego stanu.
Czas zdefiniowany przez drugi argument jest wyraony w sposb waciwy dla obiektu calendar
Object utworzonego na listingu 15.3. Jest to czas liczony w milisekundach, zliczany od 1970
roku. Pokrywa si to rwnie z domylnymi ustawieniami obiektu Java Calendar.
Po wywoaniu tej metody meneder alarmw przywoa odbiorc TestReceiver (listing 15.4)
po upywie 30 sekund.

Rozdzia 15 Badanie menedera alarmw

497

Projekt testowy
Utwrzmy teraz projekt testowy, dziki ktremu dokadniej przeanalizujemy dziaanie dotychczas omwionego kodu.
Na kocu rozdziau zamiecilimy adres URL, z ktrego mona pobra projekty
utworzone na potrzeby ksiki oraz zaimportowa je do rodowiska Eclipse.

Do utworzenia projektu bdziemy potrzebowa nastpujcych plikw:


TestAlarmsDriverActivity.java aktywno suca do ustanawiania alarmw
(listing 15.12).
SendAlarmOnceTester.java jest to gwna klasa, suca do testowania funkcji
jednokrotnego wysania alarmu. Zaprezentujemy rwnie podobne klasy, pozwalajce
Czytelnikowi na przetestowanie innych funkcjonalnoci (listing 15.11).
BaseTester.java baza klasowa umoliwiajca klasom testujcym, takim jak
SendAlarmOnceTester.java, odsyanie wynikw za pomoc interfejsu IReportBack
(listing 15.10).
IReportBack.java ten niewielki pomocniczy interfejs, stworzony dla klasy
BaseTester.java, zbiera komunikaty debugowania i przesya je do aktywnoci
sterujcej (listing 15.9).
TestReceiver.java jest to klasa przywoywana w momencie uruchamiania alarmu.
Zostaa ona zaprezentowana na listingu 15.4.
Utils.java narzdzia pozwalajce na zarzdzanie dat, godzin czy kalendarzem,
zaprezentowane na listingu 15.2.
/res/menu/main_menu.xml plik menu aktywnoci sterujcej (listing 15.13).
/res/layout/main.xml plik ukadu graficznego aktywnoci sterujcej (listing 15.14).
AndroidManifest.xml znany nam doskonale plik manifest, wymagany przez kad
aplikacj pisan dla Androida (listing 15.15).
Zaprezentujemy po kolei kady z wymienionych plikw, poczwszy od klas bazowych, dziki
ktrym skoordynujemy dziaania pomidzy aktywnoci sterujc a rnorodnymi klasami
sterujcymi, pozwalajcymi na analiz poszczeglnych waciwoci alarmu. Pierwsza z klas bazowych, IReportBack, zostaa umieszczona na listingu 15.9.
Listing 15.9. IReportBack.java
//IReportBack.java
package com.androidbook.alarms;

/*
* Interfejs, zazwyczaj implementowany przez aktywno,
* za pomoc ktrego klasa robocza moe przekaza informacje
* na temat zachodzcych zdarze.
*/
public interface IReportBack
{
public void reportBack(String tag, String message);
}

498 Android 3. Tworzenie aplikacji


Jak zostao wspomniane w komentarzach, interfejs ten jest wykorzystywany przez klas testujc
do przekazywania komunikatw aktywnoci sterujcej. Zobaczymy to wyranie podczas omawiania kodw klas podrzdnych, na przykad SendAlarmOnceTester.java (listing 15.11).
Wszystkie klasy testujce, takie jak SendAlarmOnceTester, wywodz si z klasy BaseTester.
Kod rdowy pliku BaseTester.java jest dostpny na listingu 15.10.
Listing 15.10. BaseTester.java
//BaseTester.java
package com.androidbook.alarms;
import android.content.Context;public class BaseTester
{
protected IReportBack mReportTo;
protected Context mContext;
public BaseTester(Context ctx, IReportBack target)
{
mReportTo = target;
mContext = ctx;
}
}

Jest to prosta klasa pomocnicza, zapewniajca dwa elementy wywodzcym si od niej klasom
testujcym, takim jak SendAlarmOnceTester: kontekst, ktry w miar potrzeby bdzie wykorzystywany przez ich metody, oraz aktywno implementujc interfejs IReportBack, dziki
czemu komunikaty bd zapisywane w dzienniku.
Po utworzeniu interfejsu IReportBack oraz SendAlarmOnceTester bdziemy mogli zacz
wprowadzanie kodu klasy SendAlarmOnceTester.java, testujcej wysyanie pojedynczego alarmu
(listing 15.11).
Listing 15.11. Plik klasy pozwalajcej na jednorazowe wysanie alarmu
// SendAlarmOnceTester.java
package com.androidbook.alarms;
import java.util.Calendar;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
public class SendAlarmOnceTester extends BaseTester
{
private static String tag = "SendAlarmOnceTester";
SendAlarmOnceTester(Context ctx, IReportBack target)
{
super(ctx, target);
}

/*
* Alarm moe przywoywa danie nadania komunikatu
* o okrelonej porze.
* Nazwa odbiorcy komunikatu jest jawnie

Rozdzia 15 Badanie menedera alarmw

499

* zdefiniowana w intencji.
*/
public void sendAlarmOnce()
{

//Generuje wystpienie w czasie rwnym 30 sekundom od biecej chwili.


Calendar cal = Utils.getTimeAfterInSecs(30);

//Jeeli chcemy wywoa alarm dzisiaj o godzinie 11.


//Calendar cal = Utils.getTodayAt(11);
//Wywietla w widoku debugowania informacj o fakcie.
//Ustanawiamy alarm na okrelon godzin.
String s = Utils.getDateTimeString(cal);
mReportTo.reportBack(tag, "Ustanawianie alarmu na: " + s);

//Pobiera intencj pozwalajc na przywoanie odbiorcy.


//TestReceiver
Intent intent =
new Intent(mContext, TestReceiver.class);
intent.putExtra("message", "Alarm jednorazowy");
PendingIntent pi =
PendingIntent.getBroadcast(
mContext, //kontekst
1,

//identyfikator dania, pozwalajcy na rozrnianie intencji


//dostarczana intencja
PendingIntent.FLAG_ONE_SHOT); //flagi intencji oczekujcej
intent,

// Tworzy harmonogram alarmu!


AlarmManager am =
(AlarmManager)
mContext.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(),
pi);
}
}

Intencja klasy SendAlarmOnceTester wysya pojedynczy alarm, co powoduje wywoanie odbiorcy komunikatw. Moemy si o tym przekona, przygldajc si metodzie sendAlarmOnce(),
pokazanej na listingu 15.11. Odbiorca TestReceiver, bdcy celem alarmu, zosta przedstawiony na listingu 15.4, zatem kady aspekt tej metody zosta ju wczeniej omwiony. Listing
15.11 stanowi jedynie zoenie opisanych powyej fragmentw kodu.
Przeanalizujmy teraz aktywno sterujc, wywoujc metod sendAlarmOnce(). Jej kod
rdowy prezentujemy na listingu 15.12. Ta gwna aktywno projektu testowego przywouje
obiekty menu, za pomoc ktrych bdziemy sprawdza rne rodzaje alarmw (zarwno ju
przedstawionych, jak i tych, ktre dopiero zostan omwione). Na razie jednak dysponujemy
wycznie fragmentem kodu pozwalajcym na wywoanie elementu menu, dziki ktremu uruchomimy omwiony powyej mechanizm. W dalszej czci rozdziau poznamy fragmenty kodu uruchamiane po wciniciu pozostaych opcji menu.

500 Android 3. Tworzenie aplikacji


Metoda onCreate() klasy TestAlarmsDriverActivity (listing 15.12) tworzy wystpienie klasy
SendAlarmOnceTester, do ktrej zostan przeniesione dziaania menu. Zwrmy uwag, e
wspomniana aktywno przenosi samoistnie siebie sam, jak rwnie zmienne IReportBack
i Context do konstruktora klasy SendAlarmOnceTester. Implementuje ona rwnie interfejs
IReportBack oraz aktualizuje widok debuggera za pomoc przekazanego tekstu (zapisana pogrubion czcionk metoda reportBack na listingu 15.12).
Listing 15.12. Przykadowa aktywno pozwalajca na testowanie rnych ustawie alarmw
// TestAlarmsDriverActivity.java
package com.androidbook.alarms;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;
public class TestAlarmsDriverActivity extends Activity
implements IReportBack
{
public static final String tag="TestAlarmsDriverActivity";
private SendAlarmOnceTester alarmTester = null;

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
alarmTester = new SendAlarmOnceTester(this,this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{

//Wywouje klas nadrzdn w celu doczenia dowolnych menu systemowych


super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();

//z aktywnoci

inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
appendMenuItemText(item);
if (item.getItemId() == R.id.menu_clear)
{
this.emptyText();
return true;
}
if (item.getItemId() == R.id.menu_alarm_once)
{
alarmTester.sendAlarmOnce();
return true;

Rozdzia 15 Badanie menedera alarmw

501

//Pniej dodamy tutaj wicej opcji menu


return true;
}

//Funkcja odziedziczona z interfejsu IReportBack


public void reportBack(String tag, String message)
{
this.appendText(tag + ":" + message);
Log.d(tag,message);
}

//Proste funkcje suce do pracy z widokiem debuggera


//tej aktywnoci
private TextView getTextView() {
return (TextView)this.findViewById(R.id.text1);
}
private void appendMenuItemText(MenuItem menuItem){
String title = menuItem.getTitle().toString();
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + title);
}
private void emptyText(){
TextView tv = getTextView();
tv.setText("");
}
private void appendText(String s){
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + s);
Log.d(tag,s);
}
}

Jak wida, aktywno TestAlarmsDriverActivity reaguje na kilka elementw menu. Zdefiniowany dla niej plik menu.xml zosta zaprezentowany na listingu 15.13. Zamiecilimy w nim
od razu wszystkie dodatkowe scenariusze testowe, ktrymi bdziemy si zajmowa przez reszt
rozdziau. Poniewa obecno tych dodatkowych opcji nie bdzie nam przeszkadza w skompilowaniu projektu, postanowilimy umieci je wszystkie za jednym zamachem.
Listing 15.13. Elementy menu suce do testowania rnorodnych scenariuszy menedera alarmw
<!-- /res/menu/main_menu.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa korzysta z domylnej kategorii. -->


<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/menu_alarm_once"
android:title="Pojedynczy alarm" />
<item android:id="@+id/menu_alarm_repeated"
android:title="Alarm powtarzalny" />
<item android:id="@+id/menu_alarm_cancel"
android:title="Anulowanie alarmw" />
<item android:id="@+id/menu_alarm_multiple"
android:title="Wiele alarmw" />
<item android:id="@+id/menu_alarm_distinct_intents"
android:title="Oddzielne intencje" />

502 Android 3. Tworzenie aplikacji


<item android:id="@+id/menu_alarm_intent_primacy"
android:title="Pierwszestwo intencji" />
<item android:id="@+id/menu_clear"
android:title="Wyczy" />
</group>
</menu>

Listing 15.14 zawiera plik ukadu graficznego obsugujcy aktywno sterujc TestAlarms
DriverActivity (listing 15.12). Plik ten znajduje si w katalogu /res/layout/main.xml.
Listing 15.14. Ukad graficzny aktywnoci TestAlarmsDriverActivity
<?xml version="1.0" encoding="utf-8"?>

<!-- /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>

Na listingu 15.15 umiecilimy plik manifest tego projektu.


Listing 15.15. Plik manifest programu testujcego meneder alarmw
<?xml version="1.0" encoding="utf-8"?>

<!-- AndroidManifest.xml -->


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.alarms"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="Test alarmw">
<activity android:name=".TestAlarmsDriverActivity"
android:label="Test alarmw">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".TestReceiver">
<intent-filter>
<action android:name="com.androidbook.intents.testbc"/>
</intent-filter>
</receiver>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

Rozdzia 15 Badanie menedera alarmw

503

Poza definicj odbiorcy nie s potrzebne inne wpisy w manifecie, eby korzysta z menedera
alarmw. Definicja odbiorcy jest na powyszym listingu zaznaczona pogrubionym drukiem.
Po skompilowaniu i uruchomieniu projektu powinnimy ujrze aktywno i struktur menu
przypominajc interfejs uytkownika na rysunkach 15.1 i 15.2.

Rysunek 15.1. Przykadowa aktywno pozwalajca na przetestowanie menedera alarmw

Na rysunku 15.1 widzimy cz opcji dostpnych w menu. Aby ujrze pozostae elementy menu,
musimy klikn ikon Wicej. Opcje te zostay ukazane na rysunku 15.2.
Jeeli teraz wybierzemy widoczn na rysunku 15.1 opcj Alarm pojedynczy, uruchomimy kod
zawarty w metodzie sendAlarmOnce() (listing 15.11). W tym momencie zostanie zdefiniowany
alarm, ktry uruchomi si po 30 sekundach. Po tym czasie odbiorca TestReceiver zacznie
umieszcza komunikaty w oknie LogCat.

Analiza alternatywnych wersji menedera alarmw


Po wyjanieniu podstawowych zasad dotyczcych konfigurowania alarmu moemy si zaj
kilkoma dodatkowymi scenariuszami, na przykad ustanowieniem powtarzajcego si alarmu
lub anulowaniem alarmw. Przyjrzymy si take nietypowym sytuacjom, na ktre moemy
natrafi podczas pracy z menederem alarmw.

Konfigurowanie powtarzalnego alarmu


Omwilimy ju sposb utworzenia jednorazowego alarmu, czas zatem zastanowi si, jak
moemy uzyska alarm, ktrego wywoywanie moe nastpowa w sposb powtarzalny.

504 Android 3. Tworzenie aplikacji

Rysunek 15.2. Rozszerzone menu naszej przykadowej aktywnoci

Aby zrozumie zasad postpowania w takim przypadku, spjrzmy na kod na listingu 15.16.
Mamy tu do czynienia ze rodowiskiem testowym, zawierajcym metod SendOnceAlarm
Tester(), w ktrym zaimplementowano rwnie metod sendRepeatingAlarm(). W ten
sposb umoliwiono sprawdzanie powtarzalnego alarmu.
Listing 15.16. Konfigurowanie powtarzalnego alarmu
// SendRepeatingAlarmTester.java
package com.androidbook.alarms;
import java.util.Calendar;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
public class SendRepeatingAlarmTester
extends SendAlarmOnceTester
{
private static String tag = "SendRepeatingAlarmTester";
SendRepeatingAlarmTester(Context ctx, IReportBack target)
{
super(ctx, target);
}

/*
* Alarm moe wywoa danie nadawania komunikatu
* o okrelonej porze oraz
* w regularnych odstpach.

Rozdzia 15 Badanie menedera alarmw

505

*
* Wykorzystuje t sam intencj co powyej,
* lecz inny identyfikator dania w celu uniknicia konfliktw
* z wczeniej utworzonym alarmem jednokrotnym.
*
* Wykorzystuje metod getDistinctPendingIntent().
*/
public void sendRepeatingAlarm()
{
Calendar cal = Utils.getTimeAfterInSecs(30);

//Calendar testcal = Utils.getTodayAt(11);


String s = Utils.getDateTimeString(cal);
this.mReportTo.reportBack(tag,
"Ustanowienie powtarzalnego alarmu w odstepach 5 s, poczawszy od: " + s);

//Pobiera intencj wywoujc odbiorc


Intent intent =
new Intent(this.mContext, TestReceiver.class);
intent.putExtra("message", "Alarm powtarzalny");
PendingIntent pi = this.getDistinctPendingIntent(intent, 2);

// Tworzy harmonogram alarmu!


AlarmManager am =
(AlarmManager)
this.mContext.getSystemService(Context.ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(),
5*1000, //5 sekund
pi);
}
protected PendingIntent getDistinctPendingIntent
(Intent intent, int requestId)
{
PendingIntent pi =
PendingIntent.getBroadcast(
mContext, //kontekst
requestId,
intent,

//identyfikator dania
//dostarczana intencja

0);
return pi;
}
}

Kluczowe elementy na listingu 15.16 zostay zaznaczone pogrubionym drukiem. Powtarzalny


alarm zostaje wywoany za pomoc metody setRepeating() obiektu menedera alarmw.
Jedn z danych wejciowych tej metody jest intencja oczekujca, wskazujca odbiorc komunikatw. Wykorzystalimy tu t sam intencj co w przypadku klasy SendAlarmOnceTester.
Jednak w trakcie odczytywania intencji oczekujcej wykorzystalimy inny identyfikator dania
o wartoci 2. Jeli pozostawilibymy stary identyfikator, nasz program mgby zachowa si
w dziwny sposb. Powiedzmy, e najpierw klikamy obiekt menu odpowiedzialny za uruchomienie

506 Android 3. Tworzenie aplikacji


powtarzalnego alarmu. W ten sposb wczymy alarm oraz wywoamy odbiorc TestReceiver.
Zamy, e ten powtarzalny alarm zostanie uruchomiony 30 sekund pniej. Przechodzimy
teraz dalej i klikamy element menu Alarm pojedynczy. Alarm ten uruchomi si tylko jeden raz
po upywie 30 sekund i wywoa tego samego odbiorc TestReceiver.
Gdyby obydwa elementy menu dziaay w ten sposb, zostayby uruchomione obydwa rodzaje
alarmw. Warto jednak zauway, e alarm odezwie si tylko jeden raz. Aby wszystko dziaao
jak naley, musimy wprowadzi rne argumenty requestCode w oczekujcej intencji. Wyjanienie tej sytuacji znajdziemy w podrozdziale Pierwszestwo intencji w uruchamianiu alarmw.

Kompilowanie kodu omawianego przykadu


Aby przetestowa opisany fragment kodu, bdziemy musieli zmieni zawarto kilku plikw
w projekcie.
Najpierw musimy doda klas widoczn na listingu 15.16 w postaci nowego pliku rdowego
o nazwie SendRepeatingAlarmTester.java.
Nastpnie musimy zmieni w kilku miejscach aktywno sterujc TestAlarmsDriverActivity,
zdefiniowan na listingu 15.12.
Zastpmy ponisze wiersze:
private SendAlarmOnceTester alarmTester = null;

alarmTester = new SendAlarmOnceTester(this,this);

nastpujcymi:
private SendRepeatingAlarmTester alarmTester = null;

alarmTester = new SendRepeatingAlarmTester(this,this);

Dodajmy poniszy kod, umoliwiajcy odpowied na zdarzenia menu:


if (item.getItemId() == R.id.menu_alarm_repeated)
{
alarmTester.sendRepeatingAlarm();
return true;
}

Po wprowadzeniu tych zmian moemy wykorzysta interfejs widoczny na rysunku 15.1 do


przywoania obiektu menu Alarm powtarzalny. Wyniki testu ujrzymy w oknie LogCat. Dowiemy si teraz, w jaki sposb moemy anulowa powtarzalny alarm.

Anulowanie alarmu
Aby zrozumie, jak przebiega anulowanie alarmw, wprowadzimy kolejn klas testow, nazwan CancelRepeatingAlarmTester (listing 15.17).
Listing 15.17. Anulowanie powtarzalnego alarmu
// CancelRepeatingAlarmTester.java
package com.androidbook.alarms;
import android.app.AlarmManager;

Rozdzia 15 Badanie menedera alarmw

507

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
public class CancelRepeatingAlarmTester
extends SendRepeatingAlarmTester
{
private static String tag = "CancelRepeatingAlarmTester";
CancelRepeatingAlarmTester(Context ctx, IReportBack target) {
super(ctx, target);
}

/*
* Alarm moe zosta zatrzymany poprzez anulowanie intencji.
* Do anulowania intencji bdzie nam potrzebna
* jej kopia.
*
* Intencja ta musi posiada t sam sygnatur
* oraz identyfikator dania.
*/
public void cancelRepeatingAlarm()
{

//Pobiera intencj do przywoania


//klasy TestReceiver.
Intent intent =
new Intent(this.mContext, TestReceiver.class);

//Aby anulowa intencj, nie trzeba wypenia argumentu extra


//intent.putExtra("message", "Powtarzalny alarm");
PendingIntent pi = this.getDistinctPendingIntent(intent, 2);

// Ustanawia harmonogram alarmu!


AlarmManager am =
(AlarmManager)
this.mContext.getSystemService(Context.ALARM_SERVICE);
am.cancel(pi);
this.mReportTo.reportBack(tag,"Nie powinnismy miec juz do czynienia z alarmem");
}
}

Aby anulowa alarm, musimy najpierw skonstruowa intencj oczekujc, a nastpnie przekaza j menederowi alarmw jako argument metody cancel().
Musimy jednak mie pewno, e obiekt pendingIntent jest skonstruowany dokadnie w taki
sam sposb, jak to miao miejsce podczas konfigurowania alarmu, cznie z identyfikatorem
dania i docelowym odbiorc. Przyjrzyjmy si kodowi rdowemu metody getDistinct
PendingIntent() z listingu 15.16, aby zrozumie, w jaki sposb kod dania jest wykorzystywany wraz z metod PendingIntent.getBroadcast() moemy zignorowa dodatkowe dane
intencji z listingu 15.17, poniewa nie odgrywaj one roli w procesie jej anulowania.

508 Android 3. Tworzenie aplikacji

Kompilowanie kodu omawianego przykadu


Aby przetestowa ten fragment kodu, bdziemy musieli zmieni zawarto kilku plikw
w projekcie.
Najpierw musimy doda klas widoczn na listingu 15.17 w postaci nowego pliku rdowego
o nazwie CancelRepeatingAlarmTester.java.
Nastpnie musimy zmieni w kilku miejscach aktywno sterujc TestAlarmsDriverActivity,
zdefiniowan na listingu 15.12.
Zastpmy ponisze wiersze:
private SendAlarmOnceTester alarmTester = null;

alarmTester = new SendAlarmOnceTester(this,this);

nastpujcymi:
private CancelRepeatingAlarmTester alarmTester = null;

alarmTester = new CancelRepeatingAlarmTester(this,this);

Dodajmy poniszy kod, umoliwiajcy odpowied na zdarzenia menu:


if (item.getItemId() == R.id.menu_alarm_cancel)
{
alarmTester.cancelRepeatingAlarm();
return true;
}

Moemy przetestowa t funkcj, klikajc najpierw obiekt menu Alarm powtarzalny (rysunek
15.1). W wyniku tego widok LogCat zacznie by aktualizowany co 5 sekund. Jeeli wybierzemy
teraz opcj Anuluj alarmy, komunikaty przestan napywa.

Praca z wieloma alarmami jednoczenie


Naszym zdaniem podczas konfigurowania wielu menederw alarmw dla tego samego odbiorcy komunikatw zachowanie menederw alarmw jest nieco nieintuicyjne jeeli kilkakrotnie przywoamy alarm wskazujcy danego odbiorc, poskutkuje jedynie ostatnie wywoanie.
W celu wyjanienia tego zachowania przyjrzyjmy si najpierw przygotowanej na listingu 15.18
klasie testujcej. Znajdziemy tutaj dwie metody. Pierwsza z nich, scheduleSameIntentMultiple
Times(), ustanawia wielokrotnie harmonogram dla tej samej intencji. Druga funkcja, schedule
DistinctIntents(), ma takie samo zadanie, rozrnia ona jednak intencje po identyfikatorze
dania.
Listing 15.18. Praca z wieloma alarmami
//ScheduleIntentMultipleTimesTester.java
package com.androidbook.alarms;
import java.util.Calendar;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;

Rozdzia 15 Badanie menedera alarmw

public class ScheduleIntentMultipleTimesTester


extends CancelRepeatingAlarmTester
{
private static String tag = "ScheduleIntentMultipleTimesTester";
ScheduleIntentMultipleTimesTester(Context ctx, IReportBack target){
super(ctx, target);
}

/*
* Nie mona wielokrotnie ustanawia harmonogramu tej samej intencji.
* Jeeli tak zrobimy, tylko ostatni bdzie wykonywany.
*
* Zwrmy uwag, e wykorzystujemy ten sam identyfikator dania.
*/
public void scheduleSameIntentMultipleTimes()
{

//Pobiera wiele wystpie czasu.


Calendar
Calendar
Calendar
Calendar

cal = Utils.getTimeAfterInSecs(30);
cal2 = Utils.getTimeAfterInSecs(35);
cal3 = Utils.getTimeAfterInSecs(40);
cal4 = Utils.getTimeAfterInSecs(45);

//Wywietla w widoku debugowania informacj, e


//alarmy zostan uruchomione o okrelonej porze.
String s = Utils.getDateTimeString(cal);
mReportTo.reportBack(tag, "Nastawianie alarmu na: " + s);

//Pobiera intencj suc do wywoania odbiorcy.


Intent intent =
new Intent(mContext, TestReceiver.class);
intent.putExtra("message", "Ta sama intencja wiele razy");
PendingIntent pi = this.getDistinctPendingIntent(intent, 1);

// Ustanawia wiele harmonogramw tej samej intencji.


AlarmManager am =
(AlarmManager)
mContext.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(),
pi);
am.set(AlarmManager.RTC_WAKEUP,
cal2.getTimeInMillis(),
pi);
am.set(AlarmManager.RTC_WAKEUP,
cal3.getTimeInMillis(),
pi);
am.set(AlarmManager.RTC_WAKEUP,
cal4.getTimeInMillis(),
pi);
}

/*
* Mona wielokrotnie wykorzystywa t sam intencj,
* jeeli zmienimy identyfikator dania w oczekujcej intencji.

509

510 Android 3. Tworzenie aplikacji


* Identyfikator ten odrnia dan intencj od innych.
*/
public void scheduleDistinctIntents()
{

//Wystpienie zostanie pobrane po 30 sekundach


//od biecego momentu.
Calendar
Calendar
Calendar
Calendar

cal = Utils.getTimeAfterInSecs(30);
cal2 = Utils.getTimeAfterInSecs(35);
cal3 = Utils.getTimeAfterInSecs(40);
cal4 = Utils.getTimeAfterInSecs(45);

//W przypadku gdy chcemy uruchomi alarm dzisiaj o godzinie 11.


//Calendar cal = Utils.getTodayAt(11);
//Wywietla informacj w widoku debugowania, e
//ustanawiamy alarm na okrelon godzin.
String s = Utils.getDateTimeString(cal);
mReportTo.reportBack(tag, "Ustanawianie alarmu na: " + s);

//Pobiera intencj, aby przywoa


//klas TestReceiver.
Intent intent =
new Intent(mContext, TestReceiver.class);
intent.putExtra("message", "Ustanawia oddzielne alarmy");

//Tworzy harmonogram tej samej intencji, ale za pomoc rnych id. da.
AlarmManager am =
(AlarmManager)
mContext.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(),
getDistinctPendingIntent(intent,1));

am.set(AlarmManager.RTC_WAKEUP,
cal2.getTimeInMillis(),
getDistinctPendingIntent(intent,2));
am.set(AlarmManager.RTC_WAKEUP,
cal3.getTimeInMillis(),
getDistinctPendingIntent(intent,3));
am.set(AlarmManager.RTC_WAKEUP,
cal4.getTimeInMillis(),
getDistinctPendingIntent(intent,4));

W kodzie metody scheduleSameIntentMultipleTimes() wykorzystalimy t sam intencj


i za jej pomoc utworzylimy cztery alarmy. Przekonamy si o tym, wybierajc element menu
Wiele alarmw; zosta uruchomiony jedynie ostatni alarm, pozostae nie zadziaay.
Zalecanym rozwizaniem jest taka zmiana kodu, aby kada intencja oczekujca posiadaa inny
identyfikator dania. Dlatego wanie korzystamy z funkcji getDistinctPendingIntent(),
ktra szybko tworzy oczekujce intencje na podstawie identyfikatora dania. Na listingu 15.16
widzimy kod rdowy tej funkcji.

Rozdzia 15 Badanie menedera alarmw

511

Spojrzenie na metod scheduleDistinctIntents() z listingu 15.18 pomoe nam rozwiza


problem duplikujcych si intencji. Widzimy tu zrnicowane identyfikatory da, dziki czemu
odbiorca TestReceiver zostanie wywoany wiele razy, czego dowd znajdziemy w widoku LogCat.
Twrcy Androida usilnie zalecaj, aby pamita o nastpujcych zasadach podczas tworzenia
intencji oczekujcych:
Starajmy si nie tworzy wielu rnorodnych intencji oczekujcych. Musimy by
ostroni, w przypadku gdy tworzymy wiele unikatowych intencji oczekujcych,
rnicych si identyfikatorem dania oraz pozostaymi aspektami intencji.
Spodziewamy si po oczekujcej intencji, e bdzie szybko odtwarzana przez obiekt
j wysyajcy po to, aby moga zosta anulowana. Wynika z tego pewien naturalny
porzdek tworzenia intencji oczekujcych. W idealnym przypadku parametry
wykorzystywane do jej tworzenia powinny by niepowtarzalne. Jeeli tak nie jest,
a my musimy wykorzystywa identyfikatory dania do odrnienia intencji od innych,
powinnimy zapamita wartoci tych identyfikatorw. Bd nam one potrzebne
w momencie, gdy bdziemy chcieli anulowa te intencje oczekujce.
W przypadku gdy nie mamy identyfikatorw dania, dwie intencje oczekujce
wskazuj t sam intencj, jeeli ich kluczowe atrybuty s takie same. W tym przypadku
nie s tu brane pod uwag dodatkowe dane intencji.
W przypadku intencji oczekujcych metody pobierajce zazwyczaj lokalizuj ju istniejc
intencj, a nie tworz nowej.
Standardowo intencje oczekujce powinny wskazywa okrelon klas lub skadnik.

Kompilowanie kodu omawianego przykadu


Aby przetestowa ten fragment kodu, bdziemy musieli zmieni zawarto kilku plikw
w projekcie.
Najpierw musimy doda klas widoczn na listingu 15.18 w postaci nowego pliku rdowego
o nazwie CancelRepeatingAlarmTester.java.
Nastpnie musimy zmieni w kilku miejscach aktywno sterujc TestAlarmsDriverActivity,
zdefiniowan na listingu 15.12.
Zastpmy ponisze wiersze:
private SendAlarmOnceTester alarmTester = null;

alarmTester = new SendAlarmOnceTester(this,this);

nastpujcymi:
private ScheduleIntentMultipleTimesTester alarmTester = null;

alarmTester = new ScheduleIntentMultipleTimesTester (this,this);

Dodajmy poniszy kod, umoliwiajcy odpowied na dwa elementy menu:


if (item.getItemId() == R.id.menu_alarm_multiple)
{
alarmTester.scheduleSameIntentMultipleTimes();
return true;
}
if (item.getItemId() == R.id.menu_alarm_distinct_intents)

512 Android 3. Tworzenie aplikacji


{
alarmTester.scheduleDistinctIntents();
return true;
}

Po wprowadzeniu tych zmian moemy przetestowa funkcje dostpne w tym przykadzie poprzez
kliknicie dwch elementw menu: Wiele alarmw i Oddzielne intencje. Wyniki testu ujrzymy
w oknie LogCat.

Pierwszestwo intencji w uruchamianiu alarmw


Wielokrotnie ju wspominalimy, e w przypadku skonfigurowania kilku alarmw wobec tego
samego typu intencji zostanie uruchomiony tylko ostatni utworzony alarm. Sprbujmy znale
wyjanienie, dlaczego tak si dzieje. Przygldajc si naszym przykadowym kodom, moemy
uzna, e konfigurujemy alarm w menederze alarmw. Przynajmniej takie wraenie sprawia
interfejs API, w ktrym znajdziemy nastpujc metod:
alarmManager.set(time, intent);

Zamy jednak nastpujc sytuacj:


alarmManager.set(time1, intent1);
alarmManager.setRepeated(time2, interval, intent1);

Moglibymy si spodziewa, e obiekt intent1 moe by wycznie pasywnym odbiorc, ktry


jest odbierany przez obydwa alarmy. Jednak w praktyce okazuje si, e liczy si wycznie ostatnia ustawiona metoda. Jest to tak, jakbymy konfigurowali intencj, tak jak w poniszym
przykadzie:
intent1.set(...)
intent1.setRepeated(..)

W tym przypadku prawdopodobnie nabiera sensu ustawienie tylko jednego obiektu intencji
oraz zwizanego z nim alarmu, a take stwierdzenie, e jeli ustanowimy ten alarm wiele razy,
kady poprzedni alarm zostanie zresetowany, podobnie jak ma to miejsce w przypadku zwykego budzika.
T koncepcj moemy sprawdzi za pomoc kodu umieszczonego na listingu 15.19. Interesujc
nas tutaj metod jest alarmIntentPrimacy().
Listing 15.19. Kod sucy do przetestowania pierwszestwa intencji
//AlarmIntentPrimacyTester.java
package com.androidbook.alarms;
import java.util.Calendar;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
public class AlarmIntentPrimacyTester
extends ScheduleIntentMultipleTimesTester
{
private static String tag = "AlarmIntentPrimacyTester";
AlarmIntentPrimacyTester(Context ctx, IReportBack target){
super(ctx, target);

Rozdzia 15 Badanie menedera alarmw

513

/*
* Tutaj nie alarm ma znaczenie,
* lecz oczekujca intencja.
* Nawet jeli zdefiniujemy powtarzalny alarm,
* w przypadku wykorzystywania tej samej intencji za jednym razem
* tylko ta pniejsza intencja odniesie skutek.
*
* Moemy to porwna do wielokrotnego konfigurowania
* alarmu wobec istniejcej intencji,
* a nie okrnym sposobem.
*/
public void alarmIntentPrimacy()
{
Calendar cal = Utils.getTimeAfterInSecs(30);
String s = Utils.getDateTimeString(cal);
this.mReportTo.reportBack(tag,
"Ustanawianie powtarzalnego alarmu o interwale 5 s, poczwszy od: " + s);

//Pobiera intencj suc do przywoania


//klasy TestReceiver.
Intent intent =
new Intent(this.mContext, TestReceiver.class);
intent.putExtra("message", "Powtarzalny alarm");
PendingIntent pi = getDistinctPendingIntent(intent,0);
AlarmManager am =
(AlarmManager)
this.mContext.getSystemService(Context.ALARM_SERVICE);
this.mReportTo.reportBack(tag,"Konfig. powtarzalnego alarmu, interwa 5 s");
am.setRepeating(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(),
5*1000, //5 sekund
pi);
this.mReportTo.reportBack(tag,"Konfig. jednorazowego alarmu dla tej samej
intencji");
am.set(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(),
pi);
this.mReportTo.reportBack(tag,
"Pniejszy alarm, jednorazowy, posiada pierwszestwo");
}
}

Kompilowanie kodu omawianego przykadu


Aby przetestowa ten fragment kodu, bdziemy musieli zmieni zawarto kilku plikw
w projekcie.
Najpierw musimy doda klas widoczn na listingu 15.19 w postaci nowego pliku rdowego
o nazwie AlarmIntentPrimacyTester.java.

514 Android 3. Tworzenie aplikacji


Nastpnie musimy zmieni w kilku miejscach aktywno sterujc TestAlarmsDriverActivity,
zdefiniowan na listingu 15.12.
Zastpmy ponisze wiersze:
private SendAlarmOnceTester alarmTester = null;

alarmTester = new SendAlarmOnceTester(this,this);

nastpujcymi:
private AlarmIntentPrimacyTester alarmTester = null;

alarmTester = new AlarmIntentPrimacyTester (this,this

Dodajmy poniszy kod, umoliwiajcy odpowied na zdarzenie menu:


if (item.getItemId() == R.id.menu_alarm_intent_primacy)
{
alarmTester.alarmIntentPrimacy();
return true;
}

Po wprowadzeniu tych zmian moemy przetestowa funkcje dostpne w tym przykadzie poprzez kliknicie elementu menu Pierwszestwo intencji. Wyniki testu ujrzymy w oknie LogCat,
w ktrym bd rwnie wywietlane nadpisywania starszych alarmw przez alarmy nowsze.
Dlaczego starszy alarm jest zastpowany nowszym w przypadku korzystania z tej samej intencji?
Wiele osb spord zespou twrcw Androida zauwaa, e dwie intencje s tak naprawd wystpieniem tego samego obiektu PendingIntent, jeeli wartoci ich atrybutw s takie same.
Ustanawianie takich intencji jako celw dla wielu alarmw jest tosame z ustanawianiem alarmw dla jednej i tej samej intencji.
Jednak waciwy mechanizm zrozumiemy dopiero po przyjrzeniu si kodowi rdowemu
usugi AlarmManagerService (jest to implementacja interfejsu IAlarmManager). Na listingu
15.20 zosta zamieszczony fragment kodu odpowiedzialny za ustanawianie alarmu (wszystkie
metody ustawiajce ostatecznie przechodz przez ten fragment).
Listing 15.20. Implementacja klasy AlarmManagerService, wycig z kodu rdowego Androida
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

public void setRepeating(int type, long triggerAtTime, long interval,


PendingIntent operation) {
if (operation == null) {
Slog.w(TAG, "set/setRepeating ignored because there is no intent");
return;
}
synchronized (mLock) {
Alarm alarm = new Alarm();
alarm.type = type;
alarm.when = triggerAtTime;
alarm.repeatInterval = interval;
alarm.operation = operation;

// Usuwa ten alarm, jeeli ju zosta wstawiony do harmonogramu


removeLocked(operation);

Rozdzia 15 Badanie menedera alarmw

176
177
178
179
180
181
182
183

515

if (localLOGV) Slog.v(TAG, "set: " + alarm);


int index = addAlarmLocked(alarm);
if (index == 0) {
setLocked(alarm);
}
}
}

Zwrmy uwag, e w samym rodku metody ustawiania kod wywouje metod removeLocked
(operation), gdzie argumentem operation jest obiekt PendingIntent. Ta metoda jest wanie
odpowiedzialna za usunicie wczeniej wystpujcego alarmu. W istocie, gdy wywoujemy
metod cancel(pendingIntent), ostatecznie zostaje rwnie wywoana ta sama metoda
removeLocked(pendingIntent).
Mwic krtko, zestaw SDK postanowi anulowa wszystkie wczeniejsze alarmy i dla tej konkretnej intencji oczekujcej zostawi tylko najnowszy alarm. Jeeli chcemy, aby stao si inaczej,
musimy ustawi dla tej intencji identyfikator dania. Staje si to rwnie jasne, gdy zapoznamy
si z interfejsem API metody cancel(), ktra pobiera wycznie argument w postaci obiektu
PendingIntent. Gdyby zwizek pomidzy alarmem a obiektem PendingIntent nie mia niepowtarzalnego charakteru, jakie znaczenie miaoby anulowanie alarmu wycznie na podstawie
obiektu PendingIntent?
Oczywicie, moemy wykorzysta ten mechanizm na nasz korzy, jeeli naszym celem bdzie
anulowanie wszelkich wczeniejszych alarmw i ustanowienie nowego dla danego odbiorcy.

Trwao alarmw
Czytelnikowi naley si jeszcze jedna informacja dotyczca alarmw: nie s one zachowywane
po ponownym uruchomieniu urzdzenia. Oznacza to, e bdziemy musieli utrzymywa ustawienia alarmu oraz intencji oczekujcej w pamici trwaej oraz ponownie je rejestrowa na podstawie komunikatw nadawanych podczas ponownego uruchamiania urzdzenia oraz, ewentualnie, komunikatw zwizanych ze zmian czasu (na przykad android.intent.action.
BOOT_COMPLETED, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED).

Twierdzenia dotyczce menedera alarmw


Podsumujmy ten rozdzia skrtowym wypisaniem faktw dotyczcych alarmw, intencji oczekujcych oraz menedera alarmw:
Intencje oczekujce s przechowywane w puli oraz mog by wielokrotnie uywane.
Tak naprawd nie moemy utworzy nowej intencji oczekujcej. W rzeczywistoci
lokalizujemy tak intencj za pomoc opcji pozwalajcych na jej ponowne uycie,
aktualizowanie itp.
Intencja jest niepowtarzalnie odrnialna od innych poprzez jej argumenty dziaania,
identyfikator URI danych oraz kategori. Szczegy tej niepowtarzalnoci zostay
zdefiniowane w interfejsie API filterEquals() klasy intencji.
Intencja oczekujca jest kwalifikowana za pomoc kodu dania (w dodatku do bazowej
intencji, od ktrej jest uzaleniona).

516 Android 3. Tworzenie aplikacji

Alarmy oraz intencje oczekujce (w zasadzie wszystkie intencje) nie s niezalene.


Dana intencja oczekujca nie moe by powizana z wieloma alarmami. Najnowszy
alarm przesoni wszystkie pozostae.
Alarmy nie s przechowywane w czasie ponownego uruchamiania urzdzenia. Wszelkie
alarmy utworzone za pomoc menedera alarmw zostan utracone w momencie
ponownego uruchomienia urzdzenia.
Musimy samodzielnie utrzyma parametry alarmu, jeeli chcemy je zachowa pomidzy
ponownymi uruchomieniami urzdzenia. Musimy nasuchiwa komunikatw
informujcych o ponownym uruchomieniu oraz o zmianie czasu, aby w razie potrzeby
zresetowa te alarmy.
Jeli korzystamy z interfejsu anulowania alarmu poprzez intencje, podczas uywania
lub przechowywania alarmw musimy take przechowywa intencje, dziki czemu
bdziemy mogli anulowa w pniejszym czasie te alarmy.

Odnoniki
Ponisze odnoniki stanowi dodatkowy materia do informacji omawianych w niniejszym rozdziale. Warto zwaszcza zwrci uwag na ostatni adres URL, gdy prowadzi do strony z projektami, ktre moemy pobra i zaimportowa w rodowisku Eclipse.
http://developer.android.com/reference/android/app/AlarmManager.html
znajdziemy tu interfejs menedera alarmw. Zostay tu omwione takie metody,
jak set, setRepeating czy cancel.
http://developer.android.com/reference/android/app/PendingIntent.html
na tej stronie zosta wyjaniony mechanizm konstruowania intencji oczekujcej.
Nie zwracajmy zbytnio uwagi na flagi intencji oczekujcych; nie s one niezbdne
w przypadku menedera alarmw.
http://www.androidbook.com/item/1040 kilka krtkich przykadw oraz dalsze
odniesienia do informacji o klasach zwizanych z dat i czasem.
http://download.oracle.com/docs/cd/E17476_01/javase/1.4.2/docs/api/java/util/
Calendar.html dziki zawartym tu zasobom lepiej zrozumiemy zasady pracy
z obiektem Calendar.
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw projektw
wykorzystywanych w tej ksice. Katalog zawierajcy projekty z tego rozdziau nosi
nazw ProAndroid3_R15_MenederAlarmw.

Podsumowanie
W tym rozdziale pokazalimy, jak wykorzysta meneder alarmw do uruchomienia kodu
o okrelonej porze oraz we wskazanych przedziaach czasowych. Jest to bardzo wana funkcja,
wykorzystywana do aktualizacji widetw ekranu startowego oraz w innych czynnociach zalenych od czasu. Wymienilimy rwnie pewne specyficzne cechy zwizane z tym menederem
i pokazalimy sposoby rozwizywania wynikajcych z nich problemw.

R OZDZIA

16
Analiza animacji
dwuwymiarowej

Animacja jest procesem pozwalajcym wywietlanemu na ekranie obiektowi na


zmian koloru, pozycji, rozmiaru lub orientacji w okrelonym przedziale czasowym. Animacje, ktre mona wykorzysta w Androidzie, s bardzo praktyczne,
zabawne, proste oraz s czsto wykorzystywane.
W wersji 2.3 i wczeniejszych Androida dostpne s trzy rodzaje animacji:
animacja poklatkowa, ktra polega na rysowaniu serii klatek jedna po drugiej
w regularnych odstpach czasu; animacja ukadu graficznego, w ktrej s przetwarzane widoki osadzone w pojemniku, na przykad w tabeli na licie; a take
animacja widoku, polegajca na animowaniu dowolnego widoku oglnego przeznaczenia. Dwa ostatnie rodzaje nale do kategorii animacji klatek kluczowych
(ang. tweening), ktra polega na interpolowaniu przez komputer klatek porednich
pomidzy klatkami kluczowymi.
W wersji 3.0 Androida zmodernizowano mechanizm animacji, gdy
wprowadzono moliwo animowania elementw interfejsu uytkownika.
Niektre z nowych koncepcji, zwaszcza dotyczce fragmentw, zostay
omwione w rozdziale 29. Niniejszy rozdzia ukoczylimy przed wydaniem
wersji 3.0 Androida, wic z powodu ogranicze czasowych zajlimy si
w nim jedynie elementami dostpnymi do wersji 2.3 systemu. W rozdziale 29.
opisalimy kilka funkcji animacji dostpnych od wersji 3.0 Androida.

Animacj klatek kluczowych mona wyjani rwnie w taki sposb, e nie wymaga ona rysowania klatki po klatce. Jeeli moemy animowa obiekt bez koniecznoci nakadania i powtarzania kolejnych klatek, to mamy do czynienia z technik
klatek kluczowych. Jeeli na przykad dany obiekt znajduje si w punkcie A, a za 4
sekundy znajdzie si w punkcie B, moemy zmienia jego pooenie co sekund i za
kadym razem od nowa go rysowa. Bdziemy odnosi wraenie, e obiekt ten
porusza si z punktu A do punktu B.

518 Android 3. Tworzenie aplikacji


Koncepcja jest taka, e znajomo pocztkowych i kocowych stanw animowanego obiektu
pozwala grafikowi na zmian pewnych aspektw tego obiektu w trakcie procesu animowania.
Takim aspektem moe by kolor, pozycja, rozmiar albo jaki inny element. W komputerach jest
to osigane poprzez zmian rednich wartoci w regularnych odstpach czasu oraz ponowne
rysowanie powierzchni.
W tym rozdziale zajmiemy si zagadnieniami animacji poklatkowej, ukadu graficznego oraz
widoku zaprezentujemy je z wykorzystaniem dziaajcych przykadw oraz poddamy je dogbnej analizie.
Na kocu rozdziau zamiecilimy adres URL, z ktrego moemy pobra projekty
utworzone na potrzeby tego rozdziau i zaimportowa je do rodowiska Eclipse.

Animacja poklatkowa
Animacja jest prostym procesem polegajcym na wywietlaniu serii obrazw nastpujcych
po sobie w krtkich odstpach czasu, w wyniku czego powstaje wraenie poruszajcego lub
zmieniajcego si obiektu. W taki sposb dziaaj projektory filmowe. Pokaemy przykadowy
projekt, w ktrym zaprojektujemy obraz i zapiszemy go w formie serii oddzielnych klatek, rnicych si od siebie w niewielkim stopniu. Nastpnie umiecimy ten zbir obrazw w przykadowym kodzie umoliwiajcym uruchomienie animacji.

Zaplanowanie animacji poklatkowej


Przed rozpoczciem pisania kodu naley najpierw zaplanowa sekwencj animacji za pomoc
rozrysowania jej na papierze. Przykad takiego planowania zosta zilustrowany na rysunku 16.1,
przedstawiajcym zbir rwnowymiarowych okrgw, na ktrych obwodach zostay umieszczone w rnych pozycjach kolorowe kka. Mona stworzy zbir takich rysunkw przedstawiajcych okrg oraz kko umieszczone w rnych miejscach na obwodzie tego okrgu. Po zachowaniu siedmiu lub omiu takich klatek utworzymy animacj symulujc ruch kka po okrgu.

Rysunek 16.1. Etap projektowania animacji

Rozdzia 16 Analiza animacji dwuwymiarowej

519

Okrelmy sobie podstawowy czon nazwy takiego rysunku, na przykad colored-ball, a nastpnie zachowajmy utworzone rysunki w podkatalogu /res/drawable, eby w przyszoci mona
byo uzyska do nich dostp za pomoc identyfikatorw zasobw. Nazwa kadego pliku powinna zosta utworzona za pomoc wzoru colored-ballN, gdzie N jest numerem porzdkowym klatki. Po utworzeniu animacji powinna ona wyglda tak jak na rysunku 16.2.

Rysunek 16.2. rodowisko testowe animacji poklatkowej

Gwny obszar aktywnoci jest wykorzystywany przez widok animacji. Wstawilimy przycisk
uruchamiania i zatrzymywania animacji w celu obserwacji jej zachowania. W grnej czci
ekranu umiecilimy rwnie notatnik testowy, w ktrym mona zapisywa wszelkie wane
zdarzenia podczas eksperymentowania z programem. Zobaczmy, w jaki sposb mona utworzy ukad graficzny takiej aktywnoci.

Utworzenie aktywnoci
Rozpocznijmy od utworzenia prostego pliku XML ukadu graficznego w podkatalogu /res/layout
(listing 16.1).
Listing 16.1. Plik XML ukadu graficznego do przykadu animacji poklatkowej
<?xml version="1.0" encoding="utf-8"?>

<!-- nazwa pliku: /res/layout/frame_animations_layout.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/textViewId1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"

520 Android 3. Tworzenie aplikacji


android:text="Notatnik testowy"
/>
<Button
android:id="@+id/startFAButtonId"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Uruchom animacj"
/>
<ImageView
android:id="@+id/animationImage"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>

Pierwsz kontrolk jest kontrolka notatnika testowego stanowica prosty widok TextView.
Nastpnie dodajemy przycisk uruchamiania i zatrzymywania animacji. Ostatni jest widok
ImageView, w ktrym bdzie odtwarzana animacja. Po skonstruowaniu ukadu graficznego
naley utworzy aktywno wczytujc ten widok (listing 16.2).
Listing 16.2. Aktywno wczytujca widok ImageView
public class FrameAnimationActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.frame_animations_layout);
}
}

Tak aktywno bdzie mona uruchomi za pomoc dowolnego elementu menu, dostpnego
w biecej aplikacji, poprzez wykonanie nastpujcego kodu:
Intent intent = new Intent(inActivity,FrameAnimationActivity.class);
inActivity.startActivity(intent);

W tym momencie aktywno powinna wyglda tak jak na rysunku 16.3.

Dodawanie animacji do aktywnoci


Po utworzeniu aktywnoci oraz ukadu graficznego pokaemy, w jaki sposb mona do nich
dodawa animacj. W Androidzie animacja poklatkowa jest tworzona poprzez klas Animation
Drawable z pakietu graficznego. Mona stwierdzi po nazwie, e bdzie si ona zachowywa
jak kady inny obiekt rysowany, mogcy stanowi to dla dowolnego widoku (na przykad
mapy bitowe ta s reprezentowane jako elementy Drawable). Klasa AnimationDrawable poza
tym, e naley do kategorii Drawable, moe pobiera list innych obiektw tego typu (na przykad obrazw) i wywietla je w okrelonych interwaach czasowych. W rzeczywistoci klasa ta
jest cienk oson wok obsugi animacji zapewnianej przez bazow klas Drawable.

Rozdzia 16 Analiza animacji dwuwymiarowej

521

Rysunek 16.3. Aktywno animacji poklatkowej


Klasa Drawable uruchamia animacj w ten sposb, e pojemnik lub widok wywouje
klas Runnable, ktra w istocie przerysowuje obiekt Drawable za pomoc innego
zestawu parametrw. Zwrmy uwag, e nie musimy zna takich szczegw
wewntrznej implementacji, eby korzysta z klasy AnimationDrawable. Jednak
w przypadku bardziej zoonych wymaga mona zajrze do kodu rdowego klasy
AnimationDrawable, aby znale wskazwki do napisania wasnych protokow animacji.

eby mc skorzysta z klasy AnimationDrawable, naley najpierw umieci zestaw zasobw


typu Drawable (na przykad zbir obrazw) w podkatalogu /res/drawable. Gwoli cisoci,
umiecimy tam osiem podobnych, lecz nie identycznych obrazw omwionych w punkcie
Zaplanowanie animacji poklatkowej. Nastpnie utworzymy plik XML definiujcy list klatek
(listing 16.3). Take ten plik musi zosta umieszczony w podkatalogu /res/drawable.
Listing 16.3. Plik XML definiujcy list animowanych klatek
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/colored_ball1" android:duration="50" />
<item android:drawable="@drawable/colored_ball2" android:duration="50" />
<item android:drawable="@drawable/colored_ball3" android:duration="50" />
<item android:drawable="@drawable/colored_ball4" android:duration="50" />
<item android:drawable="@drawable/colored_ball5" android:duration="50" />
<item android:drawable="@drawable/colored_ball6" android:duration="50" />
<item android:drawable="@drawable/colored_ball7" android:duration="50" />
<item android:drawable="@drawable/colored_ball8" android:duration="50" />
</animation-list>

522 Android 3. Tworzenie aplikacji


Podczas przygotowywania listy obrazw musimy pamita o pewnych ograniczeniach
klasy AnimationDrawable. Przed rozpoczciem animacji klasa ta wczytuje wszystkie
obrazy do pamici. Podczas testowania przykadowego projektu na emulatorze
wyposaonym w wersj systemu 2.3 liczba klatek wiksza od 6 przekraczaa pojemno
pamici przydzielonej dla aplikacji. W zalenoci od rodowiska testowego by moe
bdziemy musieli ograniczy liczb klatek. Aby rozwiza ten problem, musimy bezporednio
skorzysta z funkcji animacyjnych klasy Drawable i wprowadzi wasny mechanizm.
Niestety, klasa Drawable nie zostaa szczegowo omwiona w tym wydaniu ksiki.
Proponujemy wizyt na stronie www.androidbook.com, gdy planujemy zaktualizowa
jej zawarto w niedugim czasie.

Kada klatka wskazuje na jeden z rysunkw okrelony przez jego identyfikator zasobu. Znacznik
animation-list zostaje przeksztacony do obiektu AnimationDrawable, reprezentujcego
zbir obrazw. Musimy teraz umieci klas Drawable jako zasb ta dla widoku ImageView.
Zakadajc, e nazwalimy ten plik frame_animation.xml i umiecilimy go w podkatalogu
/res/drawable, moemy zastosowa poniszy kod do ustanowienia klasy AnimationDrawable
jako ta widoku ImageView:
view.setBackGroundResource(Resource.drawable.frame_animation);

Dziki tej linii kodu Android rozpoznaje identyfikator zasobu Resource.drawable.frame_


animation jako zasb XML i zgodnie z nim tworzy odpowiedni obiekt Java Animation
Drawable, zanim ustawi go jako to. Gdy ju bdziemy mieli to, moemy uzyska dostp do
tego obiektu AnimationDrawable poprzez wprowadzenie instrukcji get do widoku View
w nastpujcy sposb:
Object backgroundObject = view.getBackground();
AnimationDrawable ad = (AnimationDrawable)backgroundObject;

Po umieszczeniu obiektu klasy AnimationDrawable moemy wprowadzi metody start()


i stop() suce do uruchamiania i zatrzymywania animacji. Poniej zaprezentowalimy dwie
inne istotne metody tego obiektu:
setOneShot();
addFrame(drawable, duration);

Metoda setOneShot() odtwarza animacj jeden raz i potem j zatrzymuje. Metoda addFrame()
dodaje now klatk za pomoc obiektu Drawable i konfiguruje czas jej wywietlania. Dziaanie
tej metody przypomina funkcj znacznika XML android:drawable.
Teraz musimy zoy wszystkie fragmenty kodu w cao, aby otrzyma rodowisko testowe
animacji poklatkowej (listing 16.4).
Listing 16.4. Peny kod rodowiska testowego animacji poklatkowej
public class FrameAnimationActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.frame_animations_layout);
this.setupButton();
}
private void setupButton()
{

Rozdzia 16 Analiza animacji dwuwymiarowej

523

Button b = (Button)this.findViewById(R.id.startFAButtonId);
b.setOnClickListener(
new Button.OnClickListener(){
public void onClick(View v)
{
parentButtonClicked(v);
}
});
}
private void parentButtonClicked(View v)
{
animate();
}
private void animate()
{
ImageView imgView = (ImageView)findViewById(R.id.animationImage);
imgView.setVisibility(ImageView.VISIBLE);
imgView.setBackgroundResource(R.drawable.frame_animation);
AnimationDrawable frameAnimation =
(AnimationDrawable) imgView.getBackground();
if (frameAnimation.isRunning())
{
frameAnimation.stop();
}
else
{
frameAnimation.stop();
frameAnimation.start();
}
}
}//eof-class

animate() lokalizuje widok ImageView w biecej aktywnoci i przypisuje mu to


AnimationDrawable rozpoznane przez zasb R.drawable.frame_animation. Nastpnie kod

Metoda

odczytuje ten obiekt i przeprowadza proces animowania. Przycisk uruchamiania i zatrzymywania jest skonfigurowany w ten sposb, e jego nacinicie w trakcie odtwarzania animacji
zatrzyma j; jeeli animacja jest zatrzymana, jego nacinicie spowoduje jej uruchomienie.
Zauwamy, e jeli przypiszemy parametrowi listy animacji OneShot warto true, animacja wykona tylko jeden cykl. Jednak nie mona dokadnie przewidzie, kiedy si to stanie.
Chocia animacja zostaje zakoczona po wywietleniu ostatniego obrazu, nie otrzymamy
adnego informujcego o tym komunikatu. Z tego powodu nie istnieje aden bezporedni
sposb wywoania kolejnej czynnoci w odpowiedzi na zakoczenie animacji.
Pomimo tej niedogodnoci mona uzyska wspaniae efekty wizualne poprzez wywietlanie
po kolei serii obrazw w prostym procesie animacji poklatkowej.

Animacja ukadu graficznego


Podobnie jak w przypadku animacji poklatkowej, animacja ukadu graficznego jest bardzo prosta.
Jak sama nazwa wskazuje, animacja tego typu jest przeznaczona dla pewnego rodzaju widokw,

524 Android 3. Tworzenie aplikacji


uoonych w okrelony sposb. Stosowana jest ona na przykad w przypadku widokw
ListView oraz GridView, ktre s dwiema powszechnie implementowanymi kontrolkami
w systemie Android. W szczeglnoci animacja ukadu graficznego jest uywana do dodawania
efektw graficznych, zmieniajcych sposb wywietlania elementw umieszczonych w wymienionych widokach. Tak naprawd moe by ona stosowana wobec wszystkich kontrolek wywodzcych si z klasy ViewGroup.
W przeciwiestwie do animacji poklatkowej, animacja ukadu graficznego nie jest generowana
poprzez powtarzanie klatek. Zamiast tego s zmieniane w czasie rne waciwoci widoku.
Kady widok w Androidzie zawiera macierz transformacji, ktra odwzorowuje widok wywietlony na ekranie. Poprzez zmian takiej macierzy na rne sposoby mona przeprowadzi
procesy skalowania, obracania i przemieszczania (translacji) tego widoku. Na przykad poprzez
zmian przezroczystoci widoku w skali od 0 do 1 otrzymujemy tak zwan animacj typu alfa.

Podstawowe typy animacji klatek kluczowych


Poniej prezentujemy nieco bardziej szczegowo podstawowe rodzaje animacji klatek kluczowych (ang. tweening):
Animacja skali. Ten typ animacji umoliwia powikszanie lub zmniejszanie widoku
w osi x oraz w osi y. Mona take okreli punkt zwrotny, wok ktrego bdzie
odtwarzana animacja.
Animacja rotacyjna. Dziki niej mona obraca widok wok punktu zwrotnego
o okrelony kt.
Animacja translacyjna. Suy ona do przesuwania widoku wzdu osi x lub y.
Animacja typu alfa. Suy do zmieniania przezroczystoci widoku.
Animacje tego typu s definiowane w postaci plikw XML umieszczonych w podkatalogu
/res/anim. Listing 16.5 prezentuje krtki przykad, pomagajcy zrozumie, w jaki sposb te animacje s definiowane.
Listing 16.5. Animacja skali zdefiniowana w pliku XML, umieszczonym w podkatalogu
/res/anim/scale.xml
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<scale
android:fromXScale="1"
android:toXScale="1"
android:fromYScale="0.1"
android:toYScale="1.0"
android:duration="500"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="100" />
</set>

Wszystkie wartoci parametrw w tym pliku animacji zostaj okrelane od do, poniewa
musimy okreli wartoci pocztkowe i kocowe animacji.

Rozdzia 16 Analiza animacji dwuwymiarowej

525

Kada z animacji dopuszcza rwnie moliwo korzystania z interpolatorw czasu w postaci


argumentw. Interpolatory zostan omwione na kocu podrozdziau zwizanego z animacj
ukadu graficznego, teraz jednak wystarczy wiedzie, e s one odpowiedzialne za szybko
zmian argumentw w trakcie przetwarzania animacji.
Po utworzeniu tego pliku deklarowania animacji moemy powiza animacj z ukadem graficznym, dziki czemu elementy skadowe ukadu graficznego bd animowane.
W tym miejscu warto wspomnie, e kada z tych animacji jest reprezentowana jako
klasa Java w pakiecie android.view.animation. Dokumentacja kadej z tych klas
nie tylko opisuje jej metody jzyka Java, lecz rwnie dopuszczalne argumenty XML
dla kadego typu animacji.

Skoro ju naszkicowalimy zarys rodzajw animacji ukadu graficznego wystarczajcy do ich


chociaby podstawowego zrozumienia, zajmijmy si projektowaniem przykadu.

Zaplanowanie rodowiska testowego


animacji ukadu graficznego
Za pomoc prostego zestawu ListView w aktywnoci mona przetestowa wszystkie omwione
przez nas koncepcje animacji ukadu graficznego. Po utworzeniu widoku ListView mona do
niego doczy animacj, co spowoduje jej przetworzenie wobec kadego elementu tego widoku.
Zamy, e chcemy utworzy animacj skali, ktra powiksza widok od zera do oryginalnego
rozmiaru w osi y. Moemy to sobie wyobrazi wizualnie jako linijk tekstu, ktra najpierw przypomina poziom lini, a nastpnie zostaje powikszona do waciwego rozmiaru czcionki.
Mona t animacj doczy do widoku ListView. Kiedy to zrobimy, kady element tej listy
bdzie wywietlany za pomoc tej animacji.
Moemy doda kilka parametrw, ktre urozmaic podstawow animacj, na przykad animowanie listy od gry do dou lub odwrotnie. Parametry te s definiowane w klasie poredniej,
zachowujcej si jak mediator pomidzy konkretnym plikiem XML animacji a widokiem listy.
Istnieje moliwo zdefiniowania zarwno animacji, jak i mediatora w pliku XML umieszczonym w podkatalogu /res/anim. Gdy ju utworzymy taki poredniczcy plik XML, moemy go
wykorzysta w postaci danych wejciowych dla widoku ListView w jego wasnym pliku definicji
XML. Gdy ta podstawowa konfiguracja bdzie ju dziaa, bdziemy zmienia animacje, eby
przekona si, w jaki sposb wpywaj one na wywietlanie elementw widoku ListView.
Zanim rozpoczniemy wiczenie, przyjrzyjmy si, jak widok ListView bdzie wyglda po
zakoczeniu animacji (rysunek 16.4).

Utworzenie aktywnoci oraz widoku ListView


Rozpoczniemy od utworzenia ukadu graficznego XML dla widoku ListView przedstawionego
na rysunku 16.4, dziki czemu moliwe bdzie wczytanie tego ukadu graficznego w prostej aktywnoci. Na listingu 16.6 zosta umieszczony taki nieskomplikowany ukad graficzny z zaimplementowanym widokiem ListView. Taki plik naley umieci w podkatalogu /res/layout.
Zakadajc, e nazwa pliku brzmi list_layout.xml, kompletna cieka do niego bdzie wygldaa
nastpujco: /res/layout/list_layout.xml.

526 Android 3. Tworzenie aplikacji

Rysunek 16.4. Animowana lista ListView


Listing 16.6. Plik XML ukadu graficznego definiujcy widok ListView
<?xml version="1.0" encoding="utf-8"?>

<!-- nazwa pliku: /res/layout/list_layout.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:id="@+id/list_view_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>

Listing 16.6 przedstawia prosty meneder LinearLayout z umieszczonym wewntrz niego


prostym widokiem ListView. Powinnimy jednak skorzysta z okazji i wyjani pewn rzecz
dotyczc definicji widoku ListView, ktra jest do marginalnie powizana z treci rozdziau.
Jeeli Czytelnik bdzie pracowa na aplikacji Notepad lub innych przykadowych programach, zauway zapewne, e identyfikator widoku ListView jest przewanie okrelany jako
@android:id/list. Zgodnie z informacjami z rozdziau 3. odniesienie @android:id/list
wskazuje na identyfikator predefiniowany w przestrzeni nazw android. Pytanie brzmi: kiedy naley stosowa odniesienie android:id, a kiedy nasz wasny identyfikator, na przykad
@+id/list_view_id?
Identyfikatora @android:id/list uywamy jedynie w przypadku, gdy aktywnoci jest List
Activity. W przypadku tej aktywnoci zakada si, e widok ListView, okrelony przez
ten predefiniowany identyfikator, jest dostpny do wczytania. W tym wypadku uywamy raczej

Rozdzia 16 Analiza animacji dwuwymiarowej

527

aktywnoci oglnego przeznaczenia, a nie ListActivity, i musimy wasnorcznie zapeni


w jawny sposb widok ListView. W zwizku z tym nie ma adnych ogranicze co do rodzaju
identyfikatora, ktry ma reprezentowa t list. Jednak mona take wykorzysta odniesienie
@android:id/list, poniewa nie stwarza to adnego konfliktu z powodu braku aktywnoci
ListActivity.
To taka maa dygresja, warto jednak o niej pamita podczas tworzenia wasnych widokw
ListView poza aktywnoci ListActivity. Gdy ju posiadamy ukad graficzny wymagany dla
aktywnoci, moemy napisa kod odpowiedzialny za wczytanie tego pliku ukadu graficznego,
dziki czemu zostanie wygenerowany interfejs uytkownika (listing 16.7).
Listing 16.7. Kod aktywnoci odpowiedzialnej za animacj ukadu graficznego
public class LayoutAnimationActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.list_layout);
setupListView();
}
private void setupListView()
{
String[] listItems = new String[] {
" Element 1", " Element 2", " Element 3",
" Element 4", " Element 5", " Element 6",
};
ArrayAdapter listItemAdapter =
new ArrayAdapter(this
,android.R.layout.simple_list_item_1
,listItems);
ListView lv = (ListView)this.findViewById(R.id.list_view_id);
lv.setAdapter(listItemAdapter);
}
}

Niektre fragmenty kodu widocznego na listingu 16.7 s oczywiste, ale inne nie. Pierwsza cz
kodu w zwyky sposb wczytuje widok na podstawie wygenerowanego identyfikatora ukadu
graficznego R.layout.list_layout. Naszym zadaniem jest zapenienie widoku ListView
z tego ukadu graficznego szecioma elementami. Te elementy tekstowe zostay wczytane do
tablicy. Musimy ustanowi adapter danych wobec widoku ListView, eby te elementy mogy
zosta wywietlone.
Aby utworzy wymagany adapter, musimy okreli, w jaki sposb kady z elementw bdzie
wstawiany podczas wywietlania listy na ekranie. Ukad graficzny okrelamy za pomoc predefiniowanego ukadu, znajdujcego si w strukturze Androida. W naszym przykadzie ukad
graficzny wyznaczono nastpujco:
android.R.layout.simple_list_item_1

Innymi dostpnymi ukadami graficznymi widoku dla tych elementw s:

528 Android 3. Tworzenie aplikacji


simple_list_item_2
simple_list_item_checked
simple_list_item_multiple_choice
simple_list_item_single_choice

Mona zajrze do dokumentacji Androida, aby si dowiedzie, jak te ukady graficzne wygldaj
i jak si zachowuj. Teraz moemy wywoa t aktywno za pomoc dowolnego przycisku
menu w aplikacji po wstawieniu nastpujcego kodu:
Intent intent = new Intent(inActivity,LayoutAnimationActivity.class);
inActivity.startActivity(intent);

Jednak podobnie jak w przypadku wywoa innych aktywnoci musimy zarejestrowa


aktywno LayoutAnimationActivity w pliku AndroidManifest.xml, jeeli powysze wywoanie
aktywnoci ma zadziaa. Poniej umiecilimy potrzebny do tego kod:
<activity android:name=".LayoutAnimationActivity"
android:label="Testowa aktywno widoku animacji"/>

Animowanie widoku ListView


Po przygotowaniu rodowiska testowego (listingi 16.6 i 16.7) Czytelnik dowie si, w jaki sposb
wstawia animacj skali do widoku ListView. Spjrzmy, jak animacja ta zostaje zdefiniowana
w pliku XML (listing 16.8).
Listing 16.8. Definiowanie animacji skali w pliku XML
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<scale
android:fromXScale="1"
android:toXScale="1"
android:fromYScale="0.1"
android:toYScale="1.0"
android:duration="500"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="100" />
</set>

Jak ju wczeniej wspomnielimy, pliki definiujce animacje s przechowywane w podkatalogu


/res/anim.
Przetumaczmy te atrybuty XML na jzyk polski.
Wagi from i to s wskanikami pocztku oraz zakoczenia procesu powikszania. W naszym
wypadku powikszanie rozpoczyna si od wartoci 1 i takie pozostaje dla osi x. Oznacza to, e
element nie bdzie powikszany ani zmniejszany w tej osi.
Jednak w przypadku osi y powikszanie rozpoczyna si od wartoci 0.1 i dy do 1.0. Innymi
sowy, na pocztku animacji rozmiar obiektu stanowi jedn dziesit jego naturalnego rozmiaru,
do ktrego dy w czasie trwania animacji.
Caa operacja skalowania zajmie 500 milisekund.
rodek dziaania znajduje si w poowie drogi obydwu osi (50%).

Rozdzia 16 Analiza animacji dwuwymiarowej

529

Warto startOffset odnosi si do czasu (wyraonego w milisekundach), po ktrym animacja


zostanie uruchomiona.
Wze nadrzdny animacji skali wskazuje na zestaw animacji, ktry dopuszcza wprowadzenie
wikszej liczby animacji. Omwimy rwnie tego rodzaju przykad. Na razie jednak mamy do
dyspozycji tylko jedn animacj w zestawie.
Nazwijmy ten plik scale.xml i umiemy go w podkatalogu /res/anim. Nie jestemy na razie
gotowi, eby wstawi ten plik XML animacji jako argument w widoku ListView; widok ten
wymaga jeszcze jednego pliku XML, ktry bdzie zachowywa si jak porednik pomidzy
widokiem a zestawem animacji. Kod pliku XML, w ktrym zaimplementowane jest takie powizanie, zosta pokazany na listingu 16.9.
Listing 16.9. Definicja dla pliku XML stanowicego kontroler ukadu graficznego
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="30%"
android:animationOrder="reverse"
android:animation="@anim/scale" />

Rwnie ten plik naley umieci w podkatalogu /res/anim. W naszym przykadzie zakadamy, e plik nosi nazw list_layout_controller. Po przyjrzeniu si definicji pliku poredniczcego zrozumiemy, dlaczego jest on niezbdny.
W pliku tym zostaje okrelone, e animacja tej listy powinna przebiega w odwrconym porzdku oraz e animacja kadego elementu bdzie opniona o 30% wzgldem cakowitego czasu
trwania animacji. Znajduje si tu rwnie odniesienie do pliku animacji scale.xml. Zauwamy rwnie, e w kodzie jest uyte odniesienie do tego pliku @anim/scale zamiast jego nazwy.
Gdy ju posiadamy wymagane pliki XML z danymi wejciowymi, pokaemy, w jaki sposb
naley zaktualizowa definicj XML widoku ListView, eby obejmowaa ona animacj
XML jako argument. Najpierw przejrzyjmy dotychczas utworzone pliki XML:
// pojedyncza animacja skali
/res/anim/scale.xml

// plik poredniczcy
/res/anim/list_layout_controller.xml

// plik ukadu graficznego widoku aktywnoci


/res/layout/list_layout.xml

Gdy te pliki s gotowe, musimy zmodyfikowa plik XML ukadu graficznego list_layout.xml
w taki sposb, eby widok ListView wskazywa plik list_layout_controller.xml (listing 16.10).
Listing 16.10. Zaktualizowany kod pliku List_Layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView

530 Android 3. Tworzenie aplikacji


android:id="@+id/list_view_id"
android:persistentDrawingCache="animation|scrolling"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layoutAnimation="@anim/list_layout_controller" />
/>
</LinearLayout>

Zmienione wiersze zostay wyrnione pogrubion czcionk. Kluczowym znacznikiem jest


android:layoutAnimation, ktry wskazuje poredniczcy plik XML definiujcy kontroler
ukadu graficznego za pomoc znacznika layoutAnimation (listing 16.9). Z kolei znacznik
layoutAnimation odnosi si do animacji, w naszym wypadku animacji skali zdefiniowanej
w pliku scale.xml.
Android zaleca take wstawienie znacznika persistentDrawingCache, ktry optymalizuje
animacj i przesuwanie. Wicej informacji na jego temat mona znale w dokumentacji rodowiska Android SDK.
Po zaktualizowaniu pliku list_layout.xml zgodnie z listingiem 16.10 wtyczka ADT rodowiska
Eclipse automatycznie przekompiluje pakiet, uwzgldniajc wprowadzone zmiany. Gdybymy
teraz uruchomili aplikacj, zobaczylibymy, e animacja skali jest przeprowadzana na kadym
elemencie. Zdefiniowalimy czas trwania animacji na 500 milisekund, zatem ujrzymy wyranie
zmian skali podczas rysowania obiektu.
Moemy ju eksperymentowa z innymi rodzajami animacji. Sprawdzimy teraz animacj typu
alfa. W tym celu utworzymy plik /res/anim/alpha.xml i umiecimy w nim tre listingu 16.11.
Listing 16.11. Plik alpha.xml do testowania animacji typu alfa
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="1000" />

Animacja typu alfa jest odpowiedzialna za kontrol zmiany nasycenia kolorw. W tym przykadzie w cigu 1000 milisekund (1 sekundy) kolor z przezroczystego staje si w peni nasycony.
Dobrze jest ustawi czas trwania animacji na co najmniej 1 sekund, w przeciwnym wypadku
zmiana nasycenia bdzie trudna do zaobserwowania.
W przypadku zmiany animacji pojedynczego elementu musimy zmieni rwnie tre pliku
poredniczcego (listing 16.9), eby wskazywaa plik z now animacj. Poniej pokazalimy
sposb wskazywania z animacji skali na animacj typu alfa:
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="30%"
android:animationOrder="reverse"
android:animation="@anim/alpha" />

Zmieniony wiersz w tym kodzie wyrniono pogrubion czcionk. Sprbujmy teraz stworzy
animacj czc zmian pooenia ze zmian gradientu nasycenia koloru. Listing 16.12 przedstawia przykadowy kod takiej animacji.

Rozdzia 16 Analiza animacji dwuwymiarowej

531

Listing 16.12. Poczenie animacji translacyjnej z animacj typu alfa w zestawie animacji
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<translate android:fromYDelta="-100%" android:toYDelta="0"
android:duration="500" />
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="500" />
</set>

Zwrmy uwag, w jaki sposb okrelilimy dwie animacje w zestawie animacji. Animacja
translacyjna bdzie przesuwaa tekst z gry na d w wydzielonym dla niego obszarze wywietlania. Animacja typu alfa bdzie powodowa zmian gradientu nasycenia koloru od przezroczystego do cakowicie nasyconego podczas przesuwania tekstu w d. Warto 500 czasu
trwania animacji pozwoli uytkownikowi obserwowa w wygodny sposb zmian. Oczywicie
znowu bdzie trzeba zmieni plik poredniczcy layoutAnimation, tak eby znalazo si w nim
odniesienie do nowego pliku. Zakadajc, e nazw pliku zawierajcego poczone animacje
jest /res/anim/translate-alpha.xml, plik layoutAnimation bdzie wyglda nastpujco:
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="30%"
android:animationOrder="reverse"
android:animation="@anim/translate-alpha" />

Zobaczmy, w jaki sposb mona uywa animacji rotacyjnej (listing 16.13).


Listing 16.13. Plik XML animacji rotacyjnej
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromDegrees="0.0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="500" />

Kod z listingu 16.13 spowoduje wykonanie jednego penego obrotu przez kady element tekstowy wok rodka tego elementu. Czas trwania 500 milisekund cakowicie wystarczy, eby
obserwator dostrzeg animacj. Podobnie jak w poprzednich przypadkach, tak i teraz musz
zosta zmodyfikowane pliki XML kontrolera animacji oraz ukadu graficznego ListView,
a aplikacja musi zosta ponownie uruchomiona, eby animacja zadziaaa.
Omwilimy ju podstawowe pojcia dotyczce animacji ukadu graficznego, poczwszy od
prostego pliku animacji, a skoczywszy na powizaniu go poprzez plik poredniczcy layout
Animation z widokiem ListView. Ta wiedza wystarczy, eby ujrze animowane efekty. Musimy
omwi jednak jeszcze jedno pojcie dotyczce animacji ukadu graficznego interpolatory.

Stosowanie interpolatorw
Interpolatory okrelaj, w jaki sposb dana waciwo, na przykad gradient koloru, zmienia
si wzgldem czasu. Czy bdzie si ona zmieniaa w sposb liniowy, czy w sposb wykadniczy?
Czy rozpocznie si szybko, lecz bdzie zwalniaa z biegiem czasu? Zastanwmy si nad przykadem animacji typu alfa z listingu 16.11:

532 Android 3. Tworzenie aplikacji


<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="1000" />

Animacja rozpoznaje zastosowany interpolator w tym przypadku accelerate_interpolator.


Istnieje odpowiedni obiekt Java, sucy do definiowania tego interpolatora. Poza tym zwrmy
uwag, e okrelilimy ten interpolator jako odniesienie do zasobw. Oznacza to, e musi istnie
plik odpowiadajcy identyfikatorowi anim/accelerate_interpolator, w ktrym opisany jest ten
obiekt jzyka Java oraz jego dodatkowe parametry. Tak jest w istocie. Przyjrzyjmy si definicji pliku
XML, do ktrego odniesieniem jest identyfikator @android:anim/accelerate_interpolator:
<accelerateInterpolator
xmlns:android="http://schemas.android.com/apk/res/android"
factor="1" />

Plik ten mona odnale w nastpujcym podkatalogu pakietu Android:


/res/anim/accelerate_interpolator.xml
Znacznik XML accelerateInterpolator odpowiada nastpujcemu obiektowi rodowiska
Java:
android.view.animation.AccelerateInterpolator

W dokumentacji jzyka Java dotyczcej tej klasy mona zobaczy, jakie znaczniki XML s dla niej
dostpne. Zadaniem tego interpolatora jest zapewnienie wspczynnika powielania danego przedziau czasowego w oparciu o krzyw hiperboliczn. Wida to w kodzie rdowym interpolatora:
public float getInterpolation(float input)
{
if (mFactor == 1.0f)
{
return (float)(input * input);
}
else
{
return (float)Math.pow(input, 2 * mFactor);
}
}

Kady interpolator w inny sposb implementuje metod getInterpolation. W naszym przypadku, jeli interpolator zostanie skonfigurowany tak, e wspczynnik bdzie wynosi 1.0,
zostanie zwrcony kwadrat tego wspczynnika. W przeciwnym razie zostanie zwrcona
potga danych wejciowych, ktre bd nadal skalowane przez ten wspczynnik. Jeeli zatem warto wspczynnika bdzie wynosia 1.5, zamiast funkcji kwadratowej ujrzymy funkcj
szecienn.
Poniej wypisalimy list obsugiwanych interpolatorw:
AccelerateDecelerateInterpolator
AccelerateInterpolator
CycleInterpolator
DecelerateInterpolator
LinearInterpolator
AnticipateInterpolator
AnticipateOvershootInterpolator
BounceInterpolator
OvershootInterpolator

Rozdzia 16 Analiza animacji dwuwymiarowej

533

eby zaprezentowa potencjaln elastyczno interpolatorw, przyjrzyjmy si pokrtce obiektowi BounceInterpolator, powodujcemu podskakiwanie elementu (to znaczy jego naprzemienny ruch w gr i w d) do samego koca poniszej animacji:
public class BounceInterpolator implements Interpolator {
private static float bounce(float t) {
return t * t * 8.0f;
}
public float getInterpolation(float t) {
t *= 1.1226f;
if (t < 0.3535f) return bounce(t);
else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
else return bounce(t - 1.0435f) + 0.95f;
}
}

Zachowanie tych interpolatorw zostao omwione pod poniszym adresem:


http://developer.android.com/reference/android/view/animation/package-summary.html
W dokumentacji jzyka Java wymienione s rwnie znaczniki XML, pozwalajce na kontrolowanie kadej z tych klas. Jednak z dokumentacji trudno wywnioskowa przeznaczenie kadego
typu interpolatora. Najlepiej jest samemu wyprbowa wszystkie interpolatory i sprawdzi
skutki ich dziaania. Pod poniszym adresem mona rwnie przejrze kod rdowy:
http://android.git.kernel.org/?p=platform%2Fframeworks%2Fbase.git&a=search&
h=HEAD&st=grep&s=BounceInterpolator
Na tym zakoczymy wywody powicone animacji ukadu graficznego. Przejdziemy teraz do
trzeciej czci animowania, powiconej programowaniu animacji widoku.

Animacja widoku
Skoro zapoznalimy si ju z animacj poklatkow oraz animacj ukadu graficznego, moemy
zaj si animacj widoku najbardziej skomplikowanym rodzajem animacji. Stosowana jest
w niej technika animowania dowolnego widoku poprzez kontrolowanie macierzy transformacji,
sucej do wywietlania widoku.

Animacja widoku
Widok wywietlany przez Androida przechodzi przez macierz transformacji. W aplikacjach
graficznych macierze transformacji su do przeksztacenia w jaki sposb widoku. Proces ten
polega na przetumaczeniu wejciowego zestawu wsprzdnych pikseli i kombinacji kolorw
na nowy zestaw. Po przeprowadzeniu transformacji ujrzymy obraz zmieniony pod wzgldem
rozmiaru, pozycji, orientacji lub koloru.
Te przeksztacenia mona przeprowadzi za pomoc aparatu matematycznego, mnoc w okrelony sposb wejciowy zestaw wsprzdnych przez wartoci macierzy transformacji, dziki
czemu powstanie nowy zestaw wsprzdnych. Poprzez zmian macierzy transformacji wpywamy na wygld widoku.

534 Android 3. Tworzenie aplikacji


Macierz, ktra nie zmienia widoku podczas tego mnoenia, nazywana jest macierz jednostkow. Transformacj przewanie rozpoczynamy od macierzy jednostkowej i kolejno wprowadzamy serie transformacji rozmiaru, pozycji i orientacji. Nastpnie za pomoc macierzy kocowej rysujemy widok.
Android odsania tak macierz transformacji widoku poprzez umoliwienie zarejestrowania
obiektu animacji wobec tego widoku. Obiekt animacji bdzie posiada procedur wywoania,
dziki ktrej uzyska dostp do tej macierzy i w okrelony sposb zmieni jej wartoci, co pocignie za sob zmian wywietlania widoku. Zajmiemy si teraz tym procesem.
Rozpocznijmy tworzenie przykadowego projektu od zaplanowania animacji widoku. Na pocztek zapenimy aktywno kilkoma elementami w widoku ListView, podobnie jak miao to
miejsce w podrozdziale Animacja ukadu graficznego. Nastpnie w grnej czci ekranu
umiecimy przycisk powodujcy uruchomienie animacji ListView (rysunek 16.5). Widoczne
s zarwno lista elementw, jak i przycisk, adna animacja nie zostaa jednak jeszcze uruchomiona. Do tego bdzie suy utworzony przycisk.

Rysunek 16.5. Aktywno animacji widoku

Po klikniciu przycisku Uruchom animacj powinien si pojawi may widok porodku ekranu,
ktry nastpnie stopniowo bdzie si powiksza a do wypenienia zarezerwowanej dla niego
przestrzeni. Zaprezentujemy kod, ktry nam to umoliwi. Na listingu 16.14 zosta pokazany
kod pliku XML ukadu graficznego, nadajcy si do zastosowania w aktywnoci.
Listing 16.14. Plik XML ukadu graficznego dla aktywnoci animacji widoku
<?xml version="1.0" encoding="utf-8"?>

<!-- Ten plik jest umieszczony w /res/layout/list_layout.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>

Rozdzia 16 Analiza animacji dwuwymiarowej

535

<Button
android:id="@+id/btn_animate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Uruchom animacj"
/>
<ListView
android:id="@+id/list_view_id"
android:persistentDrawingCache="animation|scrolling"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>

Pogrubiona czcionka ma zwrci uwag Czytelnika na lokalizacj oraz nazw pliku. Ten ukad
graficzny skada si z dwch czci: pierwsza z nich to przycisk btn_animate, sucy do uruchomienia animacji widoku; drug jest widok ListView, w naszym przypadku nazwany
list_view_id.
Skoro mamy ju ukad graficzny dla aktywnoci, moemy utworzy sam aktywno, eby
wywietli widok i skonfigurowa przycisk Uruchom animacj (listing 16.15).
Listing 16.15. Kod dla aktywnoci animacji widoku przed rozpoczciem animacji
public class ViewAnimationActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.list_layout);
setupListView();
this.setupButton();
}
private void setupListView()
{
String[] listItems = new String[] {
"Element 1", "Element 2", "Element 3",
"Element 4", "Element 5", "Element 6",
};
ArrayAdapter listItemAdapter =
new ArrayAdapter(this
,android.R.layout.simple_list_item_1
,listItems);
ListView lv = (ListView)this.findViewById(R.id.list_view_id);
lv.setAdapter(listItemAdapter);
}
private void setupButton()
{
Button b = (Button)this.findViewById(R.id.btn_animate);
b.setOnClickListener(
new Button.OnClickListener(){
public void onClick(View v)
{

536 Android 3. Tworzenie aplikacji


//animateListView();
}
});
}
}

Kod przedstawiony dla aktywnoci animacji widoku z listingu 16.15 bardzo przypomina kod
aktywnoci animacji ukadu graficznego z listingu 16.7. W podobny sposb wczytalimy widok
i wstawilimy sze elementw tekstowych do widoku ListView. Skonfigurowalimy przycisk
w taki sposb, eby wywoywa metod animateListView() po klikniciu. Na razie jednak
oznaczymy ten fragment komunikatem, dopki nasz przykad nie zadziaa.
Aktywno moemy wywoa tu po jej zarejestrowaniu w pliku AndroidManifest.xml:
<activity android:name=".ViewAnimationActivity"
android:label="Aktywno testowa animacji widoku">

Po przeprowadzeniu procesu rejestracji moemy wywoa aktywno animacji widoku za pomoc dowolnego przycisku menu w aplikacji, korzystajc z poniszego fragmentu kodu:
Intent intent = new Intent(this, ViewAnimationActivity.class);
startActivity(intent);

Po uruchomieniu programu pojawi si ekran pokazany na rysunku 16.5.

Dodawanie animacji
W tym wiczeniu naszym celem jest dodanie animacji do widoku ListView, widocznego na
rysunku 16.5. W tym celu potrzebujemy klasy wywodzcej si z pakietu android.view.
animation.Animation. Nastpnie musimy przesoni metod applyTransformation, aby
mona byo zmodyfikowa macierz transformacji. Nazwijmy t klas ViewAnimation. Po jej
utworzeniu moemy przeprowadzi w klasie ListView nastpujc czynno:
ListView lv = (ListView)this.findViewById(R.id.list_view_id);
lv.startAnimation(new ViewAnimation());

Pjdmy dalej. Przyjrzyjmy si kodowi rdowemu klasy ViewAnimation i zastanwmy si,


jaki rodzaj animacji chcemy otrzyma (listing 16.16).
Listing 16.16. Kod rdowy klasy ViewAnimation
public class ViewAnimation extends Animation
{
@Override
public void initialize(int width, int height, int parentWidth,
int parentHeight)
{
super.initialize(width, height, parentWidth, parentHeight);
setDuration(2500);
setFillAfter(true);
setInterpolator(new LinearInterpolator());
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t)

Rozdzia 16 Analiza animacji dwuwymiarowej

537

{
final Matrix matrix = t.getMatrix();
matrix.setScale(interpolatedTime, interpolatedTime);
}
}

Metoda zwrotna initialize informuje nas o wymiarach widoku. W niej s rwnie inicjalizowane wszelkie parametry animacji. W naszym przykadzie skonfigurowalimy czas trwania
na 2500 milisekund (2,5 sekundy). Sprawimy take, e wynik kocowy animacji pozostanie
niezmieniony po jej zakoczeniu, a to za spraw przypisania parametrowi FillAfter wartoci
true. W dodatku okrelilimy, e nasz interpolator jest liniowy, co oznacza, e animacja zmienia si stopniowo od pocztku do koca. Wszystkie wymienione waciwoci pochodz z bazowej klasy android.view.animation.Animation.
Cz zasadnicza animacji jest przeprowadzana w metodzie applyTransformation. Szkielet
Androida bdzie j bez przerwy wywoywa w celu symulowania animacji. Za kadym wywoaniem tej metody zmienia si warto parametru interpolatedTime. Zmienia si ona w zakresie od
0 do 1 w zalenoci od tego, w jakim momencie si znajdujemy podczas 2,5-sekundowego
cyklu animacji, ustawionego na etapie jej inicjalizacji. Kiedy warto parametru interpolatedTime
wynosi 1, znajdujemy si na kocu animacji.
Naszym kolejnym zadaniem jest zmiana macierzy transformacji, dostpnej poprzez obiekt
transformacji t, umieszczony w metodzie applyTransformation. Najpierw naley uzyska
dostp do macierzy i zmieni jej wartoci. Po narysowaniu nowego widoku zadziaa rwnie
zmodyfikowana macierz. W dokumentacji interfejsw API dotyczcej klasy android.graphics.
Matrix mona znale opis wielu metod dostpnych w obiekcie Matrix:
http://developer.android.com/reference/android/graphics/Matrix.html
W kodzie z listingu 16.16 zmian macierzy transformacji zajmuje si poniszy wiersz:
matrix.setScale(interpolatedTime, interpolatedTime);

Metoda setScale zawiera dwa parametry s to wspczynniki skali w osiach x oraz y. Poniewa wartoci parametru interpolatedTime mieszcz si w zakresie od 0 do 1, mona go
zastosowa bezporednio w postaci wspczynnika skali.
Zatem na pocztku animacji wspczynnik ten wynosi 0 w obydwu kierunkach. W poowie przebiegu animacji osie x oraz y bd miay warto 0,5. Po zakoczeniu animacji widok bdzie mia
peny rozmiar, poniewa obydwa wspczynniki skali bd miay wartoci rwne 1. W wyniku tego
widok ListView jest na pocztku animacji niewielki i powiksza si do standardowego rozmiaru.
Na listingu 16.17 zosta zaprezentowany kompletny kod rdowy aktywnoci ViewAnimation
Activity zawierajcej animacj.
Listing 16.17. Kod rdowy aktywnoci animacji widoku wraz z animacj
public class ViewAnimationActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.list_layout);
setupListView();

538 Android 3. Tworzenie aplikacji


this.setupButton();
}
private void setupListView()
{
String[] listItems = new String[] {
"Element 1", "Element 2", "Element 3",
"Element 4", "Element 5", "Element 6",
};
ArrayAdapter listItemAdapter =
new ArrayAdapter(this
,android.R.layout.simple_list_item_1
,listItems);
ListView lv = (ListView)this.findViewById(R.id.list_view_id);
lv.setAdapter(listItemAdapter);
}
private void setupButton()
{
Button b = (Button)this.findViewById(R.id.btn_animate);
b.setOnClickListener(
new Button.OnClickListener(){
public void onClick(View v)
{
animateListView();
}
});
}
private void animateListView()
{
ListView lv = (ListView)this.findViewById(R.id.list_view_id);
lv.startAnimation(new ViewAnimation());
}
}

Po uruchomieniu kodu z listingu 16.17 Czytelnik zobaczy co dziwnego. Widok ListView,


zamiast rwnomiernie powiksza si od rodka ekranu, rozrasta si od lewego grnego rogu.
Wynika to z faktu, e operacje macierzy transformacji maj swj pocztek wanie w lewym
grnym rogu ekranu. Chcc uzyska zamierzony efekt, musimy najpierw przesun cay widok
w taki sposb, eby jego rodek pokrywa si ze rodkiem animacji (w lewym grnym rogu). Nastpnie wprowadzamy macierz i z powrotem przenosimy widok na waciwe miejsce.
Na listingu 16.18 wstawilimy przerobiony kod z listingu 16.16 i zaznaczylimy najistotniejsze
elementy.
Listing 16.18. Animacja widoku wykorzystujca metody preTranslate i postTranslate
public class ViewAnimation extends Animation {
float centerX, centerY;
public ViewAnimation3(){}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
centerX = width/2.0f;
centerY = height/2.0f;

Rozdzia 16 Analiza animacji dwuwymiarowej

539

setDuration(2500);
setFillAfter(true);
setInterpolator(new LinearInterpolator());
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final Matrix matrix = t.getMatrix();
matrix.setScale(interpolatedTime, interpolatedTime);
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}

Metody preTranslate oraz postTranslate konfiguruj macierz przed operacj skalowania


oraz po tej operacji. Jest to proces rwnowany utworzeniu zespou trzech macierzy transformacji. Nastpujcy kod:
matrix.setScale(interpolatedTime, interpolatedTime);
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);

jest rwnowany instrukcjom:


przejd do innego rodka
skaluj
przejd do oryginalnego rodka

Taki wzorzec metod pre i post jest stosowany bardzo czsto. Podobne wyniki mona osign
za pomoc innych metod klasy Matrix, ta technika jest jednak najpopularniejsza a do tego
jest zwiza. Pozostae techniki rwnie zostan omwione pod koniec rozdziau.
Co waniejsze, klasa Matrix umoliwia nie tylko skalowanie widoku, lecz rwnie przenoszenie
go za pomoc metod translate oraz zmian jego orientacji za pomoc metod rotate. Mona
sprawdzi te metody i przekona si, jak wygldaj ich efekty. W rzeczywistoci wszystkie
animacje omwione w podrozdziale Animacja ukadu graficznego s implementowane wewntrznie za pomoc metod klasy Matrix.

Zastosowanie klasy Camera


do symulowania gbi w obrazie dwuwymiarowym
Pakiet graficzny w Androidzie zawiera jeszcze jedn klas zwizan z animacj a dokadniej
z transformacj klas Camera. Mona j wykorzysta do symulowania gbi poprzez rzutowanie obrazu dwuwymiarowego, poruszajcego si w przestrzeni trjwymiarowej po paszczynie.
Moemy na przykad wysa nasz widok ListView o 10 pikseli w gb ekranu po osi z i obrci
j o 30 stopni wok osi y. Na listingu 16.19 podajemy przykad modyfikowania macierzy za
pomoc klasy Camera:
Listing 16.19. Zastosowanie klasy Camera
...
public class ViewAnimation extends Animation {
float centerX, centerY;
Camera camera = new Camera();
public ViewAnimation1(float cx, float cy){

540 Android 3. Tworzenie aplikacji


centerX = cx;
centerY = cy;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
setDuration(2500);
setFillAfter(true);
setInterpolator(new LinearInterpolator());
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
applyTransformationNew(interpolatedTime,t);
}
protected void applyTransformationNew(float interpolatedTime, Transformation t)
{
final Matrix matrix = t.getMatrix();
camera.save();
camera.translate(0.0f, 0.0f, (1300 - 1300.0f * interpolatedTime));
camera.rotateY(360 * interpolatedTime);
camera.getMatrix(matrix);
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
camera.restore();
}
}

Animacja widoku ListView przebiega tu w nastpujcy sposb: najpierw jest on umieszczony


w odlegoci 1300 pikseli od ekranu po osi z, a nastpnie wraca do paszczyzny, w ktrej o
z przyjmuje warto 0. W midzyczasie zostaje on rwnie obrcony od 0 do 360 stopni wok
osi y. Zobaczmy, w jaki sposb w kodzie jest zdefiniowane to zachowanie, opisane w poniszej
metodzie:
camera.translate(0.0f, 0.0f, (1300 - 1300.0f * interpolatedTime));

Metoda ta powoduje, e obiekt camera przesuwa si w taki sposb, i przy wartoci 0 parametru interpolatedTime (pocztek animacji) warto z bdzie wynosia 1300. Podczas trwania
animacji warto z bdzie systematycznie malaa a do samego koca, gdy warto parametru
interpolatedTime wyniesie 1, a tym samym warto parametru z bdzie rwna 0.
Metoda camera.rotateY(360 * interpolatedTime) wykorzystuje moliwo obracania bryy
w trjwymiarze wok wybranej osi przez obiekt camera. Na pocztku animacji jej warto
wynosi 0. Na kocu animacji przybierze warto 360.
Metoda camera.getMatrix(matrix) pobiera operacje dotychczas wykonane na obiekcie
Camera i narzuca je przekazanej macierzy transformacji. W tym momencie klasa matrix posiada wszystkie translacje potrzebne do uzyskania kocowego efektu, zapewnione przez klas
Camera. Teraz klasa Camera schodzi z widoku (niezamierzona gra sw), poniewa w macierzy
zostay zaimplementowane wszystkie niezbdne operacje. Wykonujemy teraz operacje pre i post
w celu przesunicia rodka widoku i sprowadzenia go z powrotem. Na koniec przywracamy
obiekt Camera do pierwotnego, uprzednio zachowanego stanu.
Po wstawieniu kodu do naszego przykadu zobaczymy kontrolk ListView zbliajc si ze
rodka widoku w stron uytkownika, przy okazji wirujc, dokadnie tak jak zaplanowalimy.

Rozdzia 16 Analiza animacji dwuwymiarowej

541

Cz naszej analizy wicej si z animacj widoku dotyczya sposobu animowania dowolnego


widoku poprzez rozszerzenie klasy Animation i zastosowanie jej wobec tego widoku. Poza
modyfikowaniem matryc (bezporednio i za pomoc klasy Camera) klasa Animation umoliwia te wykrywanie poszczeglnych etapw animacji. Wanie tym si teraz zajmiemy.

Analiza interfejsu AnimationListener


Android wykorzystuje interfejs nasuchujcy AnimationListener do monitorowania zdarze
animacji (listing 16.20). Moemy nasuchiwa tych zdarze poprzez zaimplementowanie interfejsu AnimationListener i skonfigurowanie tej implementacji wobec klasy Animation.
Listing 16.20. Implementacja interfejsu AnimationListener
public class ViewAnimationListener
implements Animation.AnimationListener {
private ViewAnimationListener(){}
public void onAnimationStart(Animation animation)
{
Log.d("Przykadowa animacja", "onAnimationStart");
}
public void onAnimationEnd(Animation animation)
{
Log.d("Przykadowa animacja", "onAnimationEnd");
}
public void onAnimationRepeat(Animation animation)
{
Log.d("Przykadowa animacja", "onAnimationRepeat");
}
}

Klasa ViewAnimationListener suy jedynie do tworzenia dziennikw komunikatw. Moemy


zaktualizowa metod animateListView w naszym przykadzie animacji widoku (listing 16.17),
eby doczy obiekt nasuchujcy animacj:
private void animateListView()
{
ListView lv = (ListView)this.findViewById(R.id.list_view_id);
ViewAnimation animation = new ViewAnimation();
animation.setAnimationListener(new ViewAnimationListener()):
lv.startAnimation(animation);
}

Kilka uwag na temat macierzy transformacji


Jak pokazalimy w tym rozdziale, macierze stanowi podstaw przeksztacania widokw
i przetwarzania animacji. Teraz omwimy w skrcie niektre kluczowe metody klasy Matrix.
Poniej zostay wymienione podstawowe operacje na macierzach:
matrix.reset();
matrix.setScale();
matrix.setTranslate()
matrix.setRotate();
matrix.setSkew();

542 Android 3. Tworzenie aplikacji


Pierwsza operacja przeksztaca macierz do postaci macierzy jednostkowej, ktra po zastosowaniu nie wprowadza zmian w widoku. Operacja setScale jest odpowiedzialna za zmian
rozmiaru, setTranslate powoduje przesunicie pozycji obiektu imitujce ruch, a setRotate
suy do zmiany orientacji. Operacja setSkew pozwala na wykrzywienie widoku.
Mona powiza ze sob macierze lub je wsplnie powiela, aby utworzy efekt zoony z wielu
transformacji. Rozpatrzmy nastpujcy przykad, w ktrym m1, m2 oraz m3 s macierzami
jednostkowymi:
m1.setScale();
m2.setTranlate()
m3.concat(m1,m2)

Transformacja widoku przez macierz m1 i nastpujca po niej transformacja widoku przez


macierz m2 s tosame z transformacj tego samego widoku przez macierz m3. Zwrmy uwag,
e metody typu set zastpuj poprzednie transformacje, natomiast m3.concat(m1,m2) nie jest
tym samym co m3.concat(m2,m1).
Pokazalimy ju sposb postpowania podczas stosowania metod preTranslate oraz post
Translate wobec zmiany macierzy transformacji. W rzeczywistoci metody pre i post nie
s przeznaczone wycznie dla operacji translate, lecz tego typu odmiany s dostpne dla
kadego rodzaju metod transformacji typu set. Ostatecznie metoda preTranslate, taka jak
m1.preTranslate(m2), jest rwnowana operacji:
m1.concat(m2,m1)

W analogiczny sposb metoda m1.postTranslate(m2) jest tosama operacji:


m1.concat(m1,m2)

Po rozszerzeniu ekwiwalentem poniszego kodu:


matrix.setScale(interpolatedTime, interpolatedTime);
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);

jest:
Matrix matrixPreTranslate = new Matrix();
matrixPreTranslate.setTranslate(-centerX, -centerY);
Matrix matrixPostTranslate = new Matrix();
matrixPostTranslate.setTranslate(cetnerX, centerY);
matrix.concat(matrixPreTranslate,matrix);
matrix.postTranslate(matrix,matrixPostTranslate);

Odnoniki
Poniej prezentujemy przydatne odnoniki do materiaw, dziki ktrym jeszcze lepiej zrozumiemy koncepcje zawarte w tym rozdziale:
http://developer.android.com/reference/android/view/animation/package-summary.html
znajdziemy tu rnorodne interfejsy zwizane z animacj, w tym rwnie
interpolatory.

Rozdzia 16 Analiza animacji dwuwymiarowej

543

http://developer.android.com/guide/topics/resources/animation-resource.html
omwienie znacznikw XML stosowanych w rnych odmianach animacji.
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu projekt do pobrania,
przygotowany specjalnie do tego rozdziau. Jest to plik umieszczony w katalogu
ProAndroid3_R16_Animacje.

Podsumowanie
W tym rozdziale zaprezentowalimy ciekawy sposb uatrakcyjnienia interfejsu uytkownika poprzez zastosowanie animacji. Omwilimy wszystkie podstawowe typy animacji obsugiwane
w Androidzie: animacj poklatkow, animacj ukadu graficznego oraz animacj widoku. Opisalimy take dodatkowe pojcia dotyczce animacji, midzy innymi interpolatory i macierze
transformacji.
Skoro Czytelnik pozna ju podstawy, proponujemy przejrze przykadowe interfejsy API, udostpnione w zestawie Android SDK, aby przeanalizowa pliki XML definiujce rne typy animacji. Poruszymy jeszcze temat animacji w rozdziale 20., powiconym rysowaniu i animowaniu za pomoc technologii OpenGL. Natomiast w rozdziale 29. moemy si zapozna
z oglnym omwieniem animacji opartej na waciwociach, stosowanej wraz z fragmentami.

544 Android 3. Tworzenie aplikacji

R OZDZIA

17
Analiza usug
wykorzystujcych mapy
i dane o lokalizacji

W niniejszym rozdziale zajmiemy si usugami systemu Android wykorzystujcymi mapy oraz dane o lokalizacji urzdzenia. S to jedne z najciekawszych elementw zestawu Android SDK. Ta cz rodowiska SDK zawiera interfejsy API
umoliwiajce projektantom aplikacji wywietlanie map oraz manipulowanie
nimi, a take uzyskiwanie informacji o lokalizacji urzdzenia w czasie rzeczywistym. Interfejsy te posiadaj wiele innych fascynujcych funkcji.
Usugi wykorzystujce dane o lokalizacji urzdzenia skadaj si z dwch zasadniczych czci: interfejsw API map oraz interfejsw API lokalizacji. Kady z tych interfejsw jest umieszczony w oddzielnym pakiecie. I tak pakiet map to com.google.
android.maps, natomiast pakiet lokalizacji nosi nazw android.location. Interfejsy
map zawieraj narzdzia pozwalajce na wywietlanie map oraz manipulowanie
nimi. Mona na przykad przyblia oraz przesuwa widok, zmienia tryb wywietlania mapy (z widoku satelitarnego na widok ulic), nakada na map wasne informacje i tak dalej. Z drugiej strony dostpne s dane systemu GPS (ang. Global System
Positioning globalny system ustalania pooenia geograficznego) oraz informacje
o pooeniu geograficznym uzyskiwane w czasie rzeczywistym, ktre s dostarczane
poprzez pakiet lokalizacji.
Interfejsy te czsto cz si poprzez internet z usugami dostpnymi na serwerach
firmy Google. Zatem jeeli te usugi maj dziaa, zazwyczaj trzeba zapewni dostp
do internetu. Ponadto naley zaakceptowa warunki korzystania z usug, zanim bdzie mona rozpocz projektowanie aplikacji uywajcych interfejsw API tych
usug. Naley uwanie przeczyta te warunki; firma Google ogranicza zastosowanie
danych usug. Na przykad mona udostpni informacje o pooeniu dla uytku
osobistego uytkownikw, jednak pewne zastosowania komercyjne, jak rwnie
aplikacje wspomagajce zautomatyzowan kontrol pojazdw s zabronione. Warunki korzystania z usug zostan wywietlone podczas procesu rejestracji w celu
uzyskania klucza interfejsu API.

546 Android 3. Tworzenie aplikacji


W niniejszym rozdziale omwimy obydwa rodzaje pakietw. Zaczniemy od interfejsw map
i pokaemy, w jaki sposb mona wykorzysta mapy w aplikacji. Jak si przekonamy, praca
z mapami w Androidzie ogranicza si do stosowania kontrolki interfejsu UI MapView oraz klasy
MapActivity, wraz z interfejsami map API zintegrowanymi z serwerem Google Maps. Zademonstrujemy take, w jaki sposb mona umieszcza wasne informacje na wywietlanej mapie
oraz jak wywietla biece pooenie urzdzenia na mapie. W nastpnej czci zajmiemy si
usugami lokalizacji, ktre rozwijaj koncepcje wykorzystania map w usugach. Zaprezentujemy
moliwoci klasy Geocoder oraz usugi LocationManager. Poruszymy take tematyk wtkowania bardzo istotn w przypadku tych interfejsw API.

Pakiet do pracy z mapami


Jak ju wspomnielimy, interfejsy API map s jednym ze skadnikw lokalizacyjnych usug Androida. Pakiet ten zawiera wszelkie elementy niezbdne do wywietlenia mapy na ekranie,
umoliwienia uytkownikowi dostosowywania jej (na przykad zmiany skali), wywietlania wasnych informacji w grnej czci mapy i tak dalej. Pierwszym etapem pracy z tym pakietem jest
wywietlenie mapy. W tym celu wykorzystamy klas widoku MapView. Zanim jednak bdziemy
mogli z ni pracowa, musimy przeprowadzi odpowiednie przygotowania. W szczeglnoci
musimy uzyska od firmy Google klucz interfejsu API mapy. Klucz interfejsu API mapy pozwala Androidowi na nawizanie cznoci z usug Google Maps w celu uzyskania danych
mapy. W nastpnym punkcie przedstawiamy sposb uzyskania takiego klucza.

Uzyskanie klucza interfejsu API mapy od firmy Google


Pierwsz wan informacj dotyczc klucza interfejsu API mapy jest to, e w rzeczywistoci potrzebne bd dwa klucze: jeden do etapu projektowego na emulatorze, a drugi dla produktu kocowego (pracujcego na urzdzeniu fizycznym). Wynika to z faktu, i certyfikat potrzebny do uzyskania klucza bdzie inny dla wersji testowej i wersji kocowej (wyjanilimy
to w rozdziale 10.).
Na etapie projektowania wtyczka ADT generuje plik .adt i wdraa go na emulator. Poniewa
plik ten musi by podpisany za pomoc certyfikatu, na tym etapie zostaje wykorzystany certyfikat testowy. W momencie przygotowywania aplikacji do uytku najprawdopodobniej bdziemy
chcieli skorzysta z wasnorcznie utworzonego certyfikatu do podpisania pliku .apk. Dobra
wiadomo jest taka, e otrzymuje si jeden klucz interfejsu API mapy przeznaczony do wersji
testowej oraz drugi klucz dla wersji kocowej, a ich zamiana przed eksportowaniem wersji
ostatecznej nie stanowi problemu.
Aby uzyska taki klucz, potrzebny jest certyfikat sucy do podpisania aplikacji (w przypadku
emulatora certyfikat testowy). Uzyskujemy skrt MD5 certyfikatu, a nastpnie wprowadzamy
go w witrynie Google, co spowoduje wygenerowanie odpowiadajcego mu klucza.
Najpierw musimy znale certyfikat testowy, wygenerowany i przechowywany przez rodowisko Eclipse. Jego dokadn lokalizacj moemy odszuka w programie Eclipse. Z menu Preferences przechodzimy do opcji Android/Build. Pooenie certyfikatu testowego jest wywietlone
w polu Default debug keystore, zaprezentowanym na rysunku 17.1 (w rozdziale 2. zostay
umieszczone informacje dotyczce lokalizacji menu Preferences).

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

547

Rysunek 17.1. Lokalizacja certyfikatu testowego

eby uzyska skrt MD5, moemy uruchomi narzdzie keytool wraz z opcj list, zgodnie
z poniszym poleceniem:
keytool -list -alias androiddebugkey keystore "PENA CIEKA PLIKU
debug.keystore" storepass android -keypass android

Warto zauway, e parametr alias testowego magazynu kluczy posiada warto android
debugkey. Haso do magazynu kluczy brzmi android, podobnie jak haso klucza prywatnego.
Po uruchomieniu powyszego polecenia aplikacja keytool wywietli skrt (rysunek 17.2).

Rysunek 17.2. Dane wyjciowe opcji list w narzdziu keytools (skrt MD5 zosta celowo zamazany)

Teraz wklejamy uzyskany skrt MD5 certyfikatu testowego w odpowiednie pole na stronie Google:
http://code.google.com/android/maps-api-signup.html
Nastpnie trzeba przeczyta warunki korzystania z usug. Jeeli s do zaakceptowania, naley
wcisn przycisk Generate API Key, aby uzyska klucz interfejsu API map dla usugi Google
Maps, odpowiadajcy skrtowi MD5. Klucz ten jest od razu aktywny, zatem mona ju teraz
za jego pomoc uzyska dostp do danych map z serwisu Google. Pamitajmy, e do uzyskania
klucza wymagane jest posiadanie konta Google podczas prby wygenerowania klucza zostanie wywietlony monit o zalogowanie si w serwisie Google.
Przypominamy informacje z rozdziau 10. Napisalimy tam, e jeli certyfikat testowy utraci
wano, to samo spotka uywany w tym czasie klucz interfejsu map. Jeeli zmienimy certyfikat
testowy, bdziemy musieli powtrzy powysze kroki i za pomoc nowego certyfikatu uzyska

548 Android 3. Tworzenie aplikacji


kolejny klucz interfejsu map. Taki mechanizm stanowi dobr motywacj do definiowania w tworzonym certyfikacie testowym czasu wanoci duszego ni domylny rok. W rozdziale 10.
znajdziemy instrukcje dotyczce tworzenia dugotrwaego certyfikatu testowego.
Pobawmy si teraz mapami.

Klasy MapView i MapActivity


Wiele elementw technologii wykorzystujcej mapy opiera si na kontrolce interfejsu uytkownika MapView oraz na rozszerzeniu klasy android.app.Activity nazwanym MapActivity.
Obydwie klasy wykonuj zoone zadania podczas wywietlania mapy w Androidzie oraz korzystania z niej. Podczas pracy z mapami naley pamita, e obydwie klasy musz ze sob
wsppracowa. Dokadniej mwic, eby mc korzysta z klasy MapView, naley utworzy
jej egzemplarz w klasie MapActivity. Aby z kolei ten proces si powid, potrzebny bdzie
klucz interfejsu API mapy.
W przypadku tworzenia widoku MapView za pomoc ukadu graficznego XML naley skonfigurowa waciwo android:apiKey. Z kolei w celu utworzenia tej klasy w kodzie Java musimy
przekaza klucz API mapy do konstruktora widoku MapView. Poniewa dane mapy pochodz
z serwera Google Maps, aplikacja bdzie wymagaa dostpu do internetu. Oznacza to, e bdzie
potrzebne przynajmniej nastpujce danie uprawnienia w pliku AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />

Na listingu 17.1 wpisy pliku AndroidManifest.xml umoliwiajce dziaanie aplikacji obsugujcej


mapy wyrniono pogrubion czcionk.
Listing 17.1. Znaczniki pliku AndroidManifest.xml wymagane dla aplikacji obsugujcej mapy
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<uses-library android:name="com.google.android.maps" />
<activity android:name=".MapViewDemoActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-sdk android:minSdkVersion="4" />
</manifest>

Powinnimy wprowadzi jeszcze jedn modyfikacj do pliku AndroidManifest.xml. Definicja


aplikacji obsugujcej mapy wymaga odniesienia do biblioteki map (odpowiedni wiersz na listingu 17.1 zosta rwnie oznaczony pogrubion czcionk). Przyjrzyjmy si rysunkowi 17.3.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

549

Rysunek 17.3. Kontrolka MapView w trybie widoku ulic

Na rysunku 17.3 zaprezentowano aplikacj wywietlajc map w trybie widoku ulic. Wida take,
w jaki sposb mona zmienia skal mapy oraz jak zmienia jej tryb wywietlania. Ukad graficzny XML tej aplikacji jest ukazany na listingu 17.2.
Na kocu rozdziau zamieszczamy adres URL, pod ktrym moemy pobra projekty
przygotowane dla tego rozdziau. Bdziemy mogli dziki temu bezporednio
zaimportowa te projekty do rodowiska Eclipse.
Listing 17.2. Ukad graficzny XML aplikacji demonstracyjnej MapViewDemo
<?xml version="1.0" encoding="utf-8"?>

<!-- Plik ten znajduje si w /res/layout/mapview.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button android:id="@+id/zoomin" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="+"
android:onClick="myClickHandler" android:padding="12px" />
<Button android:id="@+id/zoomout" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="-"
android:onClick="myClickHandler" android:padding="12px" />
<Button android:id="@+id/sat" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Satelita"
android:onClick="myClickHandler" android:padding="8px" />

550 Android 3. Tworzenie aplikacji


<Button android:id="@+id/street" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Ulice"
android:onClick="myClickHandler" android:padding="8px" />
<Button android:id="@+id/traffic" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Ruch"
android:onClick="myClickHandler" android:padding="8px" />
<Button android:id="@+id/normal" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Normalny"
android:onClick="myClickHandler" android:padding="8px" />
</LinearLayout>
<com.google.android.maps.MapView
android:id="@+id/mapview" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:clickable="true"
android:apiKey="TUTAJ UMIESZCZAMY KLUCZ INTERFEJSU API MAPY" />
</LinearLayout>

Jak wida na listingu 17.2, nadrzdny meneder LinearLayout zawiera podrzdny meneder
LinearLayout oraz widok MapView. W podrzdnym menederze LinearLayout zostay umieszczone przyciski, widoczne u gry ekranu na rysunku 17.3. Naley rwnie pamita, eby zaktualizowa warto parametru android:apiKey wartoci swojego klucza interfejsu API mapy.
Kod naszej przykadowej aplikacji obsugujcej mapy zosta umieszczony na listingu 17.3.
Listing 17.3. Klasa rozszerzajca MapActivity, wczytujca ukad graficzny XML
// Jest to plik MapViewDemoActivity.java
import android.os.Bundle;
import android.view.View;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
public class MapViewDemoActivity extends MapActivity
{
private MapView mapView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
mapView = (MapView)findViewById(R.id.mapview);
}
public void myClickHandler(View target) {
switch(target.getId()) {
case R.id.zoomin:
mapView.getController().zoomIn();
break;
case R.id.zoomout:
mapView.getController().zoomOut();

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

551

break;
case R.id.sat:
mapView.setSatellite(true);
break;
case R.id.street:
mapView.setStreetView(true);
break;
case R.id.traffic:
mapView.setTraffic(true);
break;
case R.id.normal:
mapView.setSatellite(false);
mapView.setStreetView(false);
mapView.setTraffic(false);
break;
}

// Poniszy wiersz nie powinien by wymagany, jest jednak inaczej,


// przynajmniej a do wersji Froyo (Android 2.2)
mapView.postInvalidateDelayed(2000);
}
@Override
protected boolean isLocationDisplayed() {
return false;
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
}

Na listingu 17.3 wida, e wywietlanie widoku MapView za pomoc metody onCreate() nie
rni si od wywietlania innych typw kontrolek: konfigurujemy widok treci interfejsu uytkownika w pliku ukadu graficznego zawierajcego widok MapView, a pozostae zadania s
wykonywane przez ten widok automatycznie. O dziwo, obsuga funkcji zmiany skali mapy jest
rwnie bardzo prosta. Aby zmieni skal mapy wywietlanej w widoku, uywa si klasy
MapController widoku MapView. Najpierw wywoujemy metod mapView.getController(),
a nastpnie stosujemy metod zoomIn() dla zmniejszania skali i metod zoomOut() dla jej
zwikszania. Taki sposb pracy jest jednopoziomowy; uytkownik musi wielokrotnie powtarza czynno, aby dalej zwiksza lub zmniejsza skal ogldanej mapy.
Rwnie prosto mona zmieni tryb wywietlania mapy. Widok MapView obsuguje kilka trybw:
Domylny jest widok standardowy mapy.
W trybie widoku ulic zostaje naoona na map dodatkowa warstwa, dziki ktrej
mona oglda zdjcia ulic zaznaczonych niebieskim obrysem. Zdjcia te zostay
wykonane za pomoc kamer, ktre umieszczono na pojazdach poruszajcych si po
tych ulicach. Naley jednak pamita, e sama kontrolka MapView nie wywietla zdj
w widoku ulic. Do tego jest potrzebna odrbna kontrolka widoku. Temat ten
zostanie dokadniej omwiony w rozdziale 25.
W trybie satelitarnym s wywietlane rzeczywiste zdjcia lotnicze naoone na mapy,
dziki ktrym mona obserwowa dachy budynkw, korony drzew, drogi i tak dalej.

552 Android 3. Tworzenie aplikacji

W trybie ruchu ulicznego zaznacza si na mapie kolorowymi liniami, ktre ulice s


atwo przejezdne, a ktre s zakorkowane. Tryb ten jest obsugiwany jedynie dla
najwikszych arterii1.

Aby zmienia tryby, naley wywoa waciw metod ustawiajc, podajc warto true.
W pewnych przypadkach ustawienie jednego trybu wyczy inny tryb. Na przykad nie mona
jednoczenie ustawi trybu widoku ulic z trybem ruchu ulicznego w takim przypadku po
wybraniu trybu ruchu ulicznego tryb widoku ulic zostanie automatycznie wyczony. eby
wyczy dany tryb, naley przydzieli mu warto false. Wkrtce zajmiemy si omawianiem
klasy Overlay, teraz jednak wystarczy wiedzie, e tryb ruchu ulicznego oraz tryb widoku ulic
nie korzystaj z tych obiektw.
Instrukcja mapView.postInvalidateDelayed(2000) umoliwia uniknicie problemu
wynikajcego z korzystania z trybw widoku ulic oraz ruchu ulicznego. Problem ten
wie si z wewntrznym wykorzystywaniem wtkw do pobierania danych pozwalajcych
na wywietlanie linii w tych dwch trybach. Wicej informacji znajdziemy na stronie
http://code.google.com/p/android/issues/detail?id=10317, dotyczcej problemu
numer 10317.

Aby przesuwa map, naley dla widoku MapView w pliku XML skonfigurowa atrybut
android:clickable="true" w przeciwnym wypadku bdzie mona wycznie zmniejsza
i zwiksza skal mapy. W kodzie Java mona tego dokona za pomoc wywoania metody
setClickable(true) w widoku MapView.
Ostatnia rzecz, ktr mona powiedzie o naszym przykadowym projekcie, dotyczy dwch
metod: isLocationDisplayed() i isRouteDisplayed(). W dokumentacji tych metod widnieje informacja, e s one wymagane przez warunki korzystania z usug firmy Google, chocia w trakcie uzyskiwania klucza interfejsu map nie ma w zatwierdzanym dokumencie
adnej wzmianki na ich temat. Nie jestemy prawnikami, zalecamy jednak uwzgldnienie tych
metod. Aplikacja musi przekazywa serwerowi map wartoci true lub false w celu okrelenia, czy pooenie geograficzne urzdzenia jest wywietlane lub czy s wywietlane informacje o trasie, na przykad wyznaczanie trasy samochodowej.
Przyznajemy, e w Androidzie ilo kodu potrzebnego do wywietlenia mapy oraz zaimplementowania funkcji zmiany skali i trybw przegldania jest minimalna (listing 17.3). Istnieje
jednak jeszcze prostszy sposb wstawienia kontrolek sucych do zmieniania skali mapy.
Przyjrzyjmy si plikowi XML ukadu graficznego oraz kodowi na listingu 17.4.
Listing 17.4. Prostsza zmiana skali wywietlanej mapy
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/mapview.xml -->


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.google.android.maps.MapView android:id="@+id/mapview"
android:layout_width="fill_parent"

Tryb widoku ulicznego nie obejmuje Polski przyp. tum.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

553

android:layout_height="wrap_content"
android:clickable="true"
android:apiKey="TUTAJ UMIESZCZAMY KLUCZ INTERFEJSU API MAPY"
/>
</RelativeLayout>
public class MapViewDemoActivity extends MapActivity
{
private MapView mapView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
mapView = (MapView)findViewById(R.id.mapview);
mapView.setBuiltInZoomControls(true);
}
@Override
protected boolean isLocationDisplayed() {
return false;
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
}

Rnica pomidzy listingami 17.4 a 17.3 polega na przeksztaceniu ukadu graficznego naszego
widoku w taki sposb, eby wykorzystywa meneder RelativeLayout. Usunlimy wszystkie
kontrolki odpowiedzialne za zmian skali oraz trybu wywietlania mapy. Caa magia znajduje
si w kodzie Java, a nie w ukadzie graficznym. Kontrolka MapView zawiera ju kontrolki umoliwiajce zmian skali ogldanej mapy. Wystarczy je wczy za pomoc metody setBuilt
InZoomControls(). Na rysunku 17.4 zostay pokazane domylne kontrolki zmiany skali mapy
w widoku MapView.
Teraz dowiedzmy si, w jaki sposb dodawa wasne dane do mapy.

Dodawanie znacznikw za pomoc nakadek


Usuga Google Maps posiada funkcj pozwalajc na umieszczanie wasnych informacji na
mapie. Mona j przetestowa, wyszukujc na przykad pizzerie w swoim miecie: serwis Google
Maps umieszcza ikony pinezki lub dymki informacyjne nad miejscami speniajcymi kryteria
wyszukiwania. Jest to moliwe, poniewa Google Maps dopuszcza nakadanie wasnej warstwy
na map. W Androidzie istnieje kilka klas usprawniajcych nakadanie takich warstw. Najwaniejsz klas tego typu jest Overlay, mona jednak rwnie dobrze korzysta z rozszerzenia
tej klasy, nazwanego ItemizedOverlay. Na listingu 17.5 zosta umieszczony przykad. Moemy w tym projekcie wykorzysta plik ukadu graficznego zaprezentowany na listingu 17.4.

554 Android 3. Tworzenie aplikacji

Rysunek 17.4. Wbudowane kontrolki widoku MapView


Listing 17.5. Zaznaczanie punktw na mapie za pomoc klasy ItemizedOverlay
import java.util.ArrayList;
import java.util.List;
import
import
import
import

android.graphics.Canvas;
android.graphics.drawable.Drawable;
android.os.Bundle;
android.widget.LinearLayout;

import
import
import
import
import

com.google.android.maps.GeoPoint;
com.google.android.maps.ItemizedOverlay;
com.google.android.maps.MapActivity;
com.google.android.maps.MapView;
com.google.android.maps.OverlayItem;

public class MappingOverlayActivity extends MapActivity {


private MapView mapView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
mapView = (MapView) findViewById(R.id.mapview);
mapView.setBuiltInZoomControls(true);
Drawable marker=getResources().getDrawable(R.drawable.mapmarker);
marker.setBounds( (int) (-marker.getIntrinsicWidth()/2),
-marker.getIntrinsicHeight(),
(int) (marker.getIntrinsicWidth()/2),
0);

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

555

InterestingLocations funPlaces = new InterestingLocations(marker);


mapView.getOverlays().add(funPlaces);
GeoPoint pt = funPlaces.getCenterPt();
int latSpan = funPlaces.getLatSpanE6();
int lonSpan = funPlaces.getLonSpanE6();
Log.v("Overlays", "Rozpietosc szerokosci geog. wynosi " + latSpan);
Log.v("Overlays", "Rozpietosc dlugosci geog. wynosi " + lonSpan);
MapController mc = mapView.getController();
mc.setCenter(pt);
mc.zoomToSpan((int)(latSpan*1.5), (int)(lonSpan*1.5));
}
@Override
protected boolean isLocationDisplayed() {
return false;
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
class InterestingLocations extends ItemizedOverlay {
private ArrayList<OverlayItem> locations = new ArrayList<OverlayItem>();
private GeoPoint center = null;
public InterestingLocations(Drawable marker)
{
super(marker);

// tworzy zaznaczenia miejsc godnych uwagi


GeoPoint disneyMagicKingdom = new
GeoPoint((int)(28.418971*1000000),(int)(-81.581436*1000000));
GeoPoint disneySevenLagoon = new
GeoPoint((int)(28.410067*1000000),(int)(-81.583699*1000000));
locations.add(new OverlayItem(disneyMagicKingdom ,
"Magiczne Krlestwo", "Magiczne Krlestwo"));
locations.add(new OverlayItem(disneySevenLagoon ,
"Laguna Siedmiu Mrz", "Laguna Siedmiu Mrz"));
populate();
}

// Dodalimy t metod w celu odnalezienia rodka klastra.


// Rozpoczyna od kadej krawdzi po przeciwnej stronie i porusza si wraz
// z kadym punktem. Grna krawd posiada warto +90, dolna 90,
// zachodnia 180, a wschodnia +180
public GeoPoint getCenterPt() {
if(center == null) {
int northEdge = -90000000;
int southEdge = 90000000;
int eastEdge = -180000000;

// np. 90E6 mikrostopni

556 Android 3. Tworzenie aplikacji


int westEdge = 180000000;
Iterator<OverlayItem> iter = locations.iterator();
while(iter.hasNext()) {
GeoPoint pt = iter.next().getPoint();
if(pt.getLatitudeE6() > northEdge)
northEdge = pt.getLatitudeE6();
if(pt.getLatitudeE6() < southEdge)
southEdge = pt.getLatitudeE6();
if(pt.getLongitudeE6() > eastEdge)
eastEdge = pt.getLongitudeE6();
if(pt.getLongitudeE6() < westEdge)
westEdge = pt.getLongitudeE6();
}
center = new GeoPoint((int)((northEdge +southEdge)/2),
(int)((westEdge + eastEdge)/2));
}
return center;
}
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {

// Ukrywa cie poprzez ustanowienie wartoci false w argumencie shadow


super.draw(canvas, mapView, shadow);
}
@Override
protected OverlayItem createItem(int i) {
return locations.get(i);
}
@Override
public int size() {
return locations.size();
}
}
}

Na listingu 17.5 pokazujemy, w jaki sposb mona nakada znaczniki na map. W przykadzie
tym zaznaczylimy dwa miejsca: Magiczne Krlestwo Disneya (ang. Magic Kingdom) oraz Lagun Siedmiu Mrz (ang. Seven Seas Lagoon). Obydwa miejsca znajduj si w pobliu miasta
Orlando na Florydzie (rysunek 17.5).
Aby nasza aplikacja demonstracyjna zadziaaa, musimy wprowadzi obiekt rysowany, ktry
posuy nam za znacznik. Obraz ten musi zosta zachowany w folderze /res/drawable
w taki sposb, eby identyfikator tego zasobu w metodzie getDrawable() odpowiada
nazwie pliku obrazu. W razie moliwoci postarajmy si, aby obszar otaczajcy znacznik
by przezroczysty. Kilka przykadowych znacznikw znajdziemy w zasobach projektu
utworzonego na potrzeby tego rozdziau.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

557

Rysunek 17.5. Widok MapView wraz ze znacznikami

Aby dodawa znaczniki do mapy, naley dla niej utworzy i doda do niej rozszerzenie klasy
com.google.android.maps.Overlay. Nie mona utworzy samej klasy Overlay, zatem naley
j rozszerzy lub skorzysta z jednego z rozszerze. W naszym przykadzie zaimplementowalimy klas InterestingLocations rozszerzajc klas ItemizedOverlay, ktra z kolei rozszerza klas Overlay. Klasa Overlay definiuje kontrakt dla nakadki, a klasa ItemizedOverlay
stanowi jej przydatn implementacj, pozwalajc na atwe utworzenie listy lokalizacji, ktre
mog zosta nastpnie zaznaczone na mapie.
Standardowym algorytmem dziaania jest rozszerzenie klasy ItemizedOverlay oraz dodanie
elementw miejsc wartych odwiedzenia do konstruktora. Po utworzeniu takich miejsc
mona wywoa metod populate() w klasie ItemizedOverlay. Metoda populate() zapisuje
w pamici podrcznej element bd elementy OverlayItem. Klasa ItemizedOverlay wywouje
wewntrznie metod size() suc do okrelenia liczby nakadanych elementw, a nastpnie wchodzi w ptl, wywoujc metod createItem(i) wobec kadego elementu. Metoda
createItem przekazuje gotowy element, utworzony na podstawie indeksu tablicy.
Jak wida na listingu 17.5, tworzymy po prostu punkty i wywoujemy metod populate(),
aby wywietli znaczniki na mapie. Kontrakt klasy Overlay wykonuje pozosta cz pracy. Aby
to zadziaao, metoda onCreate() aktywnoci tworzy wystpienie klasy InterestingLocations
i przekazuje obiekt rysowany, ktry bdzie wywietlany jako znaczniki. Nastpnie metoda
onCreate() dodaje wystpienie InterestingLocations do zbioru nakadek (mapView.get
Overlays().add()).
Wybrany przez nas obiekt klasy Drawable musi by przystosowany do korzystania z nakadki
ItemizedOverlay. Interfejs API map musi otrzyma wsprzdne punktu (0,0) obiektu
Drawable. Punkt ten bdzie definiowa dokadne miejsce na mapie, reprezentowane przez
znacznik. Moemy tego sami dokona za pomoc metody setBounds() klasy Drawable, ktra
zostaa przedstawiona w powyszym przykadzie. Argumenty reprezentuj wsprzdne lewej,
prawej, grnej i dolnej krawdzi; mona rwnie wykorzysta metody getIntrinsicHeight()
oraz getIntrinsicWidth() do okrelenia, jak wysoki i szeroki jest obiekt Drawable.

558 Android 3. Tworzenie aplikacji


W naszym przykadzie punkt (0,0) znajduje si porodku dolnej krawdzi. Pamitajmy, e
w systemie wsprzdnych wartoci punktw rosn od lewej do prawej strony i z gry do dou.
Z tego wanie powodu wsprzdna grnej krawdzi musi przybra warto ujemn.
Klasa ItemizedOverlay posiada kilka metod uatwiajcych definiowanie krawdzi w obiektach
typu Drawable. Nale do nich metody boundCenterBottom() oraz boundCenter(). Pierwsza z wymienionych metod pozwala na dokadnie taki efekt, jaki uzyskalimy w naszym przykadzie, czyli umieszczenie punktu o wsprzdnych (0,0) dokadnie porodku dolnej krawdzi
obiektu. Za pomoc drugiej metody umieszczamy ten punkt dokadnie w rodku obiektu. Standardowym rozwizaniem jest wywoanie jednej z tych metod w konstruktorze. Moglibymy tego
dokona, nie korzystajc z metody setBounds():
public InterestingLocations(Drawable marker)
{
super(boundCenterBottom(marker));
[ ]

Zauwamy take, e moemy korzysta z obiektw Drawable o dowolnym rozmiarze i ksztacie.


wietnym zabiegiem kosmetycznym jest zastosowanie przezroczystego ta dookoa wybranego
ksztatu. Dymki wykorzystywane w usudze Mapy Google nie s kwadratowe, a poniewa ich
to jest przezroczyste, widzimy pod jego spodem fragment mapy. Jest to dobre rozwizanie, poniewa interfejs map rysuje rwnie cie obiektu rzucany na map, a lepiej by wygldao, gdyby
cie ten przybra zarys narysowanego obiektu, a nie mia prostoktnego ksztatu (no dobrze,
cilej: ksztatu rwnolegoboku).
A jeli Czytelnik nie chce tego cienia? I temu mona zaradzi. Wystarczy, e przesonimy metod
draw() naszej rozszerzonej klasy ItemizedOverlay i nadamy argumentowi shadow warto
false w czasie wywoywania metody draw() w nadrzdnej klasie. Spjrzmy na metod draw()
w naszym przykadowym kodzie. Wspomnielimy, e obiekt Drawable, ktrym posuylimy
si do utworzenia nakadki ItemizedOverlay, jest naszym domylnym znacznikiem. Kady obiekt
OverlayItem moe posiada unikatowy znacznik ustawiany za pomoc metody setMarker()
w innym obiekcie Drawable. Moemy ustanawia te znaczniki podczas tworzenia wystpie
obiektw OverlayItem albo moemy zrobi to pniej. Przyjrzymy si jeszcze znacznikom

w rozdziale 25., w trakcie omawiania ekranw dotykowych, zademonstrujemy jeszcze take ich
inne, ciekawe moliwoci.
Gdy nakadka jest ju powizana z map, musimy jeszcze zdefiniowa waciw pozycj na mapie,
eby znaczniki byy widoczne na ekranie. W tym celu trzeba wyznaczy interesujcy nas punkt
jako rodek ekranu. Metoda getCenter() nakadki przekazuje wsprzdne pierwszego punktu,
a nie punktu rodkowego, jak mona by si spodziewa. Nakadka ItemizedOverlay sortuje
okrelone wczeniej punkty i wskazuje pierwszy z nich. Zatem w celu odnalezienia punktu
rodkowego implementujemy wasn metod getCenterPt(), ktra bdzie iterowaa przez
punkty i wyszukiwaa punkt rodkowy. Metoda setCenter() kontrolera widoku mapy okrela
rodek wywietlanego elementu, trzeba tylko przekaza jej obliczony punkt rodkowy.
Dziki metodzie setZoom() klasy MapController okrelamy skal ogldanej mapy. Metoda ta
przyjmuje wartoci od 1 do 21, gdzie 21 oznacza najmniejsz moliw skal, a 1 najwiksz.
Poniewa jednak nie wiemy dokadnie, jaka skala bdzie potrzebna, aby wywietli cay interesujcy uytkownika zakres mapy na ekranie, wprowadzamy metod zoomToSpan() klasy
MapController. Trzeba wprowadzi wysoko i szeroko prostokta, w ktrym zostanie wywietlona mapa. Na szczcie nakadka ItemizedOverlay posiada dwie metody pozwalajce
nam na okrelenie tych wymiarw: getLatSpanE6() przekazuje zakres szerokoci geograficz-

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

559

nych, a getLonSpanE6() definiuje zakres dugoci geograficznych. Mona tu wykorzysta


wartoci otrzymane w metodzie zoomToSpan(). Zwrmy uwag, e rozszerzylimy nasz prostokt o wspczynnik 1,5, czyli interesujce nas punkty nie znajduj si ju dokadnie na krawdziach mapy podczas wywietlania.
Kolejnym interesujcym aspektem, widocznym na listingu 17.5, jest tworzenie elementu
(elementw) OverlayItem. Aby utworzy element OverlayItem, wymagany jest obiekt typu
GeoPoint. Klasa GeoPoint reprezentuje pooenie geograficzne poprzez wyznaczenie dugoci i szerokoci geograficznej w mikrostopniach. W naszym przykadzie uzyskalimy wsprzdne geograficzne Magicznego Krlestwa i Laguny Siedmiu Mrz za pomoc internetowych
witryn geokodujcych (jak si wkrtce okae, geokodowanie moe posuy do przeksztacania
adresu na par wsprzdnych dugo szeroko geograficzna). Nastpnie przekonwertowalimy wsprzdne geograficzne na mikrostopnie interfejsy API akceptuj te jednostki mnoc wynik przez 1 000 000 i otrzyman liczb przeksztacajc na liczb cakowit.
Do tej pory pokazalimy, w jaki sposb umieszcza znaczniki na mapie. Moliwoci nakadek
nie s jednak ograniczone wycznie do wywietlania pinezek i chmurek informacyjnych.
Mona za ich pomoc przeprowadza inne czynnoci. Moemy na przykad wywietla animacje
wdrujce po mapie lub pokazywa symbole, na przykad frontw atmosferycznych lub burz.
Podsumowujc, Czytelnik mgby si zgodzi, e umieszczanie znacznikw na mapie nie moe
by prostsze. Ale moe jednak mogoby? Nie posiadamy bazy danych wsprzdnych geograficznych, domylamy si jednak, e moglibymy utworzy co najmniej jeden obiekt GeoPoint,
korzystajc ze zwykego adresu. Rzeczywicie tak jest. Do tego suy klasa Geocoder, ktr
zajmiemy si w nastpnych podrozdziaach.

Pakiet do obsugi danych


o pooeniu geograficznym
Pakiet android.location zawiera funkcje usug umoliwiajcych prac na danych o pooeniu
geograficznym. W tym podrozdziale zajmiemy si dwoma istotnymi elementami tego pakietu:
klas Geocoder oraz usug LocationManager. Rozpoczniemy od klasy Geocoder.

Geokodowanie w Androidzie
Jeeli mapy maj by wykorzystywane w jaki praktyczny sposb, prawdopodobnie naley
przekonwertowa adres (lub lokacj) do wsprzdnych geograficznych. Pojcie to jest znane
jako geolokalizacja, a klasa android.location.Geocoder posiada t funkcj. W rzeczywistoci
klasa Geocoder umoliwia konwersj w obydwie strony moe przeksztaci adres do wsprzdnych geograficznych oraz przetumaczy par szeroko dugo geograficzna na list
adresw. Klasa ta posiada nastpujce metody:
List<Address> getFromLocation(double latitude, double longitude,

int maxResults),

List<Address> getFromLocationName(String locationName, int maxResults,

double lowerLeftLatitude, double lowerLeftLongitude, double


upperRightLatitude, double upperRightLongitude),

List<Address> getFromLocationName(String locationName, int maxResults).

560 Android 3. Tworzenie aplikacji


Okazuje si, e przetwarzanie adresu nie zawsze zachodzi tak samo. Na przykad metody
getFromLocationName() akceptuj nazw miejsca, adres fizyczny, kod lotniska lub zwyczajow nazw. Zatem metody przekazuj nie jeden adres, a ca ich list. Z tego powodu istnieje
moliwo ograniczenia listy wynikw poprzez ustawienie wartoci maxResults w zakresie
od 1 do 5. Przyjrzyjmy si przykadowi.
Listing 17.6 przedstawia ukad graficzny XML oraz odpowiadajcy mu kod Java interfejsu
uytkownika z rysunku 17.6. eby ten przykadowy kod zadziaa, naley umieci we waciwym miejscu wasny klucz interfejsu API mapy.
Listing 17.6. Praca z klas Geocoder
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/geocode.xml -->


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent"
android:layout_alignParentBottom="true"
android:layout_height="wrap_content" android:orientation="vertical" >
<EditText android:layout_width="fill_parent" android:id="@+id/location"
android:layout_height="wrap_content" android:text="White House"/>
<Button android:id="@+id/geocodeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="Znajd lokacj"/>
</LinearLayout>
<com.google.android.maps.MapView
android:id="@+id/geoMap" android:clickable="true"
android:layout_width="fill_parent"
android:layout_height="320px"
android:apiKey="WSTAWIAMY TUTAJ KLUCZ INTERFEJSU API MAPY"
/>
</RelativeLayout>
package com.androidbook.maps.geocoding;
import java.io.IOException;
import java.util.List;
import
import
import
import
import
import

android.location.Address;
android.location.Geocoder;
android.os.Bundle;
android.view.View;
android.widget.Button;
android.widget.EditText;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
public class GeocodingDemoActivity extends MapActivity

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

{
Geocoder geocoder = null;
MapView mapView = null;
@Override
protected boolean isLocationDisplayed() {
return false;
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.geocode);
mapView = (MapView)findViewById(R.id.geoMap);
mapView.setBuiltInZoomControls(true);

// Szeroko/dugo geograficzna miasta Jacksonville na Florydzie


int lat = (int)(30.334954*1000000);
int lng = (int)(-81.5625*1000000);
GeoPoint pt = new GeoPoint(lat,lng);
mapView.getController().setZoom(10);
mapView.getController().setCenter(pt);
geocoder = new Geocoder(this);
public void onClick(View arg0) {
try {
EditText loc = (EditText)findViewById(R.id.location);
String locationName = loc.getText().toString();
List<Address> addressList =
geocoder.getFromLocationName(locationName, 5);
if(addressList!=null && addressList.size()>0)
{
int lat = (int)(addressList.get(0).getLatitude()*1000000);
int lng = (int)(addressList.get(0).getLongitude()*1000000);
GeoPoint pt = new GeoPoint(lat,lng);
mapView.getController().setZoom(15);
mapView.getController().setCenter(pt);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

561

562 Android 3. Tworzenie aplikacji

Rysunek 17.6. Geokodowanie miejsca przy znanej lokalizacji

Aby sprawdzi zastosowanie geokodowania w Androidzie, wpiszmy nazw lokacji lub jej adres
w polu EditText, a nastpnie kliknijmy przycisk Znajd lokacj. eby odnale adres lokacji,
wywoujemy metod getFromLocationName() klasy Geocoder. Lokacja moe by adresem lub
znan nazw (w naszym przypadku White House, czyli Biay Dom). Geokodowanie moe by
czasochonnym procesem, dlatego proponujemy ograniczy liczb wynikw do piciu, zgodnie
z sugesti dokumentacji Androida.
Po wywoaniu metody getFromLocationName() otrzymujemy list adresw. Nasza przykadowa
aplikacja pobiera list adresw i, jeeli zostanie jaki znaleziony, przetwarza pierwszy z nich.
Kady adres posiada wsprzdne szerokoci i dugoci geograficznej, suce do utworzenia
obiektu GeoPoint. Nastpnie otrzymujemy dostp do kontrolera mapy i przenosimy si do
wskazanego punktu. Warto poziomu przyblienia jest liczb cakowit w zakresie wartoci
od 1 do 21. Z kadym krokiem nastpuje dwukrotne przyblienie. Moglibymy zaprezentowa
okno dialogowe wywietlajce jednoczenie wiele poszukiwanych lokacji, na razie jednak pokaemy tylko przypadek zawierajcy jedno znalezione miejsce.
W naszej przykadowej aplikacji odczytujemy jedynie dugo i szeroko geograficzn zwracanego obiektu Address. W rzeczywistoci moemy otrzyma ogrom danych powizanych z obiektem tego typu, wcznie ze zwyczajow nazw miejsca, adresem, miastem, prowincj, kodem
pocztowym, pastwem, a nawet numerem telefonu i adresem URL.
W przeciwiestwie do interfejsu map, usugi geolokalizacyjne nie korzystaj z mikrostopni.
Czst przyczyn bdw jest pozostawianie nieskonwertowanych jednostek. eby
przekaza wartoci dugoci i szerokoci geograficznej obiektu Address do metody
interfejsu map, musimy je najpierw pomnoy przez 1 000 000.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

563

Naley zrozumie kilka faktw zwizanych z geokodowaniem:


Po pierwsze, nie zawsze otrzymujemy dokadny adres. Oczywicie, dokadno zwracanych
adresw zaley od danych wejciowych, zatem naley stara si przekazywa klasie
Geocoder jak najdokadniejsz nazw szukanego obiektu.
Po drugie, jeli to moliwe, warto podawa parametr maksymalnej liczby wynikw
w zakresie od 1 do 5.
Wreszcie, naley powanie zastanowi si nad przeprowadzaniem operacji
geokodowania w wtku innym ni wtek interfejsu uytkownika. Wpywaj na to
dwa czynniki. Pierwszy powd jest oczywisty: geokodowanie jest czasochonnym
procesem i nie chcemy, eby interfejs UI trwa bezczynnie podczas wyszukiwania
lokacji, co spowoduje w kocu zamknicie aktywnoci. Drugi dotyczy susznego
zaoenia, e w przypadku urzdzenia mobilnego poczenie sieciowe moe zosta
w kadej chwili zerwane oraz e takie poczenie jest sabe. Zatem naley odpowiednio
zaj si wyjtkami I/O (ang. Input/Output wejcie-wyjcie) oraz przekraczaniem
limitw czasowych. Po przetworzeniu adresw mona bdzie przekaza wyniki do wtku
interfejsu UI. Przyjrzyjmy si temu bliej.

Geokodowanie za pomoc wtkw przebiegajcych w tle


Bardzo popularnym zwyczajem jest przeznaczanie wtkw przebiegajcych w tle do przetwarzania czasochonnych operacji. Oglny algorytm dotyczy przetwarzania zdarzenia interfejsu
UI (na przykad kliknicia przycisku), ktre inicjalizuje czasochonn operacj. Za pomoc
procedury obsugi zdarze tworzy si i uruchamia nowy wtek, przeznaczony do wykonania
tej operacji. W midzyczasie wtek interfejsu UI wraca do tego interfejsu w celu zachowania
interakcji z uytkownikiem podczas przetwarzania operacji przez wtek przebiegajcy w tle.
Po zakoczeniu operacji cz interfejsu UI moe zosta zaktualizowana lub uytkownik
otrzyma powiadomienie. Wtek przebiegajcy w tle nie aktualizuje bezporednio interfejsu UI;
informuje raczej wtek interfejsu UI o koniecznoci aktualizacji. Na listingu 17.7 zosta przedstawiony omwiony algorytm na przykadzie geokodowania. Uyjemy tego samego pliku
geocode.xml co poprzednio. Nie ma rwnie przeciwwskaza co do wykorzystania stosowanego wczeniej pliku AndroidManifest.xml.
Listing 17.7. Geokodowanie w oddzielnym wtku
package com.androidbook.maps.geocodingthreads;
import java.io.IOException;
import java.util.List;
import
import
import
import
import
import
import
import
import
import

android.app.AlertDialog;
android.app.Dialog;
android.app.ProgressDialog;
android.location.Address;
android.location.Geocoder;
android.os.Bundle;
android.os.Handler;
android.os.Message;
android.view.View;
android.widget.EditText;

import com.google.android.maps.GeoPoint;

564 Android 3. Tworzenie aplikacji


import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
public class GeocodingDemoActivity extends MapActivity
{
Geocoder geocoder = null;
MapView mapView = null;
ProgressDialog progDialog=null;
List<Address> addressList=null;
@Override
protected boolean isRouteDisplayed() {
return false;
}
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.geocode);
mapView = (MapView)findViewById(R.id.geoMap);
mapView.setBuiltInZoomControls(true);

// szeroko/dugo geograficzna miasta Jacksonville na Florydzie


int lat = (int)(30.334954*1000000);
int lng = (int)(-81.5625*1000000);
GeoPoint pt = new GeoPoint(lat,lng);
mapView.getController().setZoom(10);
mapView.getController().setCenter(pt);
geocoder = new Geocoder(this);
public void doClick(View view) {
EditText loc = (EditText)findViewById(R.id.location);
String locationName = loc.getText().toString();
progDialog =
ProgressDialog.show(GeocodingDemoActivity.this,
"Przetwarzanie...", "Szukanie lokacji...", true, false);
findLocation(locationName);
}
private void findLocation(final String locationName)
{
Thread thrd = new Thread()
{
public void run()
{
try {

// wykonuje prac w tle


addressList = geocoder.getFromLocationName(locationName, 5);

// wysya komunikat do procedury obsugi zdarze w celu przetworzenia


// wynikw
uiCallback.sendEmptyMessage(0);
} catch (IOException e) {
e.printStackTrace();

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

565

}
}
};
thrd.start();
}

// procedura obsugi metody zwrotnej wtku interfejsu ui


private Handler uiCallback = new Handler()
{
@Override
public void handleMessage(Message msg)
{

// usuwa okno dialogowe


progDialog.dismiss();
if(addressList!=null && addressList.size()>0)
{
int lat = (int)(addressList.get(0).getLatitude()*1000000);
int lng = (int)(addressList.get(0).getLongitude()*1000000);
GeoPoint pt = new GeoPoint(lat,lng);
mapView.getController().setZoom(15);
mapView.getController().animateTo(pt);
}
else
{
Dialog foundNothingDlg = new
AlertDialog.Builder(GeocodingDemoActivity.this)
.setIcon(0)
.setTitle("Wyszukiwanie lokacji zakoczone niepowodzeniem")
.setPositiveButton("OK", null)
.setMessage("Nie znaleziono lokacji...")
.create();
foundNothingDlg.show();
}
}
};
}

Kod na listingu 17.7 jest zmodyfikowan wersj przykadu z listingu 17.6. Rnica polega na
tym, e w metodzie onClick() wywietlamy okno dialogowe postpw i wywoujemy metod
findLocation() (rysunek 17.7). Metoda findLocation() tworzy nastpnie nowy wtek i wywouje metod start(), czego ostatecznym wynikiem jest wywoanie metody run() tego wtku.
W tej metodzie wykorzystujemy klas Geocoder do znalezienia szukanej lokalizacji. Po zakoczeniu wyszukiwania musimy wysa komunikat do obiektu, ktry moe nawiza interakcj z wtkiem interfejsu UI, poniewa musimy zaktualizowa map. Do tego celu suy
klasa android.os.Handler. Z poziomu wtku przebiegajcego w tle wywoujemy metod
uiCallback.sendEmptyMessage(0), aby wtek interfejsu UI mg przetwarza wyniki wyszukiwania. W naszym przypadku nie musimy tak naprawd przesya adnej treci w komunikacie,
poniewa dane s wspdzielone w obiekcie addressList. Kod wywouje nastpnie procedur
metody zwrotnej, ktra usuwa okno dialogowe, a nastpnie sprawdza list addressList zwrcon przez klas Geocoder. Poprzez wywoanie zwrotne mapa zostaje nastpnie zaktualizowana
o wyniki wyszukiwania lub zostaje wywietlony alert informujcy o braku wynikw wyszukiwania. Interfejs uytkownika dla tego przykadu zosta zilustrowany na rysunku 17.7.

566 Android 3. Tworzenie aplikacji

Rysunek 17.7. Wywietlanie okna postpw w czasie przeprowadzania dugich operacji

Usuga LocationManager
Usuga LocationManager jest jedn z najwaniejszych funkcji oferowanych przez pakiet android.
location. Dziki niej dostpne staj si dwie funkcje: mechanizm pozwalajcy uzyska wsprzdne geograficzne urzdzenia oraz technologia informujca (poprzez intencj) o tym, e
urzdzenie znalazo si w okrelonym rejonie geograficznym.
W tym punkcie opiszemy tajniki dziaania usugi ServiceManager. eby korzysta z tej usugi,
naley najpierw utworzy do niej odniesienie. Na listingu 17.8 zosta ukazany wzorzec korzystania z tej usugi.
Listing 17.8. Korzystanie z usugi ServiceManager
package com.androidbook.maps.locationmanager;
import java.util.List;
import
import
import
import
import

android.app.Activity;
android.content.Context;
android.location.Location;
android.location.LocationManager;
android.os.Bundle;

public class LocationManagerDemoActivity extends Activity


{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
LocationManager locMgr =

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

567

(LocationManager)this.getSystemService(Context.LOCATION_SERVICE);
Location loc = locMgr.getLastKnownLocation(LocationManager.GPS_PROVIDER);
List<String> providerList = locMgr.getAllProviders();
}
}

Usuga LocationManager jest usug systemow. Usugi systemowe s uzyskiwane z kontekstu za


pomoc ich nazw; nie s tworzone bezporednio. Klasa android.app.Activity zawiera metod
getSystemService(), dziki ktrej mona uzyska dostp do usugi systemowej. Jak zostao
pokazane na listingu 17.8, wywoujemy metod getSystemService() i umieszczamy w niej nazw podanej usugi w tym przypadku Context.LOCATION_SERVICE.
Usuga LocationManager dostarcza szczegy o pooeniu geograficznym poprzez dostawcw
lokalizacji. Obecnie dostpne s trzy typy tych dostawcw:
Dostawcy systemu GPS uzyskuj dane o lokalizacji za pomoc Globalnego Systemu
Pozycjonowania.
Dostawcy sieciowi korzystaj z wie operatorw sieci komrkowych lub z sieci
bezprzewodowych Wi-Fi.
Dostawca pasywny przypomina aplikacj wszc, wyszukujc aktualizacje pooenia
i przekazujc je do aplikacji dajcych tego typu informacji, nawet gdy sama aplikacja
przechowujca pasywnego dostawc nie wymaga tych danych. Oczywicie, jeeli adna
aplikacja nie bdzie potrzebowaa tych danych, my rwnie ich nie otrzymamy.
Klasa LocationManager moe uzyska informacje dotyczce ostatniej znanej lokalizacji urzdzenia dziki metodzie getLastKnownLocation(). Informacja o pooeniu geograficznym
jest uzyskiwana od dostawcy, zatem parametrem tej metody jest nazwa uywanego dostawcy.
Akceptowanymi wartociami tego parametru s Location-Manager.GPS_PROVIDER, Location
Manager.NETWORK_PROVIDER oraz LocationManager.PASSIVE_PROVIDER. Aby nasza aplikacja
skutecznie zdobya informacje o lokalizacji urzdzenia, bdziemy musieli wstawi odpowiednie wpisy o uprawnieniach w pliku AndroidManifest.xml. Dla dostawcw GPS i pasywnych wymagane bdzie uprawnienie android.permission.ACCESS_FINE_LOCATION, natomiast dostawcy sieciowi mog, zalenie od potrzeb, korzysta z uprawnie android.permission.
ACCESS_FINE_LOCATION lub android.permission.ACCESS_COARSE_LOCATION. Zamy na
przykad, e nasza aplikacja bdzie korzystaa z danych GPS lub sieciowych do aktualizowania
pooenia. Poniewa dostawca GPS wymaga uprawnienia android.permission.ACCESS_
FINE_LOCATION, spenimy tym samym wymogi dostawcy sieciowego i nie bdziemy musieli definiowa uprawnienia android.permission.ACCESS_COARSE_LOCATION. Gdybymy korzystali tylko
z dostawcy sieciowego, wystarczyoby nam wprowadzenie uprawnienia android.permission.
ACCESS_COARSE_LOCATION w pliku manifecie.
Dziki wywoaniu metody getLastKnownLocation() otrzymujemy instancj klasy android.
lub warto null, jeli lokalizacja jest niedostpna. Klasa Location
uzyskuje takie informacje o pooeniu geograficznym, jak szeroko i dugo geograficzna,
data ostatniej aktualizacji pooenia, a take potencjalnie wysoko, prdko oraz peleng.
Obiekt Location moe nas rwnie poinformowa za pomoc metody getProvider(), od
ktrego dostawcy pochodzi; do wyboru mamy GPS_PROVIDER lub NETWORK_PROVIDER. Jeeli
otrzymujemy informacje o pooeniu w trybie PASSIVE_PROVIDER, w rzeczywistoci jedynie
wyszukujemy dane o aktualizacji pooenia, zatem wszystkie informacje pochodz ostatecznie
od systemu GPS lub dostawcy sieciowego.
location.Location

568 Android 3. Tworzenie aplikacji


Poniewa klasa LocationManager operuje na dostawcach, posiada interfejsy API implementujce
tych dostawcw. Na przykad mona uzyska wszystkich dostawcw poprzez wywoanie metody getAllProviders(). Wybranego dostawc uzyskujemy poprzez wywoanie metody
getProvider() oraz przekazanie nazwy dostawcy w formie argumentu (na przykad Location
Manager.GPS_PROVIDER). W tym przypadku musimy pamita, e metoda getAllProviders()
bdzie przekazywaa rwnie dostawcw, do ktrych nie mamy dostpu lub ktrzy s aktualnie
nieaktywni. Na szczcie moemy okreli stan dostawcw za pomoc innych metod, na przykad isProviderEnabled(String providerName) lub getProviders(boolean enabledOnly),
w przypadku ktrych wywoanie ich z wartoci true spowoduje pobranie wycznie dostpnych dostawcw.
Istnieje jeszcze inny sposb pobrania odpowiedniego dostawcy, mianowicie za pomoc metody
getProviders(Criteria criteria, boolean enabledOnly) klasy LocationManager. Poprzez okrelenie kryteriw aktualizacji pooenia oraz wprowadzenie wartoci true do atrybutu
enabledOnly (dziki czemu bd wynajdywani aktywni i przygotowani do dziaania dostawcy),
otrzymamy list z nazwami dostawcw, lecz bez adnych informacji na temat ich rodzajw.
Takie rozwizanie czasami bardziej si przydaje, poniewa urzdzenie moe posiada wasnego
dostawc pooenia, ktry spenia nasze wymogi bez koniecznoci jego dokadnej znajomoci.
Obiekt Criteria moe zawiera takie parametry, jak poziom dokadnoci, a take dania informacji o szybkoci, pelengu, wysokoci, kosztach oraz zapotrzebowaniu energetycznym. Jeeli
aden dostawca nie bdzie spenia naszych kryteriw, otrzymamy pust list, po czym bdziemy
mogli zrezygnowa lub zagodzi nieco wymagania i sprbowa ponownie.

Jak uaktywni dostawc pooenia?


Mona by pomyle, e istnieje prosty interfejs API, pozwalajcy na aktywacj dostawcy pooenia (na przykad systemu GPS), w przypadku gdyby nie by aktywny, a aplikacja wymagaaby
danych. Niestety, problem jest zwizany z czym innym. Aby wczy usug lokacyjn, uytkownik musi to zrobi z poziomu aplikacji Ustawienia, dostpnej w urzdzeniu. Aplikacja moe znacznie uatwi to zadanie, jeeli pozwoli uytkownikowi wywietli okno ustawie bez koniecznoci jej ukrywania. Okno ustawie lokalizacyjnych jest w rzeczywistoci aktywnoci, ktra
odpowiada na intencje. Jedyne zatem, co nasza aplikacja musi zrobi, to wywoa t aktywno
za pomoc waciwej intencji. Wymagany do tego kod moe wyglda nastpujco:
startActivityForResult(new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0);

Pamitajmy, e w celu obsugi odpowiedzi musimy zaimplementowa metod zwrotn


onActivityResult() wewntrz naszej aktywnoci (zagadnienie to zostao omwione w rozdziale 5.). Nie zapominajmy rwnie o tym, e chocia pokadamy nadziej w uytkowniku, i
uruchomi jakiego dostawc, na przykad system GPS, wcale nie musi tego zrobi. Nasza aplikacja bdzie musiaa sprawdzi, czy uytkownik uaktywni dostawc, a nastpnie w odpowiedzi
na wynik podj jakie dziaanie.

Co moemy zrobi z pooeniem?


Jak ju wspomnielimy wczeniej, obiekty Location mog nam podawa informacje o dugoci
i szerokoci geograficznej, godzin przeprowadzenia oblicze, nazw dostawcy obliczajcego
wsprzdne, a take, opcjonalnie, wysoko, szybko, peleng oraz poziom dokadnoci. Mog
si w nich rwnie znale inne informacje, w zalenoci od dostawcy. Jeeli na przykad obiekt
Location pochodzi od dostawcy GPS, otrzymamy dane typu Bundle, okrelajce liczb sateli-

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

569

tw, ktre wzito pod uwag podczas obliczenia wsprzdnych urzdzenia. Takie dodatkowe
informacje mog, ale nie musz by dostpne. Aby si dowiedzie, czy obiekt klasy Location
posiada jedn z takich wartoci, w klasie tej naley ustawi metody typu has(), ktre przekazuj warto logiczn. Przykadem moe by metoda hasAccuracy(). Przed otrzymaniem
wartoci metody getAccuracy() rozsdnie byoby najpierw wywoa metod hasAccuracy().
Location posiada rwnie inne przydatne metody, na przykad metod statyczn
distanceBetween(), dziki ktrej moemy otrzyma najkrtsz odlego pomidzy dwoma
obiektami Location. Inn metod zwizan z odlegoci jest distanceTo(), ktra bdzie
przekazywaa najmniejsz warto dystansu pomidzy biecym obiektem Location a obiektem Location przekazanym metodzie. Zwrmy uwag, e odlego jest mierzona w metrach

Klasa

oraz e pod uwag brana jest krzywizna Ziemi. Musimy te jednak mie wiadomo, e odlegoci podawane w wyniku dziaania omawianych metod nie s mierzone na przykad z perspektywy jazdy samochodem.
Jeeli potrzebujemy wskazwek dla kierowcw lub dystansu midzy dwoma punktami mierzonego jak dla jazdy samochodem, bd nam potrzebne pocztkowy i kocowy obiekt Location,
jednak do przeprowadzenia wszystkich oblicze prawdopodobnie bdziemy musieli wykorzysta usugi Google Maps JavaScript API. Przydatny moe si okaza na przykad interfejs Google
Directions, podobny nieco do omwionego w rozdziale 11. interfejsu Google Translate. Umoliwia
on aplikacji ukazanie caej trasy przejazdu od punktu pocztkowego do punktu kocowego.

Wysyanie aktualizacji pooenia do aplikacji podczas jej tworzenia


W trakcie testowania aplikacji usuga LocationManager wymaga informacji o pooeniu geograficznym, a emulator nie ma dostpu do systemu GPS ani do wie operatorw sieci komrkowych. Aby przetestowa usug LocationManager w emulatorze, bdziemy rcznie przesyali
aktualizacje danych o pooeniu z poziomu rodowiska Eclipse. Na listingu 17.9 zosta zaprezentowany prosty przykad ilustrujcy cele, jakie sobie wyznaczylimy.
Listing 17.9. Rejestrowanie aktualizacji lokacji
package com.androidbook.location.update;
import
import
import
import
import
import
import

android.app.Activity;
android.content.Context;
android.location.Location;
android.location.LocationListener;
android.location.LocationManager;
android.os.Bundle;
android.widget.Toast;

public class LocationUpdateDemoActivity extends Activity


{
LocationManager locMgr = null;
LocationListener locListener = null;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
locMgr = (LocationManager)

570 Android 3. Tworzenie aplikacji


getSystemService(Context.LOCATION_SERVICE);
locListener = new LocationListener()
{
public void onLocationChanged(Location location)
{
if (location != null)
{
Toast.makeText(getBaseContext(),
"Nowa lokacja: szeroko [" +
location.getLatitude() +
"] dugo [" + location.getLongitude()+"]",
Toast.LENGTH_SHORT).show();
}
}
public void onProviderDisabled(String provider)
{
}
public void onProviderEnabled(String provider)
{
}
public void onStatusChanged(String provider,
int status, Bundle extras)
{
}
};
}
@Override
public void onResume() {
super.onResume();
locMgr.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
0, // minTime w milisekundach

0, // minDistance w metrach
locListener);

@Override
public void onPause() {
super.onPause();
locMgr.removeUpdates(locListener);
}

Nie zamiecilimy tutaj kodu interfejsu uytkownika. Standardowy pocztkowy ukad graficzny powinien nam cakowicie wystarczy. Z tego samego powodu nie rozszerzamy klasy
MapActivity, gdy nie bdziemy wywietla adnej mapy.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

571

Jednym z gwnych zastosowa usugi LocationManager jest uzyskiwanie powiadomie o pooeniu geograficznym urzdzenia. Na listingu 17.9 pokazalimy, w jaki sposb mona zarejestrowa obiekt nasuchujcy odbierajcy zdarzenia uaktualniania pozycji. W celu zarejestrowania
obiektu nasuchujcego wywoujemy metod requestLocationUpdates() i przekazujemy jej nazw dostawcy jako parametr. Podczas zmiany pooenia geograficznego usuga LocationManager
wywouje metod onLocationChanged() obiektu nasuchujcego wraz z danymi o nowej lokalizacji. Bardzo wane jest, aby usun we waciwym czasie wszystkie rejestracje aktualizacji
pooenia. W naszym przykadzie rejestrujemy metod onResume() i usuwamy t rejestracj
w metodzie onPause(). Jeeli nie bdziemy mieli zamiaru korzysta z aktualizacji pooenia,
powinnimy powiadomi dostawc, eby ich nie przesya. Istnieje rwnie prawdopodobiestwo, e aktywno zostanie zakoczona (na przykad w przypadku obrcenia urzdzenia
i ponownego uruchomienia aktywnoci), co oznacza, e wczeniejsza aktywno moe cigle
istnie, otrzymywa aktualizacje, wywietla je w obiekcie Toast oraz zajmowa pami.
W naszym przykadzie ustalamy wartoci minimalnego czasu oraz minimalnego dystansu na 0.
W ten sposb usuga LocationManager bdzie przesyaa informacje jak najczciej. W rzeczywistej aplikacji nie byyby to zalecane ustawienia, ale dziki nim wersje testowe bd dziaay
lepiej (jest niewskazane, aby fizyczne urzdzenie zbyt czsto sprawdzao biece pooenie
geograficzne, poniewa czynno ta wyczerpuje bateri). Naley te parametry ustawi w sposb odpowiedni do sytuacji, starajc si zminimalizowa liczb powiadomie o zmianie pooenia geograficznego.
Na listingu 17.9 zostao zaprezentowane nowe narzdzie widet Toast. Jest to przydatna
aplikacja, pozwalajca na krtki czas wywietli may widok uytkownikowi. Zasania on na
moment widok biecy, a nastpnie sam znika. Jego czas trwania mona wyduy, stosujc
zamiast argumentu LENGTH_SHORT argument LENGTH_LONG.
eby przetestowa nasz przykad na emulatorze, moemy skorzysta z umieszczonego we wtyczce
ADT interfejsu DDMS (ang. Dalvik Debug Monitor Service usuga monitora sprawdzania
bdw Dalvik). Interfejs DDMS posiada ekran, za pomoc ktrego moliwe jest emulowanie
zmieniajcej si lokalizacji (rysunek 17.8).

Rysunek 17.8. Zastosowanie interfejsu DDMS w rodowisku Eclipse do przesyania emulatorowi


danych o pooeniu geograficznym

572 Android 3. Tworzenie aplikacji


Aby otworzy interfejs DDMS w rodowisku Eclipse, klikamy opcje Window/Open Perspective/
DDMS. Powinnimy ju wczeniej mie do dyspozycji widok Emulator Control, jeeli jednak
nie jest widoczny w uywanej perspektywie, przechodzimy do zakadki Window, a nastpnie
klikamy Show View/Other/Android/Emulator Control. By moe bdziemy musieli nieco przewin w d ten panel, aby uzyska dostp do kontrolek lokalizacji. Jak wida na rysunku 17.8,
w zakadce Manual interfejsu DDMS mona wysya informacje GPS dotyczce nowego pooenia
geograficznego (par wartoci: szeroko i dugo geograficzn) do emulatora. Wysanie wsprzdnych nowej lokalizacji spowoduje uruchomienie metody onLocationChanged() w obiekcie nasuchujcym, dziki czemu uytkownik zostanie powiadomiony o zmianie pooenia
geograficznego.
Istnieje moliwo przesania emulatorowi danych o nowej lokalizacji geograficznej za pomoc
kilku innych technik, co wida w interfejsie DDMS (rysunek 17.8). Na przykad interfejs
DDMS pozwala na przesanie pliku GPX (ang. GPS Exchange Format format wymiany danych GPS) lub pliku KML (ang. Keyhole Markup Language jzyk znacznikw formatu Keyhole). Przykadowe pliki GPX mona pobra z nastpujcych adresw:
http://www.topografix.com/gpx_resources.asp,
http://tramper.co.nz/?view=gpxFiles,
http://www.gpxchange.com/.
W analogiczny sposb mona wykorzysta ponisze zasoby do pobrania istniejcych lub
utworzenia nowych plikw KML:
http://bbs.keyhole.com/,
http://code.google.com/apis/kml/documentation/kml_tut.html.
Na niektrych stronach umieszczone s pliki KMZ. S to skompresowane pliki KML,
zatem wystarczy je rozpakowa, aby uzyska pliki KML. Niektre pliki KML wymagaj
zdefiniowania ich wartoci przestrzeni nazw XML w celu uruchomienia ich w interfejsie
DDMS. Jeeli nie mona uruchomi danego pliku KML, naley upewni si, e posiada
wpis <kml xmlns="http://earth.google.com/kml/2.x">.

Mona wczyta plik GPX lub KML do emulatora oraz skonfigurowa szybko, z jak bdzie
on odtwarza te pliki (rysunek 17.9). W efekcie emulator bdzie przesya aktualizacje lokalizacji
do aplikacji, bazujc na ustalonej szybkoci symulowanego poruszania si. Jak wida na rysunku
17.9, plik GPX posiada w grnej czci ekranu punkty, a w dolnej czci cieki. Nie mona
odtwarza punktw, jednak po klikniciu jednego z nich zostanie on wysany do emulatora.
Po klikniciu cieki stanie si dostpny przycisk Play, sucy do odtwarzania punktw.
Doszy do nas informacje, e nie wszystkie pliki GPX s odczytywane przez emulator.
Jeeli po wczytaniu pliku GPX nic si nie dzieje, naley sprbowa wykorzysta inny
plik z innego rda.

Na listingu 17.9 znajduje si jeszcze kilka metod klasy LocationManager, o ktrych do tej pory
nie wspomnielimy. S to metody zwrotne onProviderDisabled(), onProviderEnabled()
oraz onStatusChanged(). W naszym przykadzie nie wykorzystalimy ich potencjau, jednak
w prawdziwej aplikacji bdziemy za ich pomoc powiadamiani o dostpnoci dostawcy pooenia, na przykadu systemu GPS, dla uytkownika lub o zmianach stanu dostawcy w czasie.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

573

Rysunek 17.9. Wczytywanie plikw GPX oraz KML do emulatora w celu odtworzenia danych

Wartoci stanw dostawcy s nastpujce: OUT_OF_SERVICE, TEMPORARILY_UNAVAILABLE


i AVAILABLE. Nawet jeli dostawca jest aktywny, nie oznacza to wcale, e bdzie wysya aktualizacje pooenia dowiemy si tego wanie za pomoc informacji o stanie. Zwrmy uwag,
e metoda onProviderDisabled() zostanie wywoana natychmiast po wywoaniu metody
requestLocationUpdates() wobec dostawcy pooenia.

Wysyanie aktualizacji pooenia z poziomu konsoli emulatora


rodowisko Eclipse zawiera atwe w uyciu narzdzia, pozwalajce na przesyanie aktualizacji
pooenia do aplikacji, istnieje jednak jeszcze inny sposb. Przypomnijmy sobie informacje
z rozdziau 2., e wykorzystujemy nastpujce polecenie w oknie narzdzi do uruchomienia
konsoli emulatora:
telnet localhost emulator_numer_portu

gdzie emulator_numer_portu jest wartoci powizan z wystpieniem uruchomionego urzdzenia AVD, ktra jest podana w pasku tytuowym emulatora. Po podczeniu si do konsoli
moemy wprowadzi polecenie geo fix, aby wysa aktualizacj pooenia. W tym celu stosujemy nastpujc skadni (wysoko jest opcjonalna):
geo fix dlugosc szerokosc [ wysokosc ]

Na przykad ponisze polecenie spowoduje przesanie wsprzdnych geograficznych Jacksonville


na Florydzie na wysoko 120 metrw nad poziomem morza:
geo fix -81.5625 30.334954 120

Powinnimy zwraca szczegln uwag na kolejno wprowadzania argumentw w poleceniu


geo fix. Pierwszym argumentem jest dugo, a szeroko drugim.

574 Android 3. Tworzenie aplikacji

Alternatywne metody uzyskiwania aktualizacji pooenia


Zaprezentowalimy wczeniej sposb uzyskania aktualizacji danych o pooeniu, przesyanej
do aktywnoci za pomoc metody requestLocationUpdates() klasy LocationManager. W rzeczywistoci istnieje kilka rnych sygnatur tej metody, wcznie z wersj wykorzystujc oczekujce intencje. Moemy w ten sposb kierowa aktualizacje pooenia do usug lub odbiorcw
komunikatw. Istnieje take moliwo przesyania tych aktualizacji do wtkw pobocznych
zamiast do wtku gwnego, dziki czemu nasza aplikacja zyskuje na elastycznoci. Naley tylko
wspomnie, e niektre z tych metod s dostpne dopiero od wersji 2.3 Androida.

Wywietlanie informacji o pooeniu


za pomoc klasy MyLocationOverlay
Powszechnym zastosowaniem systemu GPS i map jest wskazywanie uytkownikowi jego pooenia geograficznego. W Androidzie mona tego atwo dokona poprzez zastosowanie specjalnej
nakadki, noszcej nazw MyLocationOverlay. Jeeli dodamy t nakadk do widoku MapView,
w prosty sposb bdziemy mogli umieci niebiesk, migoczc kropk, ktra bdzie wskazywaa
aktualne miejsce przebywania uytkownika, wyznaczone przez usug LocationManager.
W naszym przykadowym projekcie poczymy kilka koncepcji w jednej aplikacji. Korzystajc
z listingu 17.10, moemy zmodyfikowa poprzedni przykad poprzez aktualizacj plikw main.xml
oraz MyLocationDemoActivity.java. Moemy ewentualnie utworzy nowy projekt z istniejcych kodw rdowych, przygotowanych na potrzeby niniejszego rozdziau. Nie zapomnijmy
umieci klucza interfejsu map w pliku ukadu graficznego.
Listing 17.10. Zastosowanie klasy MyLocationOverlay
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.google.android.maps.MapView
android:id="@+id/geoMap" android:clickable="true"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="UMIESZCZAMY TUTAJ KLUCZ INTERFEJSU API MAPY"
/>
</RelativeLayout>

package com.androidbook.location.myoverlay;
import
import
import
import
import

android.os.Bundle;
com.google.android.maps.MapActivity;
com.google.android.maps.MapController;
com.google.android.maps.MapView;
com.google.android.maps.MyLocationOverlay;

public class MyLocationDemoActivity extends MapActivity {

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

575

MapView mapView = null;


MapController mapController = null;
MyLocationOverlay whereAmI = null;
@Override
protected boolean isLocationDisplayed() {
return whereAmI.isMyLocationEnabled();
}
@Override
protected boolean isRouteDisplayed() {
return false;
}

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mapView = (MapView)findViewById(R.id.geoMap);
mapView.setBuiltInZoomControls(true);
mapController = mapView.getController();
mapController.setZoom(15);
whereAmI = new MyLocationOverlay(this, mapView);
mapView.getOverlays().add(whereAmI);
mapView.postInvalidate();
}
@Override
public void onResume()
{
super.onResume();
whereAmI.enableMyLocation();
whereAmI.runOnFirstFix(new Runnable() {
public void run() {
mapController.setCenter(whereAmI.getMyLocation());
}
});
}
@Override
public void onPause()
{
super.onPause();
whereAmI.disableMyLocation();
}
}

Zwrmy uwag, e w tym przykadzie metoda isLocationDisplayed() bdzie przekazywaa warto true, w przypadku gdy na mapie bdzie wywietlane pooenie geograficzne
urzdzenia.

576 Android 3. Tworzenie aplikacji


Po uruchomieniu aplikacji na emulatorze musimy zacz wysya aktualizacje pooenia geograficznego dopiero wtedy nasz przykadowy projekt stanie si naprawd interesujcy. W tym
celu naley otworzy okno Emulator Control interfejsu DDMS, ktre omwilimy kilka stron
wczeniej:
1. Znajd w internecie przykadowy plik GPX. Wymienione wczeniej witryny zawieraj
ich olbrzymi liczb. Wybierz jeden plik i skopiuj go na swj komputer.
2. Wczytaj pobrany plik w interfejsie DDMS, korzystajc z przycisku Load GPX
w zakadce GPX interfejsu DDMS.
3. Zaznacz ciek z listy na dole i klikaj przycisk odtwarzania (zielony trjkt). Warto
rwnie pamita o przycisku Speed. Powinien rozpocz si proces przesyania do
emulatora strumienia aktualizacji pooenia geograficznego, ktre zostan przekazane
aplikacji.
4. Wcinicie przycisku Speed spowoduje czstszy przebieg aktualizacji.
Rysunek 17.10 pokazuje nam, jak moe wyglda ekran wynikowy.

Rysunek 17.10. Wywietlanie biecego pooenia za pomoc klasy MyLocationOverlay

Powyszy kod jest bardzo prosty. Po skonfigurowaniu podstawowych parametrw widoku


MapView, ustawieniu kontrolek zmiany rozmiaru mapy i wybraniu odpowiedniej skali mapy
tworzymy nakadk MyLocationOverlay. Dodajemy now nakadk do widoku MapView, a nastpnie wywoujemy metod postInvalidate() w tym widoku, aby ta warstwa zostaa naniesiona na map. Gdyby ta ostatnia metoda nie zostaa umieszczona, nakadka zostaaby utworzona, ale nie byaby wywietlana.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

577

Pamitajmy, e w naszej aplikacji metoda onResume() bdzie wywoywana nawet podczas jej
uruchamiania, jak rwnie po wyjciu ze stanu wstrzymania. Trzeba zatem powiza namierzanie lokalizacji z metod onResume(), a wyczy je po wywoaniu metody onPause().
Nie ma sensu marnowanie zapasu energii w baterii na dania ustalenia pooenia geograficznego, jeeli nie bd potrzebne. Po wczeniu ledzenia pooenia geograficznego podczas wywoania metody onResume() chcemy od razu uzyska dane o aktualnym pooeniu geograficznym. Klasa myLocationOverlay posiada pomocn w tym przypadku klas runOnFirstFix().
Dziki tej metodzie moemy skonfigurowa kod, ktry zostanie uruchomiony tu po otrzymaniu wsprzdnych geograficznych pooenia urzdzenia. Moe to nastpi natychmiast, poniewa posiadamy wsprzdne ostatniego znanego pooenia, albo nieco pniej, po otrzymaniu informacji od dostawcy GPS_PROVIDER, NETWORK_PROVIDER lub PASSIVE_PROVIDER.
Gdy uzyskamy odpowiednie dane, mapa zostanie zgodnie z nimi wyrodkowana. Nie musimy
robi nic wicej, poniewa klasa MyLocationOverlay bdzie uzyskiwaa uaktualnienia pooenia geograficznego i bdzie w tych miejscach umieszczaa migoczc, niebiesk kropk. Jeeli
kropka ta zbliy si zanadto do krawdzi mapy, mapa automatycznie zostanie wyrodkowana
w taki sposb, e migoczcy punkt znajdzie si w centrum ekranu.

Dostosowywanie klasy MyLocationOverlay


Moglimy zauway, e podczas aktualizowania pooenia geograficznego moemy przyblia
i oddala widok, a nawet przesuwa widok mapy. W zalenoci od punktu widzenia moe to by
zalet lub wad. Jeeli przesuniemy map i nie bdziemy pamitali swej lokalizacji, moe by
trudno odnale niebiesk kropk, chyba e zwikszymy skal mapy w stopniu umoliwiajcym wywietlenie tego punktu. Automatyczne wyrodkowanie mapy dziaa jedynie wtedy, gdy
niebieska kropka stopniowo i bez naszego udziau zblia si do krawdzi ekranu. Jeeli samodzielnie przesuniemy widok tak bardzo, e nasz punkt zniknie, nie zostanie ju automatycznie
wyrodkowany. Taka sytuacja moe rwnie nastpi, jeeli kropka znajdzie si poza krawdzi
ekranu bez uprzedniego zblienia si do niej.
Aby bieca lokalizacja bya zawsze wywietlana w pobliu rodka ekranu, musi by cay czas
animowana, co jest wzgldnie prostym zadaniem. W nastpnej wersji naszego wiczenia wykorzystamy uprzednio utworzone elementy, wprowadzimy jednak niewielk zmian w naszej
aktywnoci, a take dodamy now klas do pakietu rozszerzenie klasy MyLocationOverlay,
dziki czemu nieco usprawnimy dziaanie aplikacji. Nowe rozszerzenie klasy MyLocationOverlay
zostao przedstawione na listingu 17.11.
Listing 17.11. Zastosowanie klasy MyLocationOverlay oraz centrowanie biecej lokalizacji
package com.androidbook.location.myoverlay;
import android.content.Context;
import android.location.Location;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
public class MyCustomLocationOverlay extends MyLocationOverlay {
MapView mMapView = null;
public MyCustomLocationOverlay(Context ctx, MapView mapView) {
super(ctx, mapView);

578 Android 3. Tworzenie aplikacji


mMapView = mapView;
}
public void onLocationChanged(Location loc) {
super.onLocationChanged(loc);
GeoPoint newPt = new GeoPoint((int) (loc.getLatitude()*1E6),
(int) (loc.getLongitude()*1E6));
mMapView.getController().animateTo(newPt);
}
}

Jedyna zmiana w porwnaniu z listingiem 17.10 polega na wykorzystaniu klasy


LocationOverlay, a nie MyLocationOverlay w metodzie onCreate():

MyCustom

whereAmI = new MyCustomLocationOverlay(this, mapView);

Teraz mona wczy t aplikacj na emulatorze, a nastpnie przesa dane o nowej lokalizacji
poprzez okno Emulator Control. Jeeli wysyamy strumie aktualizacji za pomoc pliku GPX,
stwierdzimy, e niebieski punkt zawsze jest przesuwany na rodek ekranu. Nawet jeli cakowicie oddalimy widok, mapa zostanie wyrodkowana na tym punkcie.

Stosowanie alertw odlegociowych


Wspomnielimy wczeniej, e klasa LocationManager moe powiadamia uytkownika o znalezieniu si urzdzenia w okrelonym obszarze geograficznym. Metod suc do implementacji tego mechanizmu jest addProximityAlert(), bdca czci klasy LocationManager. W oglnym zaoeniu klasa LocationManager uruchamia intencj w momencie przekroczenia okrgu
o okrelonym promieniu, ktrego rodek znajduje si w punkcie wyznaczonym przez wsprzdne geograficzne. Intencja ta moe wywoa usug lub odbiorc komunikatw, ewentualnie
uruchomi aktywno. Opcjonalnie mona rwnie zdefiniowa limit czasowy nakadany na
ten alert, ktry moe zosta przekroczony jeszcze przed uruchomieniem intencji.
Sam kod tej metody rejestruje obiekty nasuchujce zarwno dla dostawcw GPS, jak i sieciowych, a take ustanawia czas aktualizacji co jedn sekund i minimaln odlego rwn jednemu metrowi. Nie ma moliwoci przesonicia tego zachowania ani modyfikowania parametrw. Zatem jeli pozostawimy ten mechanizm wczony przez dugi czas, baterie urzdzenia
zostan bardzo szybko wyczerpane. Jeeli urzdzenie przejdzie w stan oczekiwania, alert odlegociowy bdzie sprawdza pooenie zaledwie co cztery minuty, jednak take i w tym przypadku nie moemy zmieni tego parametru.
Byoby o wiele lepiej, gdybymy mogli samodzielnie zdefiniowa promie obszaru za pomoc
omwionych wczeniej technik. Jeli na przykad utworzymy list interesujcych nas lokalizacji,
moemy sprawdza odlego od biecego miejsca do kadego z wyznaczonych punktw.
W zalenoci od dystansu moemy chcie poczeka chwil, zanim znowu sprawdzimy biece
pooenie. Jeeli najblisze interesujce nas miejsce znajduje si 100 kilometrw od nas, a my
chcemy wiedzie, e si zbliamy, gdy znajdziemy si w odlegoci 300 metrw, to jest oczywiste, e nie musimy w danej chwili sprawdza pooenia co sekund.
Jeeli jednak Czytelnik zechce wykorzysta t metod, zaprezentujemy, jak naley to zrobi. Na
listingu 17.12 zamiecilimy kod Java naszej gwnej aktywnoci oraz klasy BroadcastReceiver,
odbierajcej komunikaty.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

Listing 17.12. Konfigurowanie alertu odlegociowego za pomoc odbiorcy komunikatw


// Jest to plik ProximityActivity.java
package com.androidbook.location.proximity;
import
import
import
import
import
import
import

android.app.Activity;
android.app.PendingIntent;
android.content.Intent;
android.content.IntentFilter;
android.location.LocationManager;
android.net.Uri;
android.os.Bundle;

public class ProximityActivity extends Activity {


private final String PROX_ALERT =
"com.androidbook.intent.action.PROXIMITY_ALERT";
private ProximityReceiver proxReceiver = null;
private LocationManager locMgr = null;
PendingIntent pIntent1 = null;
PendingIntent pIntent2 = null;

/** Wywoane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
locMgr = (LocationManager)
this.getSystemService(LOCATION_SERVICE);
double lat = 30.334954; // Wsprzdne Jacksonville, Floryda
double lon = -81.5625;
float radius = 5.0f * 1609.0f; // 5 mil 1609 metrw na jedn mil
String geo = "geo:"+lat+","+lon;
Intent intent = new Intent(PROX_ALERT, Uri.parse(geo));
intent.putExtra("message", "Jacksonville, Floryda");
pIntent1 = PendingIntent.getBroadcast(getApplicationContext(), 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT);
locMgr.addProximityAlert(lat, lon, radius, -1L, pIntent1);
lat = 28.54;

// Wsprzdne Orlando, Floryda

lon = -81.38;
geo = "geo:"+lat+","+lon;
intent = new Intent(PROX_ALERT, Uri.parse(geo));
intent.putExtra("message", "Orlando, Floryda");
pIntent2 = PendingIntent.getBroadcast(getApplicationContext(), 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT);
locMgr.addProximityAlert(lat, lon, radius, -1L, pIntent2);
proxReceiver = new ProximityReceiver();

579

580 Android 3. Tworzenie aplikacji


IntentFilter iFilter = new IntentFilter(PROX_ALERT);
iFilter.addDataScheme("geo");
registerReceiver(proxReceiver, iFilter);
}
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(proxReceiver);
locMgr.removeProximityAlert(pIntent1);
locMgr.removeProximityAlert(pIntent2);
}
}

// Jest to plik ProximityReceiver.java


package com.androidbook.location.proximity;
import
import
import
import
import
import

android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.location.LocationManager;
android.os.Bundle;
android.util.Log;

public class ProximityReceiver extends BroadcastReceiver {


private static final String TAG = "ProximityReceiver";
@Override
public void onReceive(Context arg0, Intent intent) {
Log.v(TAG, "Intencja otrzymana");
if(intent.getData() != null)
Log.v(TAG, intent.getData().toString());
Bundle extras = intent.getExtras();
if(extras != null) {
Log.v(TAG, "Komunikat: " + extras.getString("message"));
Log.v(TAG, "Wkraczamy? " +
extras.getBoolean(LocationManager.KEY_PROXIMITY_ENTERING));
}
}
}

Poniewa w rzeczywistoci nie wywietlamy adnej pozycji na mapie, nie korzystamy ani z klasy
MapActivity, ani z bibliotek interfejsu Google Map, ani nie okrelamy adnego celu. Musimy
jednak doda w pliku manifecie uprawnienie android.permission.ACCESS_FINE_LOCATION,
poniewa klasa LocationManager bdzie prbowaa korzysta z dostawcy GPS. W takim
samym stopniu bdzie korzystaa z dostawcy sieciowego, ale skoro wprowadzamy uprawnienie
ACCESS_FINE_LOCATION, wymagania dotyczce uprawnie zostaj spenione. Rejestrujemy
odbiorc BroadcastReceiver w metodzie onCreate(), nie musimy wic go rejestrowa w pliku
manifecie. Gdybymy umiecili odbiorc komunikatw w oddzielnej aplikacji, wtedy zaistniaaby potrzeba jego zdefiniowania w manifecie. Definicja przykadowego kodu z listingu
17.12 zostaa zaprezentowana na listingu 17.13.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

581

Listing 17.13. Fragment pliku AndroidManifest.xml dotyczcy klasy BroadcastReceiver alertu


odlegociowego
<application >
<receiver android:name=".ProximityReceiver">
<intent-filter>
<action android:name="com.androidbook.android.intent.PROXIMITY_ALERT" />
<data android:scheme="geo" />
</intent-filter>
</receiver>
</application>

Funkcja alertu odlegociowego w Androidzie dziaa poprzez otrzymywanie obiektu Pending


Intent, wsprzdnych geograficznych interesujcego nas punktu, promienia (w metrach)
obszaru wok tego punktu oraz czasu wymaganego do wykonywania aktualizacji. Wszystkie
te argumenty s przekazywane za pomoc metody addProximityAlert() klasy LocationManager.
Obiekt PendingIntent zawiera intencj, ktra zostanie uruchomiona w momencie przekroczenia (niewane, w ktr stron) okrgu otaczajcego interesujce nas miejsce. W naszym
przykadzie postanowilimy skorzysta z intencji nadajcej komunikaty, zatem wywoalimy
metod getBroadcast() klasy PendingIntent, przekazalimy naszej aplikacji kontekst wraz
z intencj zawierajc dziaanie alertu oraz identyfikator URI obiektu Location. Jeeli urzdzenie przekroczy w jaki sposb interesujcy nas obszar, intencja bdzie nadawaa komunikaty do
wszystkich zarejestrowanych odbiorcw.
Postanowilimy nie wprowadza wartoci przekroczenia czasu alertw, okrelajc czas ich trwania za pomoc wartoci -1L. Jeeli chcemy zaimplementowa przekroczenie czasu, warto ta
musi definiowa w milisekundach okres, po ktrego miniciu klasa LocationManager zaprzestanie prb i usunie oczekujc intencj. Nie zostaniemy powiadomieni o usuniciu tej intencji.
W naszym przykadzie uzyskujemy odniesienie do klasy LocationManager, tworzymy pierwsze
obiekty Intent oraz PendingIntent, a nastpnie wywoujemy metod addProximityAlert()
konfigurujc nasz pierwszy alert. Nastpnie, ju po uruchomieniu intencji, klasa Location
Manager doda jeszcze tylko (w danych typu extra) warto logiczn okrelajc, czy wkraczamy do interesujcego nas okrgu, czy te go opuszczamy. Nie przesya biecych wsprzdnych geograficznych urzdzenia ani wsprzdnych uytych podczas wywoania metody
addProximityAlert(). Zatem aby wiedzie, w pobliu ktrej lokalizacji znalazo si urzdzenie, musielimy doda pewne informacje do intencji, mianowicie wsprzdne geograficzne
interesujcego nas miejsca. Dodatkowo, dla urozmaicenia, wprowadzilimy rwnie komunikat
(w danych typu extra) stanowicy opis tego miejsca. W razie potrzeby moemy doda wsprzdne typu double.
Po dodaniu pierwszego alertu dokadnie w taki sam sposb konfigurujemy drugi alert. Na
kocu rejestrujemy obiekt BroadcastReceiver odbierajcy intencje nadawane przez klas
LocationManager. Filtr IntentFilter stanowi dziaanie w przypadku obydwu alertw,
natomiast schematem jest zmienna geo. Obydwa te obiekty s wymagane do odbierania komunikatw, poniewa komunikaty zawieraj dane; moglibymy je odbiera bez udziau schematu, gdyby nie posiaday danych. Ostatni czynnoci, jak musimy wykona, jest wyczyszczenie pamici po wywoaniu metody onDestroy() poprzez wyrejestrowanie odbiorcy oraz
usunicie alarmw odlegociowych z klasy LocationManager za pomoc zachowanych intencji oczekujcych. Dlatego wanie przechowujemy odniesienia do obiektw PendingIntent
aby mc pniej usun alerty.

582 Android 3. Tworzenie aplikacji


Nasza klasa ProximityReceiver jest bardzo prosta. Po odebraniu nadawanego komunikatu
wyszukuje informacje, ktre bdzie moga wywietli w oknie LogCat. To tutaj znajdziemy
wszelkie dodatkowe dane, wstawiane przez klas LocationManager, informujce nas, czy
wkraczamy, czy wychodzimy z interesujcego nas obszaru.
Po uruchomieniu tej aplikacji na emulatorze ujrzymy pusty ekran, zawierajcy jedynie pasek
z nazw programu. Moemy teraz zacz wysya aktualizacje pooenia albo za pomoc interfejsu DDMS, albo polecenia geo fix w konsoli emulatora. Gdy przelemy wsprzdne przekraczajce granice obserwowanych okrgw (na przykad krawdzie obszarw o promieniu 5 mil
wok miast Jacksonville lub Orlando), w oknie LogCat powinny zacz si pojawia komunikaty pochodzce od odbiorcy komunikatw. Na rysunku 17.11 pokazujemy, jak moe wyglda okno LogCat po uaktywnieniu odbiorcw komunikatw.

Rysunek 17.11. Okno LogCat zawierajce komunikaty pochodzce od odbiorcw komunikatw

Poniewa mamy tu do czynienia z nadawanymi komunikatami, nie moemy polega na kolejnoci, w jakiej s otrzymywane. Jeeli na przykad urzdzenie znajduje si wewntrz okrgu
otaczajcego Orlando i nagle przemieci si do okrgu wok Jacksonville, uytkownik moe
otrzyma komunikat, e znajduje si w Jacksonville, jeszcze zanim pojawi si informacje o jego
obecnoci w Orlando.
Poniewa zajmujemy si tu obiektami typu Location, naszym identyfikatorem URI jest schemat geo, ktry jest jednym z lepiej znanych schematw i doskonale si nadaje do przekazywania
informacji o wsprzdnych geograficznych. Zwrmy uwag, e struktura tego identyfikatora
zawiera najpierw szeroko, a dopiero pniej dugo geograficzn, gdy jednak korzystamy
z polecenia geo fix, kolejno wystpowania tych wsprzdnych jest odwrotna. Moe to spowodowa spore problemy, jeeli nie bdziemy ostroni. W efekcie atwo straci mnstwo czasu
na znalezienie rda problemu, podczas gdy wystarczy jedynie zamieni wartoci wsprzdnych sucych do aktualizowania informacji o pooeniu. Zawsze moemy rwnie skorzysta
z plikw GPX lub KML, w ktrych moemy wybiera lokalizacje do testowania, nakadajce si
na utworzone przez nas obszary zainteresowania.
Nasza prbna aplikacja jest bardzo prosta. W rzeczywistym programie odbiorca komunikatw
moe wysya powiadomienia lub uruchamia usug. W przypadku aktywnoci lub usugi
(nawet znajdujcej si w innej aplikacji) zamiast nadawanego komunikatu moemy zamieci
oczekujc intencj. Rwnie zamiast aplikacji moemy korzysta ze wspomnianej ju usugi.

Rozdzia 17 Analiza usug wykorzystujcych mapy i dane o lokalizacji

583

Odnoniki
Poniej prezentujemy przydatny odnonik do informacji rozszerzajcych informacje zawarte
w tym rozdziale:
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw projektw
stworzonych specjalnie na potrzeby ksiki. Projekty dotyczce tego rozdziau
zostay zawarte w katalogu ProAndroid3_R17_Mapy. W katalogu umiecilimy
take plik Czytaj.TXT, w ktrym zawarte s szczegowe instrukcje dotyczce
importowania projektw ze skompresowanych plikw do rodowiska Eclipse.

Podsumowanie
W niniejszym rozdziale omwilimy usugi wykorzystujce mapy oraz dane o lokalizacji. Szczegowo przedstawilimy zastosowanie kontrolki MapView oraz klasy MapActivity. Najpierw
zajlimy si podstawowymi funkcjami mapy, a nastpnie pokazalimy, w jaki sposb mona
wykorzysta nakadki do umieszczania znacznikw na mapie. Przeanalizowalimy nawet proces
geokodowania i przetwarzania tego procesu w wtkach przeprowadzanych w tle. Podalimy
informacje na temat klasy LocationManager, ktra uzyskuje szczegowe informacje o pooeniu
geograficznym za pomoc dostawcw oraz pozwala wywietli biece pooenie urzdzenia na
mapie. Na koniec pokazalimy, w jaki sposb mona wykorzysta alerty odlegociowe.
W nastpnym rozdziale zajmiemy si usugami telefonicznymi w Androidzie.

584 Android 3. Tworzenie aplikacji

R OZDZIA

18
Uywanie interfejsw telefonii

Wiele urzdze obsugiwanych przez system Android to smartfony, dotychczas


jednak nie zajmowalimy si kwesti pisania aplikacji wykorzystujcych funkcje
telefonii. W niniejszym rozdziale zapoznamy si ze sposobami wysyania i odbierania wiadomoci SMS. Poruszymy take temat kilku innych interesujcych
aspektw dotyczcych interfejsw API telefonii w Androidzie, w tym protokou
SIP (ang. Session Initiation Protocol protok inicjalizacji sesji). Protok SIP
stanowi standard utworzony przez grup IETF, sucy do implementacji technologii VoIP (ang. Voice over Internet Protocol protok przesyania gosu poprzez internet), za pomoc ktrej uytkownik moe wykonywa poprzez sie internetow poczenia przypominajce telefoniczne. Za pomoc protokou SIP
mona take przesya obraz wideo.

Praca z wiadomociami SMS


Rozwiniciem skrtu SMS jest Short Messaging Service, czyli usuga wysyania krtkich wiadomoci tekstowych, powszechnie stosuje si jednak termin wiadomoci
tekstowe. rodowisko Android SDK pozwala na wysyanie i odbieranie takich wiadomoci. Przyjrzymy si najpierw rnym sposobom wysyania wiadomoci SMS
za pomoc rodowiska SDK.

Wysyanie wiadomoci SMS


Aby wysa wiadomo tekstow z poziomu aplikacji, musimy doda uprawnienie
android.permission.SEND_SMS w pliku manifecie, a nastpnie skorzysta z klasy
android.telephony.SmsManager. Na listingu 18.1 zamiecilimy plik ukadu
graficznego oraz kod Java przykadowej aplikacji. Jeeli chcemy dowiedzie si, gdzie
naley wstawi uprawnienie w pliku manifecie, moemy zajrze do listingu 18.2.
Na kocu rozdziau zamieszczamy adres URL strony, z ktrej moemy
pobra projekty utworzone w tym rozdziale. W ten sposb bdziemy mogli
bezporednio zaimportowa cay kod rdowy do rodowiska Eclipse.

586 Android 3. Tworzenie aplikacji


Listing 18.1. Wysyanie wiadomoci SMS (tekstowych)
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Adres docelowy:" />
<EditText android:id="@+id/addrEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:phoneNumber="true"
android:text="9045551212" />
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wiadomo:" />
<EditText android:id="@+id/msgEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Witaj, sms-ie" />
</LinearLayout>
<Button android:id="@+id/sendSmsBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wylij wiadomo"
android:onClick="doSend"/>
</LinearLayout>

// Jest to plik TelephonyDemo.java


import
import
import
import
import

android.app.Activity;
android.os.Bundle;
android.telephony.SmsManager;
android.view.View;
android.widget.EditText;

Rozdzia 18 Uywanie interfejsw telefonii

587

import android.widget.Toast;
public class TelephonyDemo extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void doSend(View view) {
EditText addrTxt =
(EditText) findViewById(R.id.addrEditText);
EditText msgTxt =
(EditText) findViewById(R.id.msgEditText);
try {
sendSmsMessage(
addrTxt.getText().toString(),msgTxt.getText().toString());
Toast.makeText(this, "Wiadomo SMS wysana",
Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(this, "Nie udao si wysa wiad. SMS",
Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}
private void sendSmsMessage(String address,String message)throws Exception
{
SmsManager smsMgr = SmsManager.getDefault();
smsMgr.sendTextMessage(address, null, message, null, null);
}
}

Na listingu 18.1 pokazano przykad wysyania wiadomoci SMS w rodowisku SDK. Jeli przyjrzymy si fragmentowi kodu ukadu graficznego, zauwaymy dwa pola EditText: w jednym
umieszczamy adres docelowy (numer telefonu) adresata, a w drugim wpisujemy tre wiadomoci. Interfejs uytkownika jest take zaopatrzony w przycisk powodujcy wysanie wiadomoci SMS, co zostao zilustrowane na rysunku 18.1.
Interesujc czci kodu jest metoda sendSmsMessage(). Wykorzystuje ona metod send
TextMessage() klasy SmsManager do wysania wiadomoci SMS. Poniej umiecilimy sygnatur metody SmsManager.sendTextMessage():
sendTextMessage(String destinationAddress, String smscAddress, String textMsg,
PendingIntent sentIntent, PendingIntent deliveryIntent);

588 Android 3. Tworzenie aplikacji

Rysunek 18.1. Przykadowy interfejs UI aplikacji sucej do wysyania wiadomoci SMS

W naszym przykadzie wypeniamy jedynie dane adresu docelowego oraz parametry wiadomoci tekstowej. Mona jednak dostosowa metod do wasnych potrzeb, tak eby nie korzystaa
z domylnego centrum usugi SMS (adresu serwera sieci komrkowej przesyajcego wiadomo). Moemy take zaimplementowa konfiguracj pozwalajc na nadanie oczekujcych
intencji po wysaniu (lub nieudanym wysaniu) wiadomoci i dostarczeniu powiadomienia.
Wysyanie wiadomoci SMS skada si z dwch gwnych etapw: wysania wiadomoci i jej
dostarczenia. Jeeli aplikacja to umoliwia, po osigniciu kadego etapu zostaje nadana intencja oczekujca. Moemy w niej umieci cokolwiek zechcemy, na przykad jakie dziaanie, ale
kod wynikowy wysany do odbiorcy komunikatw bdzie niepowtarzalny dla etapu wysyania
i dostarczenia wiadomoci SMS. Ponadto moemy otrzymywa dodatkowe dane zwizane
z bdami transmisji lub raportami o stanie wiadomoci, w zalenoci od sposobu implementacji wysyania wiadomoci.
Przy braku intencji oczekujcych aplikacja nie moe okreli, czy wiadomo zostaa pomylnie wysana. Dlatego musimy jak najwicej testowa. Jeeli wczymy t przykadow aplikacj
w emulatorze i uruchomimy kolejne wystpienie emulatora (bez rnicy, czy z wiersza polece,
czy za pomoc opcji Window/Android SDK and AVD Manager w rodowisku Eclipse), moemy
wykorzysta numer jego portu jako adres docelowy. Numer portu jest liczb pojawiajc si
w pasku tytuowym emulatora; zazwyczaj przybiera warto 5554. Po klikniciu przycisku Wylij
wiadomo w drugim emulatorze powinno si pojawi powiadomienie informujce, e zostaa
do niego dostarczona wiadomo.
Klasa SmsManager zawiera jeszcze dwie metody pozwalajce na wysanie wiadomoci SMS:
sendDataMessage() pobiera dodatkowy argument definiujcy numer portu
i zamiast cigu znakw przesya tablic bajtw;

Rozdzia 18 Uywanie interfejsw telefonii

589

sendMultipartTextMessage()

umoliwia wysyanie wiadomoci tekstowej, gdy jest


ona wiksza od dopuszczalnej wartoci okrelonej w specyfikacji SMS. Metoda ta
pobiera tablic cigu znakw, zwrmy jednak uwag, e jednoczenie moe zosta
pobrana opcjonalna tablica odpowiadajcych im intencji oczekujcych, odpowiedzialnych
za wysyanie i dostarczanie wiadomoci. Klasa SmsManager posiada metod
divideMessage(), uatwiajc dzielenie duych wiadomoci na mniejsze czci.

Podsumowujc, obsuga wysyania wiadomoci SMS w Androidzie jest wyjtkowo prosta. Pamitajmy, e emulator nie bdzie w rzeczywistoci wysya wiadomoci tekstowych. Moemy jednak
zaoy, e implementacja przebiega pomylnie, jeli metoda sendTextMessage() nie przekae adnego wyjtku. Jak wida na listingu 18.1, klasa Toast zostaa wykorzystana do wywietlenia
w interfejsie uytkownika komunikatu o powodzeniu wysania wiadomoci tekstowej.
Wysanie wiadomoci SMS jest zaledwie poow sukcesu. Zajmiemy si teraz monitorowaniem
przychodzcych wiadomoci tekstowych.

Monitorowanie przychodzcych wiadomoci tekstowych


Wykorzystamy utworzon przed chwil aplikacj do wysyania wiadomoci SMS, a nastpnie zaimplementujemy odbiorc BroadcastReceiver do nasuchiwania dziaania android.
permission.SMS_RECEIVED. Dziaanie to jest nadawane przez Androida w momencie otrzymania przez urzdzenie wiadomoci SMS. Po zarejestrowaniu odbiorcy nasza aplikacja bdzie
powiadamiana o kadej przychodzcej wiadomoci SMS. Pierwszym etapem monitorowania
przychodzcych wiadomoci SMS jest danie uprawnienia do ich otrzymywania. W tym celu
musimy doda uprawnienie android.permission.RECEIVE_SMS w pliku manifecie. Nastpnie zaimplementujemy monitor nasuchujcy. W celu zaimplementowania odbiorcy musimy
napisa rozszerzenie klasy android.content.BroadcastReceiver, a nastpnie zarejestrowa
go w pliku manifecie. Na listingu 18.2 umiecilimy zarwno plik AndroidManifest.xml, jak
i klas odbiorcy. Zwrmy uwag, e w manifecie s obecne obydwa uprawnienia, poniewa
musimy jeszcze wysa uprawnienie do uprzednio utworzonej aktywnoci.
Listing 18.2. Monitorowanie wiadomoci SMS
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik AndroidManifest.xml -->


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.telephony" android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".TelephonyDemo"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="MySMSMonitor">
<intent-filter>
<action
android:name="android.provider.Telephony.SMS_RECEIVED"/>

590 Android 3. Tworzenie aplikacji


</intent-filter>
</receiver>
</application>
<uses-sdk android:minSdkVersion="4" />
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
</manifest>

// Jest to plik MySMSMonitor.java


import
import
import
import
import

android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.telephony.SmsMessage;
android.util.Log;

public class MySMSMonitor extends BroadcastReceiver


{
private static final String ACTION =
"android.provider.Telephony.SMS_RECEIVED";
@Override
public void onReceive(Context context, Intent intent)
{
if(intent!=null && intent.getAction()!=null &&
ACTION.compareToIgnoreCase(intent.getAction())==0)
{
Object[] pduArray= (Object[]) intent.getExtras().get("pdus");
SmsMessage[] messages = new SmsMessage[pduArray.length];
for (int i = 0; i<pduArray.length; i++) {
messages[i] = SmsMessage.createFromPdu(
(byte[])pduArray [i]);
Log.d("MySMSMonitor", "Od: " +
messages[i].getOriginatingAddress());
Log.d("MySMSMonitor", "Wiadomosc: " +
messages[i].getMessageBody());
}
Log.d("MySMSMonitor","Wiadomosc SMS otrzymana.");
}
}
}

Pierwsz czci listingu 18.2 jest definicja manifestu dla klasy BroadcastReceiver pozwalajca
na odbieranie wiadomoci SMS. Klas monitorujc wiadomoci SMS jest klasa MySMSMonitor.
Zostaje w niej zaimplementowana abstrakcyjna metoda onReceive(), ktra jest wywoywana
przez system podczas otrzymywania wiadomoci SMS. Jednym ze sposobw przetestowania
tej aplikacji jest skorzystanie z widoku Emulator Control w rodowisku Eclipse. Uruchamiamy
aplikacj na emulatorze, a nastpnie przechodzimy do Window/Show View/Other/Android/
Emulator Control. Interfejs uytkownika pozwala na przesyanie do emulatora danych, ktre

Rozdzia 18 Uywanie interfejsw telefonii

591

imituj otrzymanie wiadomoci SMS lub odebranie poczenia telefonicznego. Na rysunku 18.2
wida, e wiadomoci SMS s wysyane poprzez wypenienie pola Incoming number, a nastpnie zaznaczenie opcji SMS. W dalszej kolejnoci wpisujemy tre wiadomoci w polu Message
i klikamy przycisk Send. W ten sposb zostaje wysana wiadomo tekstowa do emulatora i wywoana metoda onReceive() klasy BroadcastReceiver.

Rysunek 18.2. Wykorzystanie interfejsu UI Emulator Control do wysyania wiadomoci SMS


do emulatora

Metoda onReceive() posiada intencj suc do transmisji danych, zawierajc obiekt


SmsMessage wewntrz waciwoci bundle. Zawarto tego obiektu uzyskujemy poprzez wywoanie metody intent.getExtras().get("pdus"). Wywoanie to przekazuje tablic obiektw
zdefiniowanych w trybie PDU (ang. Protocol Description Unit jednostka opisu protokou)
standardzie przemysowym, reprezentujcym wiadomoci SMS. Moemy nastpnie przekonwertowa jednostki PDU na obiekty SmsMessage Androida, podobnie jak na listingu 18.2.
Jak wida, jednostki PDU otrzymujemy z intencji w postaci tablicy obiektw. W kocu przeprowadzamy iteracje na tablicy PDU i tworzymy obiekty SmsMessage z jednostek PDU poprzez
wywoanie metody SmsMessage.createFromPdu(). Czynnoci wykonywane po odczytaniu przychodzcej wiadomoci s przeprowadzane bardzo szybko. Odbiorca komunikatw otrzymuje
wysoki priorytet w systemie, lecz jego zadanie musi zosta szybko zakoczone i nie mona go
wywietli na pierwszym planie do wgldu uytkownika. Z tego powodu nasze moliwoci s
do ograniczone. Nie powinnimy przeprowadza bezporednio adnych czynnoci na interfejsie uytkownika. Nie zaszkodzi wysanie powiadomienia, poniewa uruchamia usug kontynuujc prac. Po zakoczeniu dziaania metody onReceive() jej gwny proces moe zosta zamknity w dowolnym momencie. Mona uruchomi usug, ale nie naley wiza jej z inn usug,
poniewa moe to oznacza, e proces musi jeszcze przez chwil istnie, co nie zawsze jest moliwe. Szczegowe informacje dotyczce odbiorcw komunikatw znajdziemy w rozdziale 14.
Przyjrzyjmy si teraz, w jaki sposb moemy pracowa z poszczeglnymi folderami wiadomoci SMS.

592 Android 3. Tworzenie aplikacji

Praca z folderami wiadomoci SMS


Nastpnym powszechnym wymogiem jest uzyskanie dostpu do skrzynki odbiorczej wiadomoci SMS. Rozpoczniemy od dodania uprawnienia odczytywania wiadomoci SMS (android.
permission.READ_SMS) w pliku manifecie. Po dodaniu tego uprawnienia bdziemy mogli
odczyta nadesane wiadomoci umieszczone w skrzynce odbiorczej.
Aby odczytywa wiadomoci SMS, musimy przeprowadzi kwerend wobec skrzynki odbiorczej, co zostao zaprezentowane na listingu 18.3.
Listing 18.3. Wywietlanie wiadomoci tekstowych dostpnych w skrzynce odbiorczej
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/sms_inbox.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/row"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</LinearLayout>

// Jest to plik SMSInboxDemo.java


import
import
import
import
import
import

android.app.ListActivity;
android.database.Cursor;
android.net.Uri;
android.os.Bundle;
android.widget.ListAdapter;
android.widget.SimpleCursorAdapter;

public class SMSInboxDemo extends ListActivity {


private ListAdapter adapter;
private static final Uri SMS_INBOX = Uri.parse("content://sms/inbox");
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Cursor c = getContentResolver()
.query(SMS_INBOX, null, null, null, null);
startManagingCursor(c);
String[] columns = new String[] { "body" };
int[] names = new int[] { R.id.row };
adapter = new SimpleCursorAdapter(this, R.layout.sms_inbox, c, columns,
names);
setListAdapter(adapter);
}
}

Rozdzia 18 Uywanie interfejsw telefonii

593

Kod z listingu 18.3 otwiera skrzynk odbiorcz wiadomoci SMS i tworzy list elementw,
z ktrych kady zawiera cz treci wiadomoci tekstowej. Fragment kodu dotyczcy ukadu
graficznego z listingu 18.3 zawiera prost kontrolk TextView, w ktrej bdzie przechowywana
tre wiadomoci kadego elementu listy. Aby uzyska list wiadomoci SMS, tworzymy identyfikator URI wskazujcy na skrzynk wiadomoci przychodzcych (content://sms/inbox), a nastpnie wykonujemy prost kwerend. W kolejnym kroku filtrujemy tre wiadomoci SMS
i wyznaczamy adapter listy klasy ListActivity. Po wykonaniu kodu z listingu 18.3 ujrzymy
list wiadomoci tekstowych, dostpnych w skrzynce odbiorczej. Zanim uruchomimy kod na
emulatorze, utwrzmy najpierw kilka wiadomoci SMS za pomoc narzdzia Emulator Control.
Skoro potrafimy uzyska dostp do skrzynki odbiorczej wiadomoci SMS, spodziewamy si, e
bdziemy mogli korzysta rwnie z innych, powizanych folderw, na przykad ze skrzynki
nadawczej lub z folderu przechowujcego wersje robocze wiadomoci. Jedyn rnic pomidzy
skrzynk odbiorcz a innymi folderami s ich identyfikatory URI. Na przykad moemy uzyska
dostp do skrzynki nadawczej poprzez wykonanie kwerendy wobec adresu content://sms/sent.
Poniej znajduje si pena lista folderw wiadomoci SMS oraz definiujce je identyfikatory URI:
Wszystkie: content://sms/All.
Odebrane: content://sms/inbox.
Wysane: content://sms/sent.
Wersje robocze: content://sms/draft.
Nieodebrane: content://sms/outbox.
Niewysane: content://sms/failed.
Zakolejkowane: content://sms/queued.
Niedostarczone: content://sms/undelivered.
Konwersacje: content://sms/conversations.
W Androidzie koncepcje wiadomoci MMS i SMS s ze sob poczone i mona uzyska dostp
jednoczenie do dostawcw treci obydwu rodzajw za pomoc upowanienia mms-sms. Zatem
moemy korzysta z nastpujcego identyfikatora URI:
content://mms-sms/conversations

Wysyanie wiadomoci e-mail


Skoro wiemy ju, w jaki sposb mona wysya w Androidzie wiadomoci SMS, moemy zaoy, e za pomoc podobnych interfejsw API s wysyane wiadomoci e-mail. Niestety, Android nie zosta wyposaony w interfejsy API obsugujce wiadomoci e-mail. Panuje powszechna
opinia, e uytkownicy nie chc aplikacji, ktra wysyaaby wiadomoci e-mail w ich imieniu
bez ich wiedzy. Zamiast tego w celu wysania wiadomoci naley skorzysta z zarejestrowanego
klienta pocztowego. Na przykad moemy wykorzysta dziaanie ACTION_SEND do uruchomienia takiej aplikacji, co zostao ukazane na listingu 18.4.
Listing 18.4. Uruchamianie aplikacji pocztowej za pomoc intencji
Intent emailIntent=new Intent(Intent.ACTION_SEND);
String subject = "Cze!";
String body = "Pozdrowienia z Androida...";

594 Android 3. Tworzenie aplikacji


String[] recipients = new String[]{"aaa@bbb.com"};
emailIntent.putExtra(Intent.EXTRA_EMAIL, recipients);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
emailIntent.setType("message/rfc822");
startActivity(emailIntent);

Powyszy kod uruchamia domyln aplikacj pocztow i pozwala zadecydowa uytkownikowi


o wysaniu wiadomoci e-mail. Dodatkowymi elementami, ktre mona doda do intencji
wiadomoci e-mail, s obiekty EXTRA_CC i EXTRA_BCC.
Zamy, e chcemy wysa wiadomo e-mail wraz z zacznikiem. W tym celu musimy
wprowadzi konstrukcj podobn do poniszej, gdzie za Uri wstawiamy odniesienie do zaczanego pliku:
emailIntent.putExtra(Intent.EXTRA_STREAM,
Uri.fromFile(new File(myFileName)));

Przejdmy teraz do menedera telefonii.

Praca z menederem telefonii


Wrd interfejsw API telefonii znajduje si take meneder telefonii (android.telephony.
TelephonyManager), dziki ktremu moemy uzyska informacje o usugach telefonicznych
dostpnych w urzdzeniu i o subskrypcji, a take rejestrowa zmiany stanu poczenia telefonicznego. Zastosowanie funkcji telefonicznych wymaga, aby aplikacje zachowyway si w odpowiedni sposb po wykryciu poczenia przychodzcego. Na przykad odtwarzacz muzyczny
wstrzymuje dziaanie w trakcie poczenia przychodzcego i odtwarzanie zostaje wznowione po
zakoczeniu rozmowy. Najprostszym sposobem nasuchiwania zmian stanu telefonu jest implementacja odbiorcy komunikatw wobec wartoci android.intent.action.PHONE_STATE.
Moemy wykorzysta w tym celu taki sam algorytm jak w przypadku omawianego wczeniej
nasuchiwania przychodzcych wiadomoci SMS. Innym rozwizaniem jest wprowadzenie
klasy TelephonyManager. W tym podrozdziale pokaemy, w jaki sposb rejestrowa zmiany
stanu poczenia telefonicznego oraz jak wykrywa przychodzce poczenia telefoniczne.
Szczegy zostay przedstawione na listingu 18.4.
Listing 18.4. Zastosowanie menedera telefonii
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/callBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wykonaj poczenie"

Rozdzia 18 Uywanie interfejsw telefonii

android:onClick="doClick"
/>
<TextView
android:id="@+id/textView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>

// Jest to plik PhoneCallActivity.java


package com.androidbook.phonecall.demo;
import
import
import
import
import
import
import
import
import

android.app.Activity;
android.content.Context;
android.content.Intent;
android.net.Uri;
android.os.Bundle;
android.telephony.PhoneStateListener;
android.telephony.TelephonyManager;
android.view.View;
android.widget.TextView;

public class PhoneCallActivity extends Activity {


private TelephonyManager teleMgr = null;
private MyPhoneStateListener myListener = null;
private String logText = "";
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView)findViewById(R.id.textView);
teleMgr =
(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
myListener = new MyPhoneStateListener();
}
protected void onResume() {
super.onResume();
teleMgr.listen(myListener, PhoneStateListener.LISTEN_CALL_STATE);
}
protected void onPause() {
super.onPause();
teleMgr.listen(myListener, PhoneStateListener.LISTEN_NONE);
}
public void doClick(View target) {
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse("tel:5551212"));

595

596 Android 3. Tworzenie aplikacji


startActivity(intent);
}
class MyPhoneStateListener extends PhoneStateListener
{
@Override
public void onCallStateChanged(int state, String incomingNumber)
{
super.onCallStateChanged(state, incomingNumber);
switch(state)
{
case TelephonyManager.CALL_STATE_IDLE:
logText = "wywolanie stanu spoczynku...przychodzacy numer to["+
incomingNumber + "]\n" + logText;
break;
case TelephonyManager.CALL_STATE_RINGING:
logText = "wywolanie stanu dzwonienia...przychodzacy numer to["+
incomingNumber + "]\n" + logText;
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
logText = "wywolanie stanu Zajety...przychodzacy numer to["+
incomingNumber + "]\n" + logText;
break;
default:
logText = "wywolanie stanu [" + state +
"]przychodzacy numer to[" +
incomingNumber + "]\n" + logText;
break;
}
tv.setText(logText);
}
}
}

Podczas pracy z menederem telefonii naley doda uprawnienie android.permission.READ_


do pliku manifestu, aby uzyska dostp do informacji na temat stanu telefonu.
Na listingu 18.5 wida, e bdziemy otrzymywa powiadomienia o zmianie stanu telefonu poprzez zaimplementowanie klasy PhoneStateListener i wywoanie metody listen() klasy
TelephonyManager. Jeli zostanie wykryte poczenie przychodzce lub stan telefonu ulegnie
zmianie, system wywoa metod onCallStateChanged() klasy PhoneStateListener wraz
z nowym stanem. Gdy wyprbujemy dziaanie projektu, okae si, e poczenia przychodzce s dostpne jedynie w stanie CALL_STATE_RINGING. W naszym przykadzie wywietlilimy
wiadomo na ekranie, jednak w tym miejscu mona wstawi okrelone dziaanie aplikacji, na
przykad wstrzymywanie odtwarzania muzyki lub wideo. Aby emulowa przychodzce poczenia telefoniczne, moemy skorzysta z interfejsu UI Emulator Control podobnie jak
w przypadku wysyania wiadomoci SMS (rysunek 18.2), tym razem jednak zamiast opcji SMS
wybieramy opcj Voice.
PHONE_STATE

Zwrmy uwag, e poprzez metod onPause() klasa TelephonyManager przerywa wysyanie


aktualizacji. Wane jest, aby zawsze wycza wysyanie komunikatw w stanie wstrzymania
aktywnoci. W przeciwnym wypadku klasa TelephonyManager moe utrzymywa odniesienie
do naszego obiektu i uniemoliwi jego pniejsze usunicie.

Rozdzia 18 Uywanie interfejsw telefonii

597

W powyszym przykadzie zajmujemy si tylko jednym ze stanw telefonw, ktre moemy nasuchiwa. Warto przejrze dokumentacj klasy PhoneStateListener, aby pozna inne stany,
na przykad LISTEN_MESSAGE_WAITING_INDICATOR. Podczas pracy ze zmianami stanu telefonu
moe by potrzebny rwnie numer telefonu subskrybenta (uytkownika). Otrzymujemy go
za pomoc metody TelephonyManager.getLine1Number().
Czytelnik by moe zastanawia si, czy mona odebra poczenie za pomoc kodu. Niestety,
na obecn chwil zestaw Android SDK nie posiada takiej moliwoci, chocia w dokumentacji
widnieje informacja, e mona uruchomi intencj za pomoc dziaania ACTION_ANSWER. W praktyce jednak rozwizanie to nie dziaa, chocia warto byoby sprawdzi, czy problem ten nie
zosta ju rozwizany.
Analogicznie, moemy chcie umieci poczenie wychodzce w kodzie. Tutaj na szczcie
sprawy maj si prociej. Najatwiejszym sposobem wprowadzenia poczenia wychodzcego
jest przywoanie aplikacji Dialer za pomoc intencji, wykorzystujc nastpujcy kod:
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:5551212"));
startActivity(intent);

Pamitajmy, e jeli nasza aplikacja ma rzeczywicie wykonywa poczenia, bdzie wymagaa


uprawnienia android.permission.CALL_PHONE. W przeciwnym wypadku w momencie prby
wywoania tej aplikacji aplikacja wyrzuci wyjtek zabezpiecze. Aby mc nawizywa poczenia
bez koniecznoci korzystania z uprawnie, zmieniamy dziaanie intencji na Intent.ACTION_VIEW,
dziki czemu aplikacja Dialer zostanie od razu wywietlona z wprowadzonym numerem telefonu, uytkownik jednak bdzie musia wcisn przycisk Wylij, aby zainicjowa poczenie.
Podczas korzystania z funkcji telefonicznych naley jeszcze pamita, e inne aplikacje mog
bardzo aktywnie reagowa na przychodzce poczenia i powodowa wstrzymanie naszej aktywnoci. W takim przypadku przestaniemy otrzymywa powiadomienia, chocia pojawi si
komunikat dokadnie w momencie wywoania metody onResume() i klasa TelephonyManager
zostanie ponownie zarejestrowana. Bdmy przygotowani na tak sytuacj podczas dokonywania zmian w procedurze obsugi powiadomie zwizanych ze stanami telefonu.

Protok inicjalizacji sesji (SIP)


W wersji 2.3 Androida (Gingerbread) wprowadzono nowe funkcje, pozwalajce na obsug
protokou SIP, dokadniej w pakiecie android.net.sip. Protok SIP stanowi utworzony przez
zrzeszenie IETF (ang. Internet Engineering Task Force) standard przesyania strumieni gosowych i wideo poprzez cze internetowe, co ma umoliwia nawizywanie komunikacji pomidzy ludmi. W caoksztacie omawiana technologia jest czsto nazywan VoIP (ang. Voice over
Internet Protocol), pamitajmy jednak, e istnieje wiele mechanizmw jej implementacji. Na
przykad w aplikacji Skype wprowadzono opatentowany protok, definiujcy wasn wersj
technologii VoIP, ktry jest niezgodny z protokoem SIP. Omawiany protok nie jest rwnie
tosamy z technologi Google Voice. Google Voice nie obsuguje (w momencie pisania ksiki)
protokou SIP, chocia istniej pewne sposoby integrowania obydwu tych technologii. Usuga
Google Voice ustanawia dla nas nowy numer telefonu, dziki ktremu moemy nastpnie
czy si z innymi telefonami, na przykad w domu czy w pracy, lub z innym telefonem komrkowym. Niektrzy dostawcy protokou SIP generuj numer telefonu, ktry moe by nastpnie wykorzystany w usudze Google Voice, jednak w tym przypadku usuga ta nie rozrnia
numerw utworzonych w koncie SIP. Moemy znale w internecie kilku dostawcw protokou
SIP, z ktrych wielu nie pobiera wysokich opat, a niektrzy oferuj darmowe usugi.

598 Android 3. Tworzenie aplikacji


Naley zauway, e standard SIP nie bierze bezporedniego udziau w przesyaniu danych audio
i wideo poprzez sie. Jest on wykorzystywany wycznie do ustanawiania i przerywania bezporedniego poczenia pomidzy urzdzeniami, za pomoc ktrego mona przesya dane.
Z protokou SIP korzystaj komputery klienckie, a take kodeki audio i wideo oraz rne
biblioteki do konfigurowania wywoa pomidzy uytkownikami. Innymi standardami powizanymi z wywoaniami protokou SIP s na przykad: protok RTP (ang. Real-time Transport
Protocol protok przesyania w czasie rzeczywistym), RTSP (ang. Real-time Streaming
Protocol protok przesyania strumienia w czasie rzeczywistym) czy SDP (ang. Session
Description Protocol protok opisu sesji).
Uytkownicy mog wykonywa poczenia SIP bez ponoszenia opat za odlego. Program
komputerowy moe by rwnie dobrze uruchomiony w urzdzeniu przenonym, na przykad
w smartfonie lub tablecie wyposaonym w system Android. Aplikacje wykorzystujce protok
SIP czsto s nazywane telefonami programowymi. Rzeczywista przewaga telefonu programowego zainstalowanego na urzdzeniu przenonym uwidacznia si, gdy urzdzenie jest podczone do internetu w sieci Wi-Fi, dziki czemu uytkownik nie ponosi adnych kosztw,
mimo e moe wykonywa i odbiera poczenia. W przypadku odbierania pocze telefon
programowy musi zarejestrowa swoje pooenie oraz dostpne funkcje w dostawcy protokou
SIP, dziki czemu serwer SIP bdzie mg tworzy bezporednie poczenia. Jeeli telefon
programowy adresata jest niedostpny, serwer SIP moe skierowa wychodzce poczenie
na przykad na konto poczty gosowej.
Firma Google udostpnia aplikacj demonstracyjn, ukazujc moliwoci protokou SIP, nazwan SipDemo. Chcielibymy przyjrze si jej teraz uwaniej i wyjani zasad dziaania. Dla
osb dopiero zapoznajcych si z protokoem SIP pewne pojcia mog na pocztku sprawia
trudnoci. Jeeli bdziemy chcieli poeksperymentowa z t aplikacj, prawdopodobnie nie
obejdzie si bez fizycznego urzdzenia, obsugujcego omawiany protok. Wynika to z faktu,
e w czasie, w ktrym powstawaa niniejsza ksika, emulatory Androida nie byy wyposaone
w obsug standardu SIP (dokadniej rzecz biorc sieci Wi-Fi). W internecie pojawiaj si
opisy prb umoliwienia uruchomienia protokou SIP na emulatorze i w momencie, kiedy
Czytelnik otrzyma t ksik, mogy si ju pojawi jakie wszechstronne i atwe do zaimplementowania rozwizania. Aby mc korzysta z testowej aplikacji, wymagane jest take utworzenie konta u dostawcy SIP. Bdzie wymagany wasny identyfikator, nazwa domeny (lub adres
proxy), a take haso. Bdzie je mona wprowadzi na ekranie preferencji aplikacji SipDemo.
Ostatni wymagan rzecz jest poczenie z sieci Wi-Fi, umoliwiajce urzdzeniu czenie
si z internetem. Osoby, ktre nie chc testowa tej aplikacji, nie powinny mie problemu ze
zrozumieniem poniej omwionych koncepcji. Okno aplikacji SipDemo zaprezentowano
na rysunku 18.3.
Aby wczyta aplikacj SipDemo jako nowy projekt rodowiska Eclipse, wczmy kreator New
Android Project, wybierzmy w nim jednak opcj Create project from existing sample, w sekcji
Build target zaznaczmy wersj 2.3 Androida (lub wysz), a nastpnie kliknijmy list Samples
i wybierzmy SipDemo. Po klikniciu przycisku Finish rodowisko zakoczy tworzenie projektu.
Moemy uruchomi t aplikacj bez zmian, jednak jak ju wczeniej wspomnielimy
bez urzdzenia obsugujcego standard SIP oraz sie Wi-Fi niczego nie bdziemy mogli w niej
dokona. Jeli jednak zdecydujemy si na przetestowanie projektu, wciskamy przycisk menu
i uzupeniamy dane swojego konta SIP. Do przetestowania poczenia bdzie rwnie potrzebne
drugie konto SIP. Wcinicie obrazu mikrofonu pozwoli nam przesya dane gosowe do odbiorcy. Aplikacja demonstracyjna umoliwia rwnie odbieranie pocze. Zastanwmy si
teraz nad mechanizmami wprowadzonymi w pakiecie android.net.sip.

Rozdzia 18 Uywanie interfejsw telefonii

599

Rysunek 18.3. Aplikacja SipDemo z widocznym gwnym menu

Pakiet android.net.sip obejmuje cztery klasy: SipManager, SipProfile, SipSession oraz


SipAudioCall. SipManager stanowi rdze pakietu, za pomoc ktrego uzyskujemy dostp do
pozostaych funkcji standardu SIP. Aby utworzy obiekt SIP, wywoujemy statyczn metod
newInstance() klasy SipManager. Dziki temu obiektowi moemy wprowadzi klas Sip
Session w wikszoci aktywnoci korzystajcych z protokou SIP, ewentualnie moemy wykorzysta klas SipAudioCall wycznie w celu poczenia dwikowego. Oznacza to, e firma
Google wprowadzia moliwo wyboru funkcji oferowanych przez standard SIP, mianowicie
konfigurowanie poczenia dwikowego.
Klasa SipProfile suy do definiowania komunikujcych si ze sob kont SIP. Klasa ta nie jest
przeznaczona dla urzdzenia uytkownika, lecz raczej dla usugi SIP przechowywanej u dostawcy protokou. Serwery dostawcy bd wspomagay cay proces nawizywania poczenia.
W klasie SipSession s przeprowadzane najwaniejsze operacje. Do procesu konfiguracji sesji
zalicza si take utworzenie obiektu SipProfile, dziki czemu aplikacja bdzie moga zosta
rozpoznana przez serwer dostawcy protokou SIP. Przekazujemy mu take klas SipSession.
Ustanawiamy w tym celu obiekt nasuchujcy, ktry bdzie informowany o interesujcych nas
zdarzeniach. Po skonfigurowaniu obiektu SipSession nasza aplikacja jest gotowa do nawizania poczenia z innym obiektem SipProfile lub do odebrania poczenia. Obiekt nasuchujcy posiada kilka metod zwrotnych, dziki czemu moemy odpowiednio sobie radzi ze zmieniajcymi si stanami sesji.
W wersji Honeycomb najprociej jest wykorzysta klas SipAudioCall. Caa logika w niej zawarta dotyczy powizania mikrofonu i suchawek bd gonika do strumienia danych, dziki
czemu mona przeprowadza rozmowy. W klasie SipAudioCall istnieje wiele metod zarzdzajcych funkcjami wyciszenia, wstrzymania itd. Klasa ta zapewnia obsug wszelkich
funkcji dwikowych, jednak wszystkie inne elementy musi zaprojektowa sam programista. Klasa SipSession zawiera metod makeCall(), suc do zamieszczania wychodzcych
pocze. Gwnym parametrem jest opis sesji (w postaci cigu znakw). Wanie tutaj

600 Android 3. Tworzenie aplikacji


wymagany jest wikszy nakad pracy. Utworzenie opisu sesji wymaga formatowania zgodnego ze wspomnianym wczeniej protokoem SDP. Zrozumienie otrzymywanego opisu sesji jest
rwnoznaczne z analizowaniem jego skadni zgodnie z tym protokoem. Dokumentacj tego
standardu znajdziemy pod adresem http://tools.ietf.org/html/rfc4566. Niestety, zestaw Android SDK nie obsuguje tego formatu. Dziki pewnym bardzo uprzejmym ludziom istnieje
kilka bezpatnych aplikacji dla Androida posiadajcych wbudowan t funkcj. Mamy na myli siproid (http://code.google.com/p/sipdroid/) oraz csipsimple (http://code.google.com/p/csipsimple/).
Nie poruszylimy nawet zagadnienia kodekw sucych do zarzdzania strumieniami wideo
pomidzy poczonymi klientami SIP, chocia siproid posiada tak moliwo. Innym bardzo
atrakcyjnym aspektem protokou SIP jest moliwo utworzenia poczenia konferencyjnego
pomidzy wiksz liczb osb. Tematyka ta przekracza zakres tej ksiki, mamy jednak nadziej, e wszyscy doceni moliwoci oferowane przez standard SIP.
Zwrmy uwag, e aplikacje wykorzystujce protok SIP bd wymagay do poprawnego dziaania bd wymagay przynajmniej uprawnie android.permission.USE_SIP oraz android.
permission.INTERNET. Dodatkowe uprawnienia bd jeszcze potrzebne w przypadku uywania
klasy SipAudioCall. Dobrym pomysem jest rwnie dodanie poniszego znacznika do pliku
AndroidManifest.xml jako obiektu podrzdnego wza <manifest>, dziki czemu aplikacja
bdzie instalowana wycznie na urzdzeniach obsugujcych standard SIP:
<uses-feature android:name="android.hardware.sip.voip" />

Odnoniki
Poniej prezentujemy kilka odnonikw do materiaw, z ktrymi warto si dokadniej zapozna:
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw projektw
przygotowanych z myl o tej ksice. Przykady z tego rozdziau znajdziemy w katalogu
ProAndroid3_R18_Telefonia. Doczylimy take plik Czytaj.TXT, w ktrym
dokadnie omwilimy proces importowania projektw do rodowiska Eclipse.
http://pl.wikipedia.org/wiki/Session_Initiation_Protocol artyku z Wikipedii dotyczcy
protokou SIP.
http://tools.ietf.org/html/rfc3261 oficjalny standard IETF dotyczcy protokou SIP.
http://tools.ietf.org/html/rfc4566 oficjalny standard IETF dotyczcy protokou SDP.
http://code.google.com/p/sipdroid/, http://code.google.com/p/csipsimple/ dwie
przeznaczone dla Androida aplikacje o otwartym kodzie rdowym, implementujce
klientw SIP.

Podsumowanie
W tym rozdziale zajmowalimy si interfejsami telefonii. Skupilimy si zwaszcza na wysyaniu
wiadomoci tekstowych, monitorowaniu otrzymywanych wiadomoci SMS oraz zaprezentowalimy metody uzyskania dostpu do rnych folderw zwizanych z wiadomociami SMS,
znajdujcych si na urzdzeniu. Dokonalimy rwnie analizy klasy TelephonyManager. Rozdzia zakoczylimy omwieniem zestawu funkcji zwizanych z protokoem SIP, wprowadzonego w wersji 2.3 systemu Android.

R OZDZIA

19
Uywanie szkieletu
multimedialnego

Obecnie zajmiemy si bardzo interesujcym elementem zestawu Android SDK


szkieletem multimediw. Pokaemy, w jaki sposb mona odtwarza oraz
rejestrowa dwik i wideo za pomoc rnych rde. Zajmiemy si take zagadnieniem wykonywania zdj za pomoc wbudowanego aparatu. Wszelkie
opisy dotyczce multimediw byyby niekompletne, gdybymy pominli tematyk kart SD (ang. Secure Digital) oraz moliwoci ich wykorzystania, poniewa bd one czsto uywane do zapisu i odczytu danych.

Stosowanie interfejsw API multimediw


W Androidzie obsuga odtwarzania plikw audio i wideo zostaa zawarta w pakiecie
android.media. W niniejszym podrozdziale omwimy interfejsy API multimediw,
dostpne w tym pakiecie.
Rdzeniem pakietu android.media jest klasa android.media.MediaPlayer. Klasa
odpowiada za odtwarzanie plikw audio i wideo. Tre obsugiwana
przez t klas moe pochodzi z nastpujcych rde:
Internet moemy odtwarza multimedia umieszczone pod danym
adresem URL.

MediaPlayer

Plik .apk istnieje moliwo odtwarzania plikw skompilowanych


w pliku .apk. Mona umieci odtwarzane pliki jako zasoby lub pliki
dodatkowe (w folderze assets).
Karta SD pliki umieszczone na karcie SD urzdzenia rwnie mog
by odtwarzane.

Klasa MediaPlayer potrafi obsuy kilka rnych formatw multimediw, w tym takie
jak 3GPP (.3gp), MP3 (.mp3), MIDI (.mid i inne), Ogg Vorbis (.ogg), PCM/WAVE
(.wav) oraz MPEG-4 (.mp4). W przypadku wersji 3.0 Androida obsugiwane s rwnie
media strumieniowe, przesyane protokoem HTTP w czasie rzeczywistym, oraz listy
odtwarzania M3U. Pen list obsugiwanych formatw mona znale pod adresem:
http://developer.android.com/guide/appendix/media-formats.html

602 Android 3. Tworzenie aplikacji

Wykorzystywanie kart SD
Zanim przejdziemy do procesu tworzenia i stosowania rnych rodzajw multimediw, zobaczmy, w jaki sposb pracujemy z kartami SD. Karty SD s uywane w urzdzeniach obsugujcych system Android do przechowywania duej iloci danych uytkownika, na przykad
plikw obrazw, audio i wideo. Zasadniczo s to niewielkie ukady scalone przechowujce dane
nawet przy braku zasilania. W rzeczywistym telefonie karta SD jest umieszczana w gniedzie
pamici i staje si dostpna dla urzdzenia. Wikszo urzdze posiada tylko jedno gniazdo
pamici i karta SD zazwyczaj nie jest w nich wymieniana. W niektrych urzdzeniach mona
posiada wiele kart i przecza si pomidzy nimi w obrbie jednego urzdzenia, a take mona je wymienia pomidzy rnymi urzdzeniami. Na szczcie emulator Androida potrafi
symulowa karty SD, a za ich pojemno suy przestrze dysku twardego.
Podczas tworzenia pierwszego urzdzenia AVD w rozdziale 2. okrelilimy rozmiar karty
SD, dziki czemu staa si ona dostpna dla aplikacji podczas jej uruchomienia na emulatorze. Jeeli przyjrzymy si zawartoci utworzonego katalogu urzdzenia AVD, ujrzymy plik
sdcard.img, w ktrym zdefiniowano rozmiar karty. Nie korzystalimy wwczas z tej symulacji karty, bdziemy jednak to robi w tym rozdziale.
Projektanci aplikacji mog, po utworzeniu karty SD, uywa narzdzi rodowiska Eclipse do
umieszczania plikw multimedialnych (gwoli cisoci, dowolnego rodzaju plikw) na karcie
SD. Do umieszczania plikw na takiej karcie lub usuwania ich z niej moemy rwnie wykorzysta aplikacj adb. Aplikacja ta jest umieszczona w podkatalogu tools rodowiska Android
SDK; w rozdziale 2. opisalimy atwy sposb uzyskania do niej dostpu z okna narzdzi.
Wiemy ju, w jaki sposb wygenerowa symulacj karty SD podczas procesu tworzenia urzdzenia AVD. Oczywicie moemy rwnie utworzy wiele takich samych urzdze AVD rnicych si jedynie rozmiarem karty SD. Istnieje jeszcze inny sposb. Wrd narzdzi rodowiska
SDK znajduje si aplikacja mksdcard, suca do tworzenia obrazu karty SD. W rzeczywistoci
aplikacja ta generuje sformatowany plik, wykorzystywany jako karta SD. Aby skorzysta z tej
aplikacji, musimy najpierw wyszuka lub utworzy folder, w ktrym bdzie przechowywany obraz
karty SD, na przykad c:\Android\sdcard\. Nastpnie otwieramy okno narzdzi (w rozdziale 2.
zostay umieszczone informacje na temat okna narzdzi) i wpisujemy nastpujce polecenie,
podajc ciek do obrazu karty SD:
mksdcard 256M c:\Android\sdcard\sdcard.img

To przykadowe polecenie tworzy obraz karty SD nazwany sdcard.img w lokacji c:\Android\


sdcard\. Rozmiar karty wynosi 256 MB. Do okrelania innych rozmiarw mona stosowa
przedrostek K dla kilobajtw, jednak dla gigabajtw jeszcze nie zaimplementowano przedrostka
jednostki G, zatem w celu okrelenia pojemnoci wyraonej w gigabajtach naley podawa
wielokrotnoci wartoci 1024 M. Moemy rwnie po prostu zdefiniowa liczb cakowit
reprezentujc cakowit liczb bajtw. Naley rwnie pamita, e emulator Androida nie
bdzie obsugiwa kart SD o rozmiarze mniejszym ni 8 MB.
Narzdzie ADT rodowiska Eclipse umoliwia zdefiniowanie dodatkowych argumentw wiersza
polece podczas uruchamiania emulatora. Aby znale pole umoliwiajce dostp do opcji
emulatora, przejdmy do okna Preferences w aplikacji Eclipse, a nastpnie wybierzmy Android/
Launch. Teoretycznie moglibymy tutaj doda polecenie -sdcard "PATH_TO_YOUR_SD_ CARD_
IMAGE_FILE", dziki czemu zostaaby przesonita cieka do pliku karty SD urzdzenia AVD.
Sposb ten jednak nie dziaa ju od kilku wersji Androida i zawsze otrzymujemy obraz karty
SD utworzonej wraz z urzdzeniem AVD. Najpewniejszym sposobem oddzielenia karty SD od

Rozdzia 19 Uywanie szkieletu multimedialnego

603

urzdzenia AVD jest uruchomienie emulatora z poziomu wiersza polece i okrelenie tam,
ktry obraz karty SD ma by wykorzystywany. Ponisze polecenie, wpisane w oknie narzdzi,
uruchamia dane urzdzenie AVD wraz z wybranym obrazem karty SD, a nie z obrazem karty
SD utworzonej wraz z tym urzdzeniem:
emulator -avd AVDName -sdcard "PATH_TO_YOUR_SD_CARD_IMAGE_FILE"

Tu po utworzeniu karty SD jest ona pusta. Mona na niej umieszcza pliki poprzez narzdzie
File Explorer w rodowisku Eclipse. W tym celu wczamy emulator i czekamy, a si uruchomi
do koca. Nastpnie w rodowisku Eclipse przechodzimy do perspektywy Java, Debug lub DDMS
i wyszukujemy zakadk File Explorer, pokazan na rysunku 19.1.

Rysunek 19.1. Widok zakadki File Explorer

Jeeli zakadka ta nie jest widoczna, moemy j wywietli, klikajc Windows/Show View/Other/
Android i zaznaczajc opcj File Explorer. Ewentualnie moemy przej do perspektywy DDMS,
wybierajc Window/Open Perspective/Other/DDMS. Widok File Explorer jest domylny w perspektywie DDMS. Lista wszystkich dostpnych widokw zostaa zaprezentowana na rysunku 19.2.

Rysunek 19.2. Wczanie widokw Androida

604 Android 3. Tworzenie aplikacji


Aby umieci plik na karcie SD, zaznaczamy folder sdcard w oknie File Explorer i wybieramy
przycisk (w prawym grnym rogu ekranu), na ktrym widoczna jest strzaka skierowana
w prawo, wskazujca ikon telefonu. Zostanie uruchomione okno dialogowe, w ktrym moemy wybra plik. Zaznaczamy plik, ktry chcemy umieci na karcie pamici. Obok opisanego
przycisku znajduje si przycisk, na ktrym widoczna jest skierowana w lewo strzaka, wskazujca
dyskietk. Po zaznaczeniu pliku w oknie File Explorer za pomoc tego przycisku przenosimy
plik z karty SD na dysk twardy stacji roboczej.
Jeli w oknie File Explorer bdzie wywietlony pusty widok, to albo nie uruchomilimy emulatora,
albo rodowisko Eclipse rozczyo si z emulatorem, albo urzdzenie AVD uruchomione na
emulatorze nie jest zaznaczone w zakadce Devices, tak jak zostao pokazane na rysunku 19.1.
Aby przej do zakadki Devices, naley wykona takie same czynnoci jak w przypadku widoku
File Explorer. Widok ten powinien by rwnie domylnie widoczny w perspektywie DDMS.
Innym sposobem przenoszenia plikw z karty SD lub na ni jest zastosowanie narzdzia adb.
Aby to sprawdzi, otwieramy okno narzdzi i wpisujemy nastpujce polecenie:
adb push c:\cieka_do_pliku\nazwa_pliku /mnt/sdcard/newfile

W ten sposb plik zostanie przeniesiony ze stacji roboczej na kart SD. Zwrmy uwag, e
urzdzenie stosuje ukoniki (/) do oddzielania katalogw. Uywamy dowolnego znaku oddzielania katalogu wykorzystywanego przez stacj robocz dla kopiowanego pliku oraz wpisujemy
waciw ciek dostpu do pliku umieszczonego w stacji roboczej. I odwrotnie, ponisze polecenie przekopiuje plik z karty SD do stacji roboczej:
adb pull /mnt/sdcard/nazwa_pliku c:\cieka_docelowa\nazwa_pliku

Jedn z ciekawszych funkcji tego polecenia jest tworzenie katalogw w razie potrzeby, a take
pobieranie i wysyanie plikw po zdefiniowaniu docelowej cieki. Niestety, aplikacja adb nie
posiada moliwoci rwnoczesnego kopiowania wielu plikw. Kady plik naley przenosi
oddzielnie.
A do wersji 2.2 Androida karta SD bya najczciej umiejscowiona w folderze /sdcard.
W tej oraz nowszych wersjach karta SD znajduje si w folderze /mnt/sdcard, jednak
istnieje symboliczne powizanie, nazwane /sdcard, wskazujce ciek /mnt/sdcard.
Zapewnia ono wsteczn kompatybilno.

Na karcie SD mona zauway katalog DCIM. Przechowywane s w nim obrazy wykonane za


pomoc cyfrowego aparatu fotograficznego. Umieszczenie katalogu DCIM, sucego do przechowywania zdj cyfrowych, na gwnym poziomie karty SD jest okrelonym standardem
przemysowym. Standardem jest rwnie tworzenie wewntrz katalogu DCIM podkatalogu reprezentujcego aparat fotograficzny o nazwie w formacie 123ABCDE, czyli pi liter poprzedzonych trzema cyframi. Emulator tworzy podkatalog 100ANDRO, jednak producenci aparatw
fotograficznych i urzdze obsugujcych system Android mog nada mu dowoln nazw.
Emulator zawiera rwnie, podobnie jak niektre telefony, podkatalog Camera, nazwa ta nie jest
jednak zgodna ze standardem. Niemniej moemy znale zdjcia w podkatalogu Camera,
w podkatalogu 100ANDRO lub w jakim innym podkatalogu umieszczonym w katalogu DCIM.
Niestety nie istnieje adna metoda informujca nas, ktry folder wewntrz katalogu DCIM moe
zosta przeznaczony na zdjcia. Mamy jednak do dyspozycji par metod, dziki ktrym mona
znale gwny katalog karty SD. Jedn z nich jest Environment.getExternalStorage
Directory(), przekazujca obiekt File nadrzdnego katalogu na karcie SD. W urzdzeniach

Rozdzia 19 Uywanie szkieletu multimedialnego

605

(nie wszystkich) wykorzystujcych wersje Androida starsze od 2.2 prawdopodobnie otrzymalibymy nazw /sdcard. W przypadku wersji 2.2 i nowszych najczciej spotykana jest cieka
/mnt/sdcard. O wiele lepiej wykorzystywa metod Environment, ni zakada, e znamy nazw
gwnego katalogu karty SD. Omwimy teraz drug metod.
Od wersji 2.2 Androida (o nazwie kodowej Froyo) do klasy Environment zostay wprowadzone
nowe stae, a take nowa metoda, suce do lokalizowania katalogw. Wczeniej hierarchia
plikw na karcie SD bya do nieuporzdkowana, nie istniaa bowiem tutaj, nie liczc katalogu
DCIM, ustandaryzowana specyfikacja nazewnictwa katalogw. Wraz z wersj Froyo nastpia
pewna standaryzacja nazw katalogw, co zostao zaprezentowane w tabeli 19.1. W trzeciej
kolumnie zostaa podana stosowana nazwa katalogu w emulatorze, gdzie gwny katalog bdzie
prawdopodobnie wyglda tak: /mnt/sdcard (w zalenoci od urzdzenia). Z powodu rnic
w nazewnictwie katalogw powinnimy zawsze wykorzystywa klas Environment do znalezienia tego waciwego.
Tabela 19.1. Ustandaryzowane nazwy folderw na karcie SD
Nazwa katalogu
w emulatorze

Staa katalogu

Opis

DIRECTORY_ALARMS

W tym katalogu Android wyszukuje dwiki


uywane w alarmach.

Alarms

DIRECTORY_DCIM

Standardowy katalog wykorzystywany do


przechowywania zdj i plikw wideo
wykonanych za pomoc aparatu.

DCIM

DIRECTORY_DOWNLOADS

Standardowy katalog przechowujcy


pobrane pliki.

Download (uwaga:
liczba pojedyncza)

DIRECTORY_MOVIES

W tym katalogu znajduj si filmy, ktre


mog by ogldane przez uytkownika.

Movies

DIRECTORY_MUSIC

Tutaj zamieszczone s pliki dwikowe,


wykorzystywane jako utwory muzyczne.

Music

DIRECTORY_NOTIFICATIONS

Przechowywane s tu pliki dwikowe,


stosowane przy powiadomieniach.

Notifications

DIRECTORY_PICTURES

Katalog, w ktrym magazynowane s


obrazy niewykonane za pomoc aparatu.

Pictures

DIRECTORY_PODCASTS

Tutaj s przechowywane pliki dwikowe


przyjmujce posta podcastw.

Podcasts

DIRECTORY_RINGTONES

Katalog dla plikw dwikowych,


wykorzystywanych jako dzwonki.

Ringtones

Now metod, suc do lokalizowania katalogw, jest Environment.getExternalStorage


PublicDirectory(String type), gdzie w miejsce parametru type wstawia si jedn ze staych
widocznych w tabeli 19.1. Metoda ta przekazuje obiekt File reprezentujcy poszukiwany
katalog. Metoda ta nie istnieje w wersjach Androida starszych od Froyo, a nawet w nowszych
urzdzeniach moemy natrafi na pewne rnice. Na przykad urzdzenia firmy Samsung posiadaj dwa gniazda pamici, zatem powysze metody okazuj si niewystarczajce do okrelenia
wszystkich katalogw dostpnych na kartach SD.

606 Android 3. Tworzenie aplikacji


Naley jeszcze wspomnie o zabezpieczeniach. Poczwszy od wersji 1.6 rodowiska Android
SDK, aby umoliwi aplikacji zapisywanie plikw na karcie SD, musimy wprowadzi nastpujce uprawnienie do pliku manifestu tej aplikacji:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Aplikacje tworzone w starszej wersji rodowiska SDK nie wymagaj podania tego uprawnienia.
Oznacza to, e jeeli warto parametru minSdkVersion aplikacji jest mniejsza od 4 (odpowiada
ona wersji 1.6 zestawu Android SDK), to nie musimy dodawa powyszego znacznika do pliku
AndroidManifest.xml, nawet jeli aplikacja jest uruchomiona na urzdzeniu obsugujcym nowsz wersj rodowiska SDK. Zatem jeeli podczas procesu tworzenia aplikacji wybierzemy
wersj Android 1.6 lub nowsz (warto minSdkVersion rwna co najmniej 4) i chcemy, aby
istniaa moliwo zapisywania danych na karcie SD, musimy doda powyszy znacznik do
naszego pliku manifestu. Jeeli uywamy wersji 1.5 Androida lub niszej, nie potrzebujemy tego znacznika. Skoro zapoznalimy si ju z podstawami kart SD, przejdmy do multimediw
dwikowych.

Odtwarzanie multimediw
Rozpoczniemy od napisania prostej aplikacji odtwarzajcej plik MP3 udostpniony w internecie
(rysunek 19.3). Nastpnie omwimy zastosowanie metody setDataSource() klasy MediaPlayer,
dziki ktrej moliwe jest odtwarzanie zawartoci multimedialnej pliku .apk lub karty SD. Klasa
MediaPlayer nie jest jednak jedynym sposobem umoliwiajcym odtwarzanie dwiku, zatem
przyjrzymy si klasie SoundPool, a take klasom JetPlayer, AsyncPlayer oraz wystpujcej na
najniszym poziomie zoonoci klasie AudioTrack. Nastpnie opiszemy kilka brakw dostrzeonych w klasie MediaPlayer. Temat zamkniemy omwieniem sposobu odtwarzania plikw wideo.

Rysunek 19.3. Interfejs aplikacji obsugujcej multimedia

Rozdzia 19 Uywanie szkieletu multimedialnego

607

Odtwarzanie rde dwikowych


Na rysunku 19.3 przedstawiono interfejs uytkownika dla naszego pierwszego przykadowego
projektu. W aplikacji tej zademonstrujemy kilka podstawowych funkcji klasy MediaPlayer, na
przykad odtwarzanie, wstrzymywanie oraz ponowne uruchamianie pliku multimedialnego.
Przyjrzyjmy si ukadowi graficznemu interfejsu uytkownika.
Interfejs UI skada si z menedera LinearLayout zawierajcego cztery przyciski: jeden suy
do uruchomienia odtwarzacza, drugi do jego wstrzymania, trzeci do jego ponownego uruchomienia, natomiast czwarty do jego zatrzymania. Plik ukadu graficznego aplikacji oraz jej kod
Java zostay umieszczone na listingu 19.1. Zakadamy, e ten przykadowy projekt bdzie tworzony dla wersji Androida o numerze co najmniej 2.2, poniewa korzystamy w nim z metody
getExternalStoragePublicDirectory() klasy Environment. Jeeli zechcemy skorzysta ze starszej wersji Androida, po prostu zastpmy t metod funkcj getExternalStorageDirectory()
i wprowadmy odpowiednie dane dotyczce lokalizacji plikw, aby aplikacja moga je odnale.
W umieszczonym na kocu rozdziau podrozdziale Odnoniki znajdziemy adres URL,
z ktrego moemy pobra i zaimportowa omwione tu projekty do rodowiska
Eclipse, zamiast mozolnie kopiowa i wkleja kod.
Listing 19.1. Ukad graficzny oraz kod aplikacji odtwarzajcej multimedia
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/startPlayerBtn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Odtwarzaj plik audio" android:onClick="doClick"
/>
<Button android:id="@+id/restartPlayerBtn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Uruchom ponownie odtwarzacz" android:onClick="doClick"
/>
<Button android:id="@+id/pausePlayerBtn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Wstrzymaj odtwarzacz" android:onClick="doClick"
/>
<Button android:id="@+id/stopPlayerBtn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Zatrzymaj odtwarzacz" android:onClick="doClick" />
</LinearLayout>

608 Android 3. Tworzenie aplikacji


// Jest to plik MainActivity.java
import
import
import
import
import
import
import

android.app.Activity;
android.content.res.AssetFileDescriptor;
android.media.MediaPlayer;
android.os.Bundle;
android.os.Environment;
android.util.Log;
android.view.View;

public class MainActivity extends Activity


{
static final String AUDIO_PATH =
"http://www.androidbook.com/akc/filestorage/android/documentfiles/3389/play.mp3";

// Environment.getExternalStoragePublicDirectory(
// Environment.DIRECTORY_MUSIC) +
// "/music_file.mp3";
// Environment.getExternalStoragePublicDirectory(
// Environment.DIRECTORY_MOVIES) +
// " /movie.mp4";
private MediaPlayer mediaPlayer;
private int playbackPosition=0;

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void doClick(View view) {
switch(view.getId()) {
case R.id.startPlayerBtn:
try {

// Tylko jedna z tych metod nie powinna by wykomentowana


playAudio(AUDIO_PATH);

// playLocalAudio();
// playLocalAudio_UsingDescriptor();
} catch (Exception e) {
e.printStackTrace();
}
break;
case R.id.pausePlayerBtn:
if(mediaPlayer != null && mediaPlayer.isPlaying()) {
playbackPosition = mediaPlayer.getCurrentPosition();
mediaPlayer.pause();
}
break;
case R.id.restartPlayerBtn:
if(mediaPlayer != null && !mediaPlayer.isPlaying()) {
mediaPlayer.seekTo(playbackPosition);
mediaPlayer.start();
}
break;

Rozdzia 19 Uywanie szkieletu multimedialnego

case R.id.stopPlayerBtn:
if(mediaPlayer != null) {
mediaPlayer.stop();
playbackPosition = 0;
}
break;
}
}
private void playAudio(String url) throws Exception
{
killMediaPlayer();
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(url);
mediaPlayer.prepare();
mediaPlayer.start();
}
private void playLocalAudio() throws Exception
{
mediaPlayer = MediaPlayer.create(this, R.raw.music_file);

// W tym przypadku wywoywanie metody prepare() nie jest wymagane


mediaPlayer.start();
}
private void playLocalAudio_UsingDescriptor() throws Exception {
AssetFileDescriptor fileDesc = getResources().openRawResourceFd(
R.raw.music_file);
if (fileDesc != null) {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(fileDesc.getFileDescriptor(),
fileDesc.getStartOffset(), fileDesc.getLength());
fileDesc.close();
mediaPlayer.prepare();
mediaPlayer.start();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
killMediaPlayer();
}
private void killMediaPlayer() {
if(mediaPlayer!=null) {
try {
mediaPlayer.release();
}
catch(Exception e) {
e.printStackTrace();

609

610 Android 3. Tworzenie aplikacji


}
}
}
}

W tym przypadku odtwarzamy plik MP3 udostpniony w internecie, zatem musimy umieci
w pliku manifecie uprawnienie android.permission.INTERNET. W kodzie z listingu 19.1 wida,
e klasa MainActivity obejmuje trzy elementy: cig znakw final okrelajcy adres URL
pliku MP3, wystpienie klasy MediaPlayer oraz obiekt playbackPosition przyjmujcy wartoci
w postaci liczb cakowitych. Nasza metoda onCreate() konfiguruje jedynie interfejs graficzny
z pliku XML. W procedurze obsugi kliknicia przycisku Odtwarzaj plik audio jest wywoywana metoda playAudio(). W metodzie tej tworzymy nowe wystpienie klasy MediaPlayer,
a rdem danych odtwarzacza staje si adres URL pliku MP3. Nastpnie wywoujemy metod
odtwarzacza prepare(), suc do przygotowania odtwarzania, po niej za zostaje przywoana
metoda start(), rozpoczynajca odtwarzanie.
Spjrzmy teraz na kod przyciskw Wstrzymaj odtwarzacz i Uruchom ponownie odtwarzacz.
Widzimy, e po klikniciu przycisku Wstrzymaj odtwarzacz otrzymujemy biec pozycj odtwarzacza za pomoc wywoania metody getCurrentPosition(). Nastpnie wywoujemy metod
pause(), aby wstrzyma odtwarzanie. Przed ponownym uruchomieniem odtwarzacza
wywoujemy metod seekTo(), ktra pobiera pozycj przechowywan przez metod getCurrent
Position(), a w dalszej kolejnoci przywoujemy metod start().
Klasa MediaPlayer posiada take metod stop(). Jeeli za pomoc tej metody zatrzymamy
odtwarzacz, przed ponownym wywoaniem metody start() musimy przywoa znowu metod
prepare(). W przypadku wstrzymania odtwarzacza poprzez metod pause() nie musimy wywoywa metody prepare() przed ponownym uruchomieniem odtwarzania. Po zakoczeniu
korzystania z aplikacji musimy rwnie wywoa metod release(). W naszym przykadzie
jest ona czci metody killMediaPlayer().
Na listingu 19.1 pokazalimy sposb odtwarzania pliku audio udostpnionego w internecie.
Klasa MediaPlayer obsuguje rwnie odtwarzanie lokalnych plikw multimedialnych, bdcych czci pakietu .apk. Na listingu 19.2 przedstawiono technik tworzenia odniesienia
do pliku zawartego w folderze /res/raw pakietu .apk oraz sposb jego odtwarzania. Moemy
utworzy katalog raw w wle res, jeli nie zosta jeszcze utworzony podczas generowania projektu w rodowisku Eclipse. Nastpnie skopiujmy dowolny plik MP3 nazwany music_file.mp3
do podkatalogu /res/raw.
Listing 19.2. Zastosowanie klasy MediaPlayer do odtwarzania lokalnego pliku w aplikacji
private void playLocalAudio()throws Exception
{
mediaPlayer = MediaPlayer.create(this, R.raw.music_file);

// W tym przypadku wywoywanie metody prepare() nie jest konieczne


mediaPlayer.start();
}

Jeeli plik audio lub wideo ma si znale w aplikacji, powinnimy go umieci w katalogu
/res/raw. Moemy nastpnie uzyska wystpienie klasy MediaPlayer dla tego zasobu poprzez
przekazanie jej identyfikatora zasobu tego pliku; w tym celu wywoujemy statyczn metod

Rozdzia 19 Uywanie szkieletu multimedialnego

611

create(),

tak jak pokazano na listingu 19.2. Odnotujmy fakt, e klasa MediaPlayer rwnie
zapewnia metody create(), dziki ktrym mona uzyska do niej dostp, zamiast samemu tworzy jej nowy egzemplarz. Na przykad na listingu 19.2 wywoujemy metod create(), lecz rwnie
dobrze moglibymy wywoa konstruktor MediaPlayer(Context context,int resourceId).
Zalecane jest stosowanie statycznych metod create(), poniewa ukrywaj one proces tworzenia
klasy MediaPlayer, a w tym przypadku wan rol odgrywa wywoanie metody prepare().
Jednak, jak si wkrtce okae, czasami nie mona wybiera pomidzy tymi opcjami w przypadku gdy nie bdzie mona lokalizowa rde danych multimedialnych za pomoc identyfikatora zasobw lub adresu URL, trzeba utworzy obiekt domylnego konstruktora.

Metoda setDataSource
Na listingu 19.2 wywoalimy metod create(), pozwalajc na wczytanie pliku audio z nieskompresowanego zasobu. Dziki temu nie musimy wywoywa metody setDataSource().
Ewentualnie, jeeli sami utworzymy klas MediaPlayer za pomoc domylnego konstruktora
lub jeli nie mona uzyska dostpu do pliku multimedialnego za pomoc identyfikatora zasobw bd adresu URL, bdzie potrzebna metoda setDataSource().
Metoda setDataSource() istnieje w olbrzymiej liczbie wersji, dziki ktrym moemy dostosowa rdo danych do wasnych potrzeb. Na przykad na listingu 19.3 zosta ukazany sposb
wczytania pliku audio z nieskompresowanego zasobu za pomoc obiektu FileDescriptor.
Listing 19.3. Konfigurowanie rda danych dla klasy MediaPlayer za pomoc obiektu FileDescriptor
private void playLocalAudio_UsingDescriptor() throws Exception {
AssetFileDescriptor fileDesc = getResources().openRawResourceFd(
R.raw.music_file);
if (fileDesc != null) {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(fileDesc.getFileDescriptor(), fileDesc
.getStartOffset(), fileDesc.getLength());
fileDesc.close();
mediaPlayer.prepare();
mediaPlayer.start();
}
}

Zakadamy, e kod z listingu 19.3 znajduje si wewntrz kontekstu aktywnoci. Jak wida,
wywoujemy metod getResources(), aby uzyska dostp do zasobw aplikacji, a nastpnie
korzystamy z metody openRawResourceFd() w celu otrzymania deskryptora pliku audio, znajdujcego si w folderze res/raw. W dalszej kolejnoci wywoujemy metod setDataSource(),
ktra poprzez obiekt AssetFileDescriptor otrzymuje informacj o pocztkowej i kocowej
pozycji odtwarzania. Ta wersja metody setDataSource() moe by rwnie wykorzystana do
odtwarzania okrelonego fragmentu pliku. Jeeli chcemy zawsze odtwarza plik w caoci, moemy uy prostszej wersji metody setDataSource(FileDescriptor desc), ktra nie wymaga
wartoci pocztkowej i czasu trwania odtwarzania tego pliku.

612 Android 3. Tworzenie aplikacji


Zastosowanie jednej z wersji metody setDataSource() zawierajcej obiekt FileDescriptor
moe by rwnie przydatne w przypadku koniecznoci odtwarzania pliku multimedialnego
zlokalizowanego w katalogu aplikacji /data. Z powodu zabezpiecze odtwarzacz multimediw
nie posiada dostpu do katalogu /data innej aplikacji, jednak ta aplikacja moe otworzy potrzebny
plik, a nastpnie wprowadzi obiekt FileDescriptor (otwarty) do metody setDataSource().
Pamitajmy, e katalog /data aplikacji znajduje si w zbiorze plikw i folderw pod adresem
/data/data/APP_PACKAGE_NAME/. Moemy uzyska dostp do tego katalogu, wywoujc
odpowiedni metod klasy Context, zamiast umieszcza ciek wewntrz kodu. Na przykad
moemy wywoa metod getFilesDir() klasy Context, aby uzyska dostp do cieki plikw
znajdujcych si w katalogu biecej aplikacji. Aktualnie cieka ta wyglda nastpujco:
/data/data/APP_PACKAGE_NAME/files. W analogiczny sposb moemy wywoa metod
getCacheDir(), aby uzyska dostp do katalogu pamici podrcznej aplikacji. Aplikacja bdzie
odczytywaa oraz zapisywaa uprawnienia zasobw znajdujcych si w tych folderach, zatem
moemy dynamicznie tworzy pliki i przekazywa je odtwarzaczowi. Na koniec, w przypadku
korzystania z obiektu FileDescriptor w podobny sposb jak na listingu 19.3 nie zapominajmy
o zamkniciu procedury wywoujcej po wywoaniu metody setDataSource().
Zauwamy, e katalog /data aplikacji w znacznym stopniu rni si od jej folderu /res/raw.
Folder /res/raw fizycznie stanowi cz pliku .apk i jest statyczny to znaczy, e nie moemy
pliku .apk modyfikowa w sposb dynamiczny. Z kolei zawarto katalogu /data jest dynamiczna.
Pozostao nam do omwienia jeszcze jedno rdo plikw audio karta SD. Na pocztku
rozdziau pokazalimy, w jaki sposb pliki s umieszczane na karcie SD. Ich odtwarzanie za
pomoc klasy MediaPlayer jest cakiem proste. W powyszym przykadzie uylimy metody
setDataSource() do uzyskania dostpu do pliku umieszczonego w internecie poprzez przekazanie jej adresu URL tego pliku. W przypadku pliku audio na karcie SD stosujemy t sam
metod setDataSource(), tym razem jednak przekazujemy jej ciek do pliku MP3 umieszczonego na tej karcie. Jeli na przykad umiecimy na karcie SD plik music_file.mp3 w standardowym folderze Music, moemy zmodyfikowa zmienn AUDIO_PATH i muzyka bdzie odtwarzana po wstawieniu nastpujcego fragmentu kodu:
static final String AUDIO_PATH =
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MUSIC) + "/music_file.mp3";

Zastosowanie klasy SoundPool do rwnoczesnego odtwarzania cieek


Klasa MediaPlayer stanowi istotne narzdzie w naszym warsztacie muzycznym, pozwala ona
jednak na przetwarzanie w danej chwili tylko jednego pliku audio lub wideo. Co zatem mona
zrobi w przypadku potrzeby jednoczesnego odtwarzania wikszej liczby cieek dwikowych?
Jednym z rozwiza jest utworzenie wielu wystpie klasy MediaPlayer i ich rwnoczesne
przetwarzanie. W przypadku gdy chcemy odtwarza krtkie fragmenty audio i zaley nam na
pynnoci, moemy zastosowa klas SoundPool. Sama klasa SoundPool wykorzystuje klas
MediaPlayer, nie posiadamy jednak dostpu do jej interfejsu.
Kolejna rnica pomidzy klasami SoundPool a MediaPlayer polega na tym, e ta pierwsza jest
przeznaczona wycznie do pracy z plikami lokalnymi. Oznacza to, e moemy wczytywa
dwiki za pomoc plikw zasobw, korzystajc z deskryptorw lub cieek plikw. Istnieje
jeszcze kilka innych przydatnych funkcji klasy SoundPool, na przykad moemy zaptli
odtwarzan ciek czy wstrzymywa i ponawia odtwarzanie pojedynczych lub wszystkich
utworw jednoczenie.

Rozdzia 19 Uywanie szkieletu multimedialnego

613

Klasa SoundPool nie jest jednak pozbawiona wad. Istnieje wsplny bufor przeznaczony dla
wszystkich cieek zarzdzanych przez t klas i nie jest on zbyt pojemny. W zasadzie jego
rozmiar wynosi 1 MB. Moe si to wydawa du wielkoci w przypadku plikw MP3, ktrych rozmiary czsto nie przekraczaj kilku kilobajtw. Klasa SoundPool dekompresuje jednak
plik audio w pamici, aby odtwarzanie dwiku przebiegao szybko i sprawnie. Rozmiar strumienia audio w pamici zaley od prdkoci transmisji, liczby kanaw (mono lub stereo), czstotliwoci prbkowania oraz dugoci cieki. Jeeli mamy problem z wczytaniem dwikw
do klasy SoundPool, powinnimy wprowadzi plik rdowy o nieco niszych parametrach
jakociowych, aby zmniejszy zuycie pamici.
Zaprezentujemy teraz przykadow aplikacj, pozwalajc na wczytanie i odtwarzanie dwikw wydawanych przez zwierzta. Jeden z dwikw, odtwarzany bez przerwy w tle, jest
wydawany przez wierszcze. Pozostae dwiki s odtwarzane w rnych odstpach czasowych.
Czasami bdziemy sysze wycznie wierszcze, a za innym razem odezwie si kilka zwierzt
naraz. Umiecimy rwnie w interfejsie przycisk pozwalajcy na wstrzymywanie i ponowne
uruchamianie odtwarzania. Listing 19.4 zawiera plik ukadu graficznego oraz kod Java definiujcy aktywno. Zalecamy Czytelnikowi pobranie tego projektu z naszej oficjalnej strony, poniewa oprcz kodu s tam rwnie pliki dwikowe. Adres URL do tej strony mona znale
na kocu rozdziau, w podrozdziale Odnoniki.
Listing 19.4. Odtwarzanie dwiku za pomoc klasy SoundPool
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="fill_parent"
>
<ToggleButton android:id="@+id/button"
android:textOn="Wstrzymaj" android:textOff="Wznw"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="doClick" android:checked="true" />
</LinearLayout>

// Jest to plik MainActivity.java


import
import
import
import
import
import
import
import
import
import
import

java.io.IOException;
android.app.Activity;
android.content.Context;
android.content.res.AssetFileDescriptor;
android.media.AudioManager;
android.media.SoundPool;
android.os.Bundle;
android.os.Handler;
android.util.Log;
android.view.View;
android.widget.ToggleButton;

public class MainActivity extends Activity implements


SoundPool.OnLoadCompleteListener {
private static final int SRC_QUALITY = 0;
private static final int PRIORITY = 1;
private SoundPool soundPool = null;
private AudioManager aMgr;

614 Android 3. Tworzenie aplikacji


private
private
private
private
private

int
int
int
int
int

sid_background;
sid_roar;
sid_bark;
sid_chimp;
sid_rooster;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onResume() {
soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC,
SRC_QUALITY);
soundPool.setOnLoadCompleteListener(this);
aMgr =
(AudioManager)this.getSystemService(Context.AUDIO_SERVICE);
sid_background = soundPool.load(this, R.raw.crickets, PRIORITY);
sid_chimp = soundPool.load(this, R.raw.chimp, PRIORITY);
sid_rooster = soundPool.load(this, R.raw.rooster, PRIORITY);
sid_roar = soundPool.load(this, R.raw.roar, PRIORITY);
try {
AssetFileDescriptor afd =
this.getAssets().openFd("dogbark.mp3");
sid_bark = soundPool.load(afd.getFileDescriptor(),
0, afd.getLength(), PRIORITY);
afd.close();
} catch (IOException e) {
e.printStackTrace();
}

//sid_bark = soundPool.load("/mnt/sdcard/dogbark.mp3", PRIORITY);


super.onResume();
}
public void doClick(View view) {
switch(view.getId()) {
case R.id.button:
if(((ToggleButton)view).isChecked()) {
soundPool.autoResume();
}
else {
soundPool.autoPause();
}
break;
}
}
@Override
protected void onPause() {

Rozdzia 19 Uywanie szkieletu multimedialnego

615

soundPool.release();
soundPool = null;
super.onPause();
}
@Override
public void onLoadComplete(SoundPool sPool, int sid, int status) {
Log.v("soundPool", "sid " + sid + " wczytany ze stanem " +
status);
final float currentVolume =
((float)aMgr.getStreamVolume(AudioManager.STREAM_MUSIC)) /
((float)aMgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
if(status != 0)
return;
if(sid == sid_background) {
if(sPool.play(sid, currentVolume, currentVolume,
PRIORITY, -1, 1.0f) == 0)
Log.v("soundPool", "Proba odtworzenia dzwieku zakonczona niepowodzeniem");
} else if(sid == sid_chimp) {
queueSound(sid, 5000, currentVolume);
} else if(sid == sid_rooster) {
queueSound(sid, 6000, currentVolume);
} else if(sid == sid_roar) {
queueSound(sid, 12000, currentVolume);
} else if(sid == sid_bark) {
queueSound(sid, 7000, currentVolume);
}
}
private void queueSound(final int sid, final long delay,
final float volume)
{
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if(soundPool == null) return;
if(soundPool.play(sid, volume, volume,
PRIORITY, 0, 1.0f) == 0)
Log.v("soundPool", "Nie udalo sie odtworzyc dzwieku (" + sid +
")");
queueSound(sid, delay, volume);
}}, delay);
}
}

Struktura tego kodu nie jest zoona. Widzimy interfejs uytkownika zawierajcy jedn kontrolk
ToggleButton. Za jej pomoc bdziemy wstrzymywa i wznawia odtwarzanie aktywnych
strumieni dwikowych. Po uruchomieniu aplikacji tworzymy obiekt klasy SoundPool i wczytujemy do niego pliki dwikowe. Jeeli zostan one prawidowo wczytane, rozpoczynamy ich
odtwarzanie. Plik z cykaniem wierszczy zosta zaptlony i odtwarza si w sposb cigy, podczas gdy pozostae dwiki s odtwarzane po opnieniu, rwnie w zaptleniu. Dziki przyjciu rnych czasw opnienia dwiki nakadaj si na siebie w rozmaitych konfiguracjach.

616 Android 3. Tworzenie aplikacji


Do utworzenia klasy SoundPool wymagane s trzy parametry:
Pierwszym z nich jest maksymalna liczba prbek, ktre obiekt SoundPool bdzie
rwnoczenie odtwarza. Nie jest to rwnoznaczne z maksymaln liczb prbek
przechowywanych przez t klas.
Drugi parametr definiuje strumie audio, w ktrym bd odtwarzane cieki.
Typow wartoci jest AudioManager.STREAM_MUSIC, klasa SoundPool moe jednak
rwnie obsugiwa alarmy i dzwonki. Pen list strumieni dwikowych znajdziemy
w dokumentacji klasy AudioManager.
W momencie tworzenia obiektu SoundPool warto parametru SRC_QUALITY
powinna wynosi 0.
Widzimy w kodzie kilka rnych metod load() klasy SoundPool. Najprostsza z nich wczytuje
plik umieszczony w katalogu /res/raw w postaci zwykego zasobu. Stosujemy t metod
w przypadku pierwszych czterech plikw dwikowych. Nastpnie pokazujemy, e moemy te
wczytywa pliki dwikowe zamieszczone w katalogu /assets aplikacji. W tej metodzie load()
wstawiamy rwnie dodatkowe parametry definiujce pozycj i dugo wczytywanego strumienia audio. W ten sposb moglibymy wykorzystywa pojedynczy plik zawierajcy wiele rnych prbek dwikowych, gdy mona wybra jego fragment, ktrego w danej chwili naley
uy. W komentarzach pokazalimy, w jaki sposb moemy uzyska dostp do pliku audio
przechowywanego na karcie SD. We wszystkich obecnie istniejcych wersjach Androida (a
do wersji 3.0) parametr PRIORITY powinien posiada warto 1.
Postanowilimy w naszym przykadzie skorzysta z pewnych funkcji, dostpnych od wersji 2.2
Androida, konkretnie z interfejsu onLoadCompleteListener naszej aktywnoci oraz metod
autoPause() i autoResume() wykorzystywanych w kodzie obsugujcym przycisk.
W trakcie wczytywania plikw dwikowych do obiektu SoundPool musimy poczeka, a proces ten zostanie prawidowo zakoczony. W metodzie zwrotnej onLoadComplete() sprawdzamy postp wczytywania i w zalenoci od rodzaju dwiku wczamy jego odtwarzanie. Jeeli
mamy do czynienia z cykaniem wierszczy, wczamy zaptlenie (warto -1 w pitym parametrze). W przypadku pozostaych plikw tworzymy kolejk dwikw, odtwarzanych po krtkim odstpie czasowym. Dugo przerwy jest podawana w milisekundach. Zwrmy uwag
na ustawienia gonoci. Biecy poziom gonoci poznajemy za pomoc klasy AudioManager.
Za pomoc tej klasy okrelamy rwnie maksymalny poziom gonoci, dziki czemu moemy oblicza warto gonoci dla metody play(), mieszczc si w przedziale od 0 do 1
(warto typu float). Metoda play() w rzeczywistoci definiuje gono oddzielnie dla kanau lewego i prawego, jednak w naszym przypadku obydwa otrzymuj tak sam warto.
Przypominamy, e warto parametru PRIORITY powinna wynosi 1. Ostatni parametr metody
play() suy do okrelania szybkoci odtwarzania. Warto ta powinna mieci si w przedziale od 0.5 do 2, gdzie 1 definiuje standardow szybko.
Metoda queueSound() wykorzystuje obiekt Handler przede wszystkim po to, aby skonfigurowa zdarzenie, ktre nastpi w niedalekiej przyszoci. Obiekt typu Runnable zostanie uruchomiony po upywie czasu przeznaczonego na przerw. Nastpnie sprawdzamy, czy nadal
istnieje obiekt SoundPool, ponownie odtwarzamy dwik i przygotowujemy go do kolejnego
odtworzenia po upyniciu dokadnie takiego samego czasu przerwy co uprzednio. Poniewa
wywoujemy metod queueSound() zawierajc oddzielne identyfikatory plikw oraz czasy
przerwy, kolejno odtwarzania dwikw wydawanych przez zwierzta moe si wydawa
nieco losowa.

Rozdzia 19 Uywanie szkieletu multimedialnego

617

Po uruchomieniu tej aplikacji usyszymy wierszcze, szympansa, koguta, psa i ryk (podejrzewamy, e niedwiedzia). Usyszymy, e wierszcze cykaj nieprzerwanie, natomiast gosy
pozostaych zwierzt pojawiaj si i znikaj. Jedna z najlepszych cech klasy SoundPool jest taka,
e pozwala na odtwarzanie wielu dwikw naraz bez wielkiego udziau ze strony programisty.
Rwnie urzdzenie nie zostaje zbyt mocno obcione, poniewa dwiki zostay zdekodowane
ju w trakcie wczytywania do pamici i jedyne, co nam pozostaje, to przekaza je sprztowi.
Jeli klikniemy przycisk, odtwarzane gosy wierszczy oraz wszystkich aktualnie syszanych
zwierzt zostan wstrzymane. Jednak metoda autoPause() nie zatrzymuje odtwarzania innych
dwikw. Po okrelonym czasie znowu usyszymy gos jakiego zwierzaka (nie liczc wierszczy). Poniewa stworzylimy kolejk dwikw, ktre maj by odtwarzane w przyszoci, bdziemy je cigle sysze. W rzeczywistoci klasa SoundPool nie posiada adnej metody pozwalajcej na zatrzymywanie odtwarzanych i przewidzianych do odtworzenia dwikw. Trzeba
samodzielnie zaimplementowa odpowiedni funkcj. Odgosy wierszczy mog zosta na nowo
odtworzone wycznie po ponownym klikniciu przycisku. Jednak nawet wtedy niekoniecznie
zostan odtworzone, poniewa klasa SoundPool usuwa najstarszy dwik, aby zrobi miejsce
dla najnowszego, w przypadku gdy zostanie osignita maksymalna liczba odtwarzanych jednoczenie dwikw.

Odtwarzanie dwikw za pomoc klasy JetPlayer


Klasa SoundPool spisuje si cakiem niele jako odtwarzacz, jednak ograniczenia pamici
mog utrudnia wykonywanie niektrych zada. Alternatyw pozwalajc na rwnoczesne
odtwarzanie wielu plikw jest klasa JetPlayer. Jest to bardzo wszechstronne, przystosowane
gwnie do gier narzdzie umoliwiajce odtwarzanie wielu dwikw oraz koordynujce odtwarzanie tych dwikw z dziaaniami uytkownika. Wykorzystywane w tym przypadku dwiki
s definiowane za pomoc formatu MIDI (ang. Musical Instrument Digital Interface cyfrowy
interfejs instrumentw muzycznych).
Dwiki przetwarzane przez klas JetPlayer s tworzone za pomoc specjalnego narzdzia
JETCreator. Znajdziemy je w katalogu zestawu Android SDK, chocia aby z niego korzysta, musimy zainstalowa rodowisko Python. Wynikowy plik o rozszerzeniu .jet moe zosta
wczytany do aplikacji, po czym nastpuje odtworzenie dwiku. Cay proces jest do zaawansowany i wykracza poza zakres tej ksiki, proponujemy wic zajrze do podrozdziau
Odnoniki, aby znale tam odniesienia do dalszych informacji.

Odtwarzanie dwikw odgrywanych w tle za pomoc klasy AsyncPlayer


Jeeli zaley nam wycznie na odtwarzaniu jakiego pliku audio i nie chcemy obcia biecego
wtku, warto si zainteresowa klas AsyncPlayer. rdo dwikowe jest przekazywane do tej
klasy w postaci identyfikatora URI, dziki czemu dostpne s zarwno pliki lokalne, jak i umieszczone w sieci. Klasa ta automatycznie tworzy wtek drugoplanowy, ktry uzyskuje dostp do
pliku audio i go odtwarza. Poniewa jest to proces asynchroniczny, nie wiadomo dokadnie,
w ktrym momencie odtwarzanie zostanie uruchomione. Analogicznie, nie bdzie wiadomo,
czy odtwarzanie dwiku zostao zakoczone lub czy w ogle jeszcze trwa. Moemy jednak
wywoa metod stop(), aby zatrzyma odtwarzanie dwiku. Wywoanie metody play()
przed zakoczeniem odtwarzania poprzedniej cieki spowoduje jej zatrzymanie. Nowy plik
dwikowy zostanie uruchomiony pniej, ju po wyczyszczeniu i uporzdkowaniu pamici.
Klasa AsyncPlayer jest bardzo prost klas, automatycznie tworzc wtek drugoplanowy.
Na listingu 19.5 zaprezentowano przykadow implementacj tej klasy.

618 Android 3. Tworzenie aplikacji


Listing 19.5. Odtwarzanie dwikw za pomoc klasy AsyncPlayer
private static final String TAG = "AsyncPlayerDemo";
private AsyncPlayer mAsync = null;
[ ... ]
mAsync = new AsyncPlayer(TAG);
mAsync.play(this, Uri.parse("file://" + "/perry_ringtone.mp3"),
false, AudioManager.STREAM_MUSIC);
[ ... ]
@Override
protected void onPause() {
mAsync.stop();
super.onPause();
}

Niskopoziomowe odtwarzanie dwikw za pomoc klasy AudioTrack


Dotychczas zajmowalimy si dwikami pochodzcymi z plikw lokalnych oraz umieszczonych w sieci. Jeeli chcemy zaj si odtwarzaniem dwiku na nieco niszym poziomie, gdy
na przykad mamy do czynienia ze strumieniem danych audio, powinnimy przyjrze si klasie
AudioTrack. Oprcz standardowych metod play() i pause(), klasa AudioTrack posiada take
kilka klas pozwalajcych na zapisywanie danych bezporednio dotyczcych sprztu odtwarzajcego dwik. Dziki tej klasie posiadamy najwiksz kontrol nad odtwarzaniem dwiku, jest
ona jednak o wiele bardziej skomplikowania ni klasy opisywane powyej. Zaprezentujemy
przykadow aplikacj podczas omawiania klasy AudioRecord. Klasa AudioRecord bardzo
przypomina klas AudioTrack, wic w celu jej zrozumienia zapoznajmy si z informacjami
dotyczcymi tej pierwszej.

Osobliwoci klasy MediaPlayer


Oglnie rzecz biorc, klasa MediaPlayer wymaga duego uporzdkowania, dlatego aby odtwarzanie zostao waciwie przygotowane i uruchomione, musimy wywoywa operacje w cile
okrelonej kolejnoci. Ponisza lista podsumowuje niektre osobliwoci zwizane ze stosowaniem interfejsw API multimediw:
Po przypisaniu rda danych do klasy MediaPlayer nie bdzie atwo zmieni je na
inne musimy utworzy now klas MediaPlayer lub wywoa metod reset(),
suc do przywrcenia pierwotnego stanu odtwarzacza.
Po wywoaniu metody prepare() moemy wywoa metody getCurrentPosition(),
getDuration() i isPlaying() w celu uzyskania biecego stanu odtwarzacza.
Dostpne staj si rwnie metody setLooping() oraz setVolume().
Po wywoaniu metody start() istnieje moliwo wywoania metod pause(), stop()
i seekTo().
Kada klasa MediaPlayer tworzy nowy wtek, wic po zakoczeniu pracy z odtwarzaczem
musimy wywoa metod release(). W przypadku klasy VideoView jest to
przeprowadzane automatycznie, jednak jeeli zdecydujemy si korzysta z klasy
MediaPlayer, naley samemu wprowadzi t metod.

Rozdzia 19 Uywanie szkieletu multimedialnego

619

Na tym zakoczymy dyskusj dotyczc odtwarzania plikw dwikowych. Zwrcimy teraz


uwag na odtwarzanie plikw wideo. Jak zobaczymy, odniesienia do zawartoci wideo s podobne do omawianych uprzednio odniesie do plikw dwikowych.

Odtwarzanie plikw wideo


W tym punkcie zajmiemy si omwieniem zagadnie dotyczcych odtwarzania plikw wideo
za pomoc rodowiska SDK. W szczeglnoci zaprezentujemy odtwarzanie plikw wideo
umieszczonych na serwerze internetowym oraz znajdujcych si na karcie SD. Mona sobie
wyobrazi, e odtwarzanie plikw wideo jest nieco bardziej skomplikowane ni uruchamianie
plikw dwikowych. Na szczcie rodowisko Android SDK zawiera dodatkowe narzdzia
wykonujce wikszo trudniejszej roboty.
Odtwarzanie plikw wideo na emulatorze nie jest zbyt szczliwym rozwizaniem. Jeeli
plik wideo jest odtwarzany, to wietnie. W przeciwnym wypadku trzeba sprbowa
uruchomi aplikacj na urzdzeniu fizycznym. Emulacja polega wycznie na obliczeniach
programowych, wic podczas odtwarzania plikw wideo mog si pojawi olbrzymie
problemy, a samo uruchomienie aplikacji moe da nieprzewidziane rezultaty.

Odtwarzanie plikw wideo jest bardziej zoonym procesem, poniewa trzeba sobie poradzi
nie tylko ze skadow dwikow, ale rwnie z wizualn. Aby nieco uatwi spraw, w Androidzie uwzgldniono wyspecjalizowan kontrolk widoku, nazwan android.widget.VideoView,
ktra zajmuje si tworzeniem i uruchamianiem klasy MediaPlayer. Aby odtworzy plik wideo,
budujemy widet VideoView w interfejsie uytkownika. Nastpnie wprowadzamy ciek lub
identyfikator URI pliku wideo i wywoujemy metod start(). Na listingu 19.6 przedstawiamy
kod odpowiedzialny za odtwarzanie plikw wideo w Androidzie.
Listing 19.6. Odtwarzanie pliku wideo za pomoc interfejsw API multimediw
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout
android:layout_width="fill_parent" android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<VideoView
android:id="@+id/videoView"
android:layout_width="200px"
android:layout_height="200px" />
</LinearLayout>

// Jest to plik MainActivity.java


import
import
import
import
import

android.app.Activity;
android.net.Uri;
android.os.Bundle;
android.widget.MediaController;
android.widget.VideoView;

public class MainActivity extends Activity {

/** Wywoywane podczas pierwszego utworzenia klasy. */


@Override

620 Android 3. Tworzenie aplikacji


protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.main);
VideoView videoView = (VideoView)this.findViewById(R.id.videoView);
MediaController mc = new MediaController(this);
videoView.setMediaController(mc);
videoView.setVideoURI(Uri.parse(
"http://www.androidbook.com/akc/filestorage/android/" +
"documentfiles/3389/movie.mp4"));

/* videoView.setVideoPath(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES) +
"/movie.mp4");
*/
videoView.requestFocus();
videoView.start();
}
}

Na listingu 19.6 pokazalimy, w jaki sposb mona odtwarza plik wideo dostpny w internecie pod adresem www.androidbook.com/akc/filestorage/android/documentfiles/3389/movie.mp4.
Oznacza to, e aplikacja przetwarzajca ten kod bdzie daa uprawnienia android.
permission.INTERNET. Wszystkie funkcje odtwarzania pliku wideo znajduj si wewntrz klasy
VideoView. W rzeczywistoci wystarczy poda odtwarzaczowi adres pliku wideo. Interfejs
UI tej aplikacji jest pokazany na rysunku 19.4.

Rysunek 19.4. Interfejs UI odtwarzacza plikw wideo z aktywnymi przyciskami kontroli

Po uruchomieniu aplikacji przez mniej wicej trzy sekundy w dolnej czci ekranu bd widoczne przyciski, ktre nastpnie znikn. Kliknicie gdziekolwiek w widoku odtwarzacza spowoduje ich ponowne wywietlenie. Podczas odtwarzania pliku audio wystarczyy nam przyciski odpowiedzialne za odtwarzanie, wstrzymywanie i ponowne uruchamianie odtwarzania

Rozdzia 19 Uywanie szkieletu multimedialnego

621

pliku. Skadowa graficzna przedstawiajca plik audio nie bya nam do niczego potrzebna.
Oczywicie w przypadku plikw wideo potrzebujemy zarwno przyciskw, jak i pojemnika,
w ktrym moemy oglda obraz. W naszym przykadzie korzystamy ze skadowej VideoView
do ogldania pliku. Jednak zamiast tworzy wasne kontrolki przyciskw (w razie potrzeby
mamy tak moliwo), tworzymy obiekt MediaController, zawierajcy predefiniowane przyciski. Jak zostao pokazane na rysunku 19.4 i listingu 19.6, konfigurujemy kontroler multimediw poprzez wywoanie metody setMediaController() umoliwiajcej odtwarzanie, wstrzymywanie oraz przewijanie pliku wideo. Gdybymy chcieli wprowadzi wasne przyciski, moemy
wywoa metody start(), pause(), stopPlayback() i seekTo().
Nie zapominajmy, e w tym przykadzie cigle korzystamy z klasy MediaPlayer tylko jej nie
widzimy. W rzeczywistoci moemy odtwarza pliki wideo bezporednio z poziomu tej klasy.
Jeeli wrcimy do przykadu umieszczonego na listingu 19.1, umiecimy plik wideo na karcie
SD i wpiszemy ciek dostpu do tego filmu jako warto zmiennej AUDIO_PATH, stwierdzimy,
e dwik jest odtwarzany cakiem dobrze, chocia nie widzimy obrazu.
Podczas gdy klasa MediaPlayer zawiera metod setDataSource(), klasa VideoView jej nie
posiada, korzysta za to z metod setVideoPath() lub setVideoURI(). Zakadajc, e plik wideo
znajduje si na karcie SD, zmieniamy kod z listingu 19.6, tak aby oznaczy komentarzem wywoanie metody setVideoURI() oraz usun znaki komentarza z wywoania metody setVideoPath(),
i wpisujemy poprawn ciek do filmu. Po ponownym uruchomieniu aplikacji usyszymy
i zobaczymy odtwarzany plik wideo w widoku VideoView. Technicznie ten sam efekt moglibymy osign, wywoujc metod setVideoURI() w sposb przedstawiony poniej:
videoView.setVideoURI(Uri.parse("file://" +
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES) + "/movie.mp4"));

By moe Czytelnik zauway, e w przeciwiestwie do klasy MediaPlayer klasa VideoView


nie posiada metody pozwalajcej na odczytywanie danych z deskryptora pliku. Mona byo
take dostrzec, e klasa MediaPlayer posiada kilka metod pozwalajcych na dodanie obiektu
SurfaceHolder (stanowi on odpowiednik okna roboczego dla obrazw lub plikw wideo).
Jeeli musimy wywietla obraz wideo z prywatnego folderu aplikacji (na przykad odtwarza plik
znajdujcy si w katalogu data/data/), odpowiedniejsze oka si klasy MediaPlayer i Surface
Holder ni VideoView. Jedn z nadajcych si do tego celu metod klasy MediaPlayer jest
create(Context context, Uri uri, SurfaceHolder holder), natomiast drug setDisplay
(SurfaceHolder holder).
Przejdmy teraz do procesu rejestracji multimediw.

Rejestrowanie multimediw
Jak pokazalimy, w systemie Android istnieje wiele sposobw odtwarzania multimediw.
W przypadku rejestrowania danych multimedialnych posiadamy nieco mniej moliwoci. Gwn
klas robocz suc do zapisywania takich danych jest MediaRecorder, ktra jest stosowana
zarwno do plikw audio, jak i wideo. W tym podrozdziale zaprezentujemy sposb korzystania
z tej klasy do rejestrowania obydwu rodzajw danych. Drug klas, pozwalajc na nagrywanie
dwiku, jest klasa AudioRecord. Aby pokaza, w jaki sposb j wykorzystywa, utworzymy
osobny przykadowy projekt. Czasami nie musimy tworzy w caoci kodu, skoro istnieje aplikacja, ktra wykonuje zamierzone czynnoci. Pokaemy wic, w jaki sposb mona uruchomi
intencj suc do rejestrowania dwiku, a take jak fotografowa za pomoc aplikacji Camera.

622 Android 3. Tworzenie aplikacji

Analiza procesu rejestracji dwiku


za pomoc klasy MediaRecorder
Szkielet multimedialny w Androidzie obsuguje proces nagrywania dwiku. Jedn z klas sucych do rejestrowania dwiku jest android.media.MediaRecorder. W tym punkcie pokaemy sposb budowania aplikacji pozwalajcej na nagranie i pniejsze odtworzenie treci
dwikowej. Interfejs uytkownika tej aplikacji zosta pokazany na rysunku 19.5.

Rysunek 19.5. Interfejs UI przykadowej aplikacji nagrywajcej dwik

Jak wida na rysunku 19.5, aplikacja posiada cztery przyciski: dwa su do kontroli nagrywania, a dwa pozostae do odtwarzania i zatrzymywania odtwarzania nagranej treci. Na listingu
19.7 zawarto tre pliku ukadu graficznego oraz kod klasy aktywnoci tego interfejsu.
Listing 19.7. Nagrywanie i odtwarzanie dwiku w Androidzie
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/record.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/beginBtn" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Rozpocznij nagrywanie"
android:onClick="doClick"/>
<Button android:id="@+id/stopBtn" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Zatrzymaj nagrywanie"
android:onClick="doClick"/>
<Button android:id=
"@+id/playRecordingBtn" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Odtwarzaj nagranie"
android:onClick="doClick"/>
<Button android:id=
"@+id/stopPlayingRecordingBtn" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Zatrzymaj odtwarzanie"
android:onClick="doClick"/>

Rozdzia 19 Uywanie szkieletu multimedialnego

</LinearLayout>

// RecorderActivity.java
import
import
import
import
import
import
import

java.io.File;
android.app.Activity;
android.media.MediaPlayer;
android.media.MediaRecorder;
android.os.Bundle;
android.os.Environment;
android.view.View;

public class RecorderActivity extends Activity {


private MediaPlayer mediaPlayer;
private MediaRecorder recorder;
private String OUTPUT_FILE;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.record);
OUTPUT_FILE = Environment.getExternalStorageDirectory() +
"/recordaudio3.3gpp";
}
public void doClick(View view) {
switch(view.getId()) {
case R.id.beginBtn:
try {
beginRecording();
} catch (Exception e) {
e.printStackTrace();
}
break;
case R.id.stopBtn:
try {
stopRecording();
} catch (Exception e) {
e.printStackTrace();
}
break;
case R.id.playRecordingBtn:
try {
playRecording();
} catch (Exception e) {
e.printStackTrace();
}
break;
case R.id.stopPlayingRecordingBtn:
try {
stopPlayingRecording();
} catch (Exception e) {
e.printStackTrace();
}
break;

623

624 Android 3. Tworzenie aplikacji


}
}
private void beginRecording() throws Exception {
killMediaRecorder();
File outFile = new File(OUTPUT_FILE);
if(outFile.exists()) {
outFile.delete();
}
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile(OUTPUT_FILE);
recorder.prepare();
recorder.start();
}
private void stopRecording() throws Exception {
if (recorder != null) {
recorder.stop();
}
}
private void killMediaRecorder() {
if (recorder != null) {
recorder.release();
}
}
private void killMediaPlayer() {
if (mediaPlayer != null) {
try {
mediaPlayer.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void playRecording() throws Exception {
killMediaPlayer();
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(OUTPUT_FILE);
mediaPlayer.prepare();
mediaPlayer.start();
}
private void stopPlayingRecording() throws Exception {
if(mediaPlayer != null) {
mediaPlayer.stop();
}

Rozdzia 19 Uywanie szkieletu multimedialnego

625

}
@Override
protected void onDestroy() {
super.onDestroy();
killMediaRecorder();
killMediaPlayer();
}
}

Zanim zajmiemy si omawianiem listingu 19.7, musimy umieci w pliku manifecie nastpujcy
wpis o uprawnieniu, aby mc rejestrowa dwik:
<uses-permission android:name="android.permission.RECORD_AUDIO" />

W podrozdziale dotyczcym kart SD wspomnielimy rwnie, e w przypadku wartoci parametru minSdkVersion rwnej lub wikszej od 4 musimy doda znacznik uses-permission
dla klasy "android.permission.WRITE_EXTERNAL_STORAGE". Oczywicie, jeeli chcemy wyprbowa funkcj rejestrowania dwiku na emulatorze, musimy podczy mikrofon do stacji
roboczej.
Jeli przyjrzymy si metodzie onCreate() z listingu 19.7, zauwaymy, e jedyn niezbdn czynnoci jest wprowadzenie cieki do wyjciowego pliku dwikowego. Nasza metoda doClick()
wykorzystuje standardowy wzorzec przeczania pomidzy przyciskami, trzeba tylko przywoa
funkcj wykonujc waciwe zadanie. Metoda beginRecording() obsuguje proces rejestracji
dwiku. Aby nagrywa dwik, musimy utworzy wystpienie klasy MediaRecorder i skonfigurowa rdo dwiku, format pliku wynikowego, koder audio oraz ciek pliku wyjciowego.
W wersjach rodowiska Android SDK starszych od 1.6 jedynym obsugiwanym rdem dwiku by mikrofon. Od czasu wydania wersji Android SDK 1.6 dostpne s trzy dodatkowe rda
dwiku, wszystkie zwizane z rozmowami telefonicznymi. Moemy rejestrowa ca rozmow
(MediaRecorder.AudioSource.VOICE_CALL), transmisj prowadzon wycznie w gr sieci
(MediaRecorder.AudioSource.VOICE_UPLINK) lub transmisj prowadzon wycznie w d
sieci (MediaRecorder.AudioSource.VOICE_DOWNLINK). Transmisj prowadzon w gr sieci
moe by gos uytkownika telefonu. Transmisj prowadzon w d okrela si zazwyczaj dwiki
dochodzce z drugiego koca poczenia.
Wraz z wersj 2.1 Androida dodano obsug dwch nowych rde dwiku: CAMCORDER oraz
VOICE_RECOGNITION. rdo CAMCORDER moe by mikrofonem zwizanym z aparatem, w przeciwnym wypadku po wybraniu tej opcji bdzie stosowany domylny mikrofon urzdzenia.
Z kolei po wyborze trybu VOICE_RECOGNITION system wykorzystuje mikrofon przystosowany
do rozpoznawania mowy, a w przypadku jego braku znowu gwny mikrofon urzdzenia.
Poprzez stwierdzenie przystosowany do rozpoznawania mowy mamy na myli urzdzenie,
ktre zupenie nie przetwarza strumienia audio ani nie wprowadza adnych modyfikacji dwiku
na drodze pomidzy mikrofonem a aplikacj. Przykadem urzdze modyfikujcych rejestrowany dwik s niektre urzdzenia firmy HTC, ktrych mikrofony wyposaono w funkcj
automatycznej regulacji wzmocnienia (ang. Automatic Gain Control AGC). Wykorzystywanie tego urzdzenia w procesie rozpoznawania mowy moe stanowi problem. rdo
VOICE_RECOGNITION pomija tego typu dodatkowe przetwarzanie, dziki czemu proces przetwarzania mowy okazuje si skuteczniejszy.
Najpopularniejszym formatem wyjciowym dwiku jest format 3GPP (ang. 3rd Generation
Partnership Project partnerski projekt trzeciej generacji). W wersjach Androida starszych

626 Android 3. Tworzenie aplikacji


od 2.3.3 (Gingerbread) wartoci kodowania musi by AMR_NB, czyli wskopasmowy koder
audio AMR (ang. Adaptive Multi-Rate), poniewa jest to jedyny obsugiwany format kodera
dwiku. W wersji 2.3.3 Androida wprowadzono rwnie kodery AMR_WB (szerokopasmowy)
oraz AAC (ang. Advance Audio Coding zaawansowane kodowanie dwiku). W naszym
przykadzie zarejestrowany dwik jest zapisany na karcie SD w pliku recordoutput.3gpp. Na
listingu 19.7 zaoylimy, e utworzylimy obraz karty SD i powizalimy go z emulatorem.
Informacje potrzebne do przeprowadzenia tej czynnoci mona znale w podrozdziale
Wykorzystywanie kart SD.
Dla klasy MediaRecorder dostpne s dodatkowe metody, ktre mog si okaza przydatne.
Metody setMaxDuration(int length_in_ms) oraz setMaxFileSize(long length_in_bytes)
s stosowane do ograniczania dugoci i rozmiaru nagra dwikowych. Po osigniciu limitu
maksymalnej dugoci nagrania w milisekundach lub maksymalnego rozmiaru w bajtach rejestracja dwiku zostanie zakoczona. Obydwie metody zostay zaimplementowane w wersji
1.5 rodowiska SDK, zatem s w zasadzie obsugiwane przez wszystkie rodzaje telefonw
umoliwiajce rejestracj dwiku.

Rejestracja dwikw za pomoc klasy AudioRecord


Na razie pokazalimy, w jaki sposb mona rejestrowa dwiki bezporednio do pliku. A jak
naley postpi w przypadku, gdy chcemy wprowadzi przetwarzanie danych dwikowych
przed ich zapisaniem? A moe nawet nie trzeba wysya rejestrowanych danych do pliku?
Klasa AudioRecord spenia wszystkie tego rodzaju wymagania. W trakcie tworzenia obiektu
AudioRecord system przestawia si na zapisywanie danych audio do wewntrznego bufora tej
klasy, a nastpnie aplikacja moe wprowadza dowolne modyfikacje do rejestrowanych dwikw. Na listingu 19.8 widzimy aktywno odczytujc i przetwarzajc dane audio za pomoc
klasy AudioRecord. Nie jest nam tu potrzebny interfejs uytkownika, poniewa wszystkie
komunikaty bd wywietlane w oknie LogCat. Plik AndroidManifest.xml rwnie zosta pominity, musimy jednak w nim doda uprawnienie android.permission.RECORD_AUDIO.
Listing 19.8. Rejestrowanie nieprzetwarzanych danych audio za pomoc klasy AudioRecord
import
import
import
import
import
import

android.app.Activity;
android.media.AudioFormat;
android.media.AudioRecord;
android.media.MediaRecorder;
android.os.Bundle;
android.util.Log;

public class MainActivity extends Activity {


protected static final String TAG = "AudioRecord";
private int mAudioBufferSize;
private int mAudioBufferSampleSize;
private AudioRecord mAudioRecord;
private boolean inRecordMode = false;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initAudioRecord();
}
@Override

Rozdzia 19 Uywanie szkieletu multimedialnego

627

public void onResume() {


super.onResume();
Log.v(TAG, "Wznawianie...");
inRecordMode = true;
Thread t = new Thread(new Runnable() {
@Override
public void run() {
getSamples();
}
});
t.start();
}
protected void onPause() {
Log.v(TAG, "Wstrzymywanie...");
inRecordMode = false;
super.onPause();
}
@Override
protected void onDestroy() {
Log.v(TAG, "Konczenie...");
if(mAudioRecord != null) {
mAudioRecord.release();
Log.v(TAG, "Zwolniono klase AudioRecord");
}
super.onDestroy();
}
private void initAudioRecord() {
try {
int sampleRate = 8000;
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
mAudioBufferSize =
2 * AudioRecord.getMinBufferSize(sampleRate,
channelConfig, audioFormat);
mAudioBufferSampleSize = mAudioBufferSize / 2;
mAudioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC,
sampleRate,
channelConfig,
audioFormat,
mAudioBufferSize);
Log.v(TAG, "Ustanawianie obiektu AudioRecord udane. Rozmiar buforu = " +
mAudioBufferSize);
Log.v(TAG, " Rozmiar buforu testowego = " +
mAudioBufferSampleSize);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
int audioRecordState = mAudioRecord.getState();
if(audioRecordState != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "Obiekt AudioRecord zostal niewlasciwie zainicjalizowany");

628 Android 3. Tworzenie aplikacji


finish();
}
else {
Log.v(TAG, "Obiekt AudioRecord zostal zainicjalizowany");
}
}
private void getSamples() {
if(mAudioRecord == null) return;
short[] audioBuffer = new short[mAudioBufferSampleSize];
mAudioRecord.startRecording();
int audioRecordingState = mAudioRecord.getRecordingState();
if(audioRecordingState != AudioRecord.RECORDSTATE_RECORDING) {
Log.e(TAG, "Obiekt AudioRecord nie rejestruje dzwieku");
finish();
}
else {
Log.v(TAG, "Obiekt AudioRecord rozpoczal rejestrowanie dzwieku...");
}
while(inRecordMode) {
int samplesRead = mAudioRecord.read(
audioBuffer, 0, mAudioBufferSampleSize);
Log.v(TAG, "Uzyskano probki: " + samplesRead);
Log.v(TAG, "Wartosci kilku pierwszych probek: " +
audioBuffer[0] + ", " +
audioBuffer[1] + ", " +
audioBuffer[2] + ", " +
audioBuffer[3] + ", " +
audioBuffer[4] + ", " +
audioBuffer[5] + ", " +
audioBuffer[6] + ", " +
audioBuffer[7] + ", " +
audioBuffer[8] + ", " +
audioBuffer[9] + ", "
);
}
mAudioRecord.stop();
Log.v(TAG, "Obiekt AudioRecord zakonczyl proces rejestrowania");
}
}

Nasza przykadowa aplikacja jest raczej nieskomplikowana. Rozpoczynamy od zainicjalizowania obiektu AudioRecord. Musimy w tym celu wybra rdo dwiku, czstotliwo
prbkowania, konfiguracj kanaw (mono, stereo, lewy, prawy itd.), format kodowania
oraz rozmiar buforu wewntrznego. Jeli chodzi o rdo dwiku, mamy do dyspozycji zestaw opcji zdefiniowanych w klasie MediaRecorder.AudioSource. Musimy tylko wspomnie,
e nie wszystkie urzdzenia posiadaj zaimplementowane rdo VOICE_CALL, poniewa w rzeczywistoci wykorzystuje ono dwa urzdzenia wejciowe. Wrd czstotliwoci prbkowania
powinnimy wybra jedn ze standardowych wartoci: 8000, 16000, 44100, 22050 lub 11025 Hz.

Rozdzia 19 Uywanie szkieletu multimedialnego

629

Konfiguracja kanaw powinna by wybrana spord wartoci CHANNEL* opisanych w klasie


AudioFormat. Wrd formatw kodowania mamy do dyspozycji ENCODING_PCM_8BIT
i ENCODING_PCM_16BIT. Zwrmy uwag, e od wyboru tej wartoci zaley jako rejestrowanego
dwiku. Jeeli nie potrzebujemy 16-bitowej dokadnoci, wybierzmy kodowanie 8-bitowe
zaoszczdzimy nieco pamici i zyskamy na wydajnoci. W dokumentacji znalelimy
wzmiank, e jedynie prbkowanie 44100 Hz bdzie dziaa na wszystkich urzdzeniach, ale
jak na ironi emulator obsuguje wycznie wartoci 8000 Hz, CHANNEL_IN_MONO oraz
ENCODING_PCM_8BIT.
Klasa AudioRecord posiada statyczn metod pomocnicz, noszc nazw getMinBufferSize(),
ktra pobierze wszystkie zdefiniowane przez nas parametry i zwrci najmniejszy dopuszczalny
bufor, wystarczajcy do poprawnego inicjalizowania obiektu AudioRecord w danych warunkach. Nie mamy bezporedniego dostpu do tego buforu, jednak klasa AudioRecord zajmuje
wystarczajco wiele miejsca, aby przechowywa dane audio i jednoczenie przetwarza wczeniejsze dwiki. Mona skorzysta z minimalnego rozmiaru buforu albo nieco zwikszy jego
pojemno. Na pewno nie powinnimy ustanawia mniejszego buforu od wartoci zalecanej
przez metod pomocnicz. W naszym przykadzie ustanowilimy bufor dwukrotnie wikszy
od zalecanego minimum. Jeeli parametry nie bd akceptowane przez klas AudioRecord, zostanie wywietlony wyjtek IllegalArgumentException. Jeeli na przykad wprowadzimy
warto czstotliwoci prbkowania nieobsugiwan przez urzdzenie, zobaczymy ten wyjtek.
Niestety, nie ma atwego sposobu, aby uzyska list obsugiwanych czstotliwoci prbkowania,
zatem naszym jedynym rozwizaniem jest metoda prb i bdw; jeeli dana czstotliwo
powoduje wywietlenie wyjtku, musimy sprawdzi inn.
Na samym kocu metody inicjalizujcej sprawdzamy jeszcze, czy obiekt AudioRecord zosta
poprawnie utworzony, i ju mona rozpocz rejestrowanie dwiku.
Postanowilimy wczy prbkowanie w metodzie onResume(), a wyczy je w metodzie
onPause() naszej aktywnoci. Nie chcemy czy gwnego wtku z procesem prbkowania,
zatem tworzymy osobny wtek, sucy wycznie do obsugi tej czynnoci. Wprowadzamy
rwnie warto logiczn (inRecordMode), dziki czemu mona zaprogramowa przerwanie
prbkowania przez wtek. Wewntrz metody getSamples() tworzymy wasny bufor danych
dwikowych. Jak ju wspomnielimy, nie mamy bezporedniego dostpu do wewntrznego
buforu klasy AudioRecord, zatem odczytujemy nasz wasny bufor. Zwrmy uwag, e rozmiar
tego buforu zosta zadeklarowany za pomoc audioBufferSampleSize, a nie audioBufferSize.
Odczytujemy wycznie rozmiar prbki, gdy tylko ta informacja jest potrzebna naszemu
buforowi. Uruchamiamy rejestracj w obiekcie AudioRecord, sprawdzamy, czy stan zmieni si
na RECORDING, i zaczynamy zaptla odczyty. S to odczyty blokujce, na szczcie znajdujemy
si w osobnym wtku, wic nie ma adnego problemu. Gdy obiekt AudioRecord zaczyna zblia si do zdefiniowanego rozmiaru danych, przekazuje odczytane wyniki, dziki czemu mona
w dalszym cigu przetwarza t prbk audio.
W midzyczasie klasa AudioRecord bdzie zbiera dodatkowe informacje o dwiku na wypadek nastpnego wywoania odczytu. Mamy ograniczony czas na przetwarzanie danych, zanim bufor klasy AudioRecord zostanie ponownie zapeniony, zatem musimy zachowa tu ostrono i nie pobiera zbyt duej iloci danych. W zalenoci od ich przeznaczenia moemy po
prostu zatrzyma proces rejestrowania i pniej go ponowi. W naszym przykadzie wykorzystujemy po prostu okno LogCat do poinformowania o zarejestrowaniu prbek i wywietlamy dziesi pierwszych wartoci. W trakcie rejestracji nagrywajmy rne dwiki, aby przekona si, e wartoci wywietlane w oknie LogCat ulegaj zmianie.

630 Android 3. Tworzenie aplikacji


Zaptlenie odczytu trwa do czasu, a parametr inRecordMode osignie warto
nastpi w momencie ukrycia lub zamknicia aplikacji.

false,

co

W trakcie uwanego przegldania dokumentacji klasy AudioRecord moemy natrafi na zwrotne


interfejsy. Pozwalaj one na konfigurowanie obiektw nasuchujcych dotyczcych albo osignicia znacznika wewntrz strumienia audio, albo okazjonalnego uruchomienia metod zwrotnych. Na listingu 19.9 zmodyfikowalimy powyszy przykad poprzez dodanie odpowiednich
instrukcji. Peny kod rdowy tego projektu znajdziemy na naszej stronie WWW.
Listing 19.9. Rejestrowanie nieskompresowanych danych audio za pomoc klasy AudioRecord i
metod zwrotnych
// Ten kod znajduje si wewntrz klasy aktywnoci
public OnRecordPositionUpdateListener mListener = new
OnRecordPositionUpdateListener() {
public void onPeriodicNotification(AudioRecord recorder) {
Log.v(TAG, "w metodzie onPeriodicNotification");
}
public void onMarkerReached(AudioRecord recorder) {
Log.v(TAG, "w metodzie onMarkerReached");
inRecordMode = false;
}
};

// Ponisze instrukcje znajduj si wewntrz metody initAudioRecord() po


// utworzeniu obiektu mAudioRecord, lecz jeszcze przed sprawdzeniem
// jego stanu.
mAudioRecord.setNotificationMarkerPosition(10000);
mAudioRecord.setPositionNotificationPeriod(1000);
mAudioRecord.setRecordPositionUpdateListener(mListener);

Zwrmy uwag, e obiekt nasuchujcy posiada dwie oddzielne metody zwrotne. Pierwsza
z nich jest wywoywana co 1000 ramek, co zostao zdefiniowane w metodzie inicjalizujcej. Ten
licznik ramek jest niezaleny od rozmiaru buforu. Nawet jeli jednorazowo bdziemy odczytywa 1600 ramek, metoda ta bdzie wywoywana co 1000 ramek. Zatem w tym przypadku
wspomniana metoda bdzie przywoana dwukrotnie w czasie jednej ptli. Druga metoda jest
wywoywana, gdy osigniemy cakowit liczb ramek. W naszym przykadzie zdefiniowalimy
t warto jako 10 000 ramek; po jej osigniciu rejestrowanie dwiku zostaje zakoczone poprzez wprowadzenie wartoci logicznej false. Gdybymy jedynie wypisali komunikat o osigniciu tej wartoci i nie wyczyli procesu rejestrowania, warto ta nie pojawiaby si ju
ponownie, niezalenie od liczby ramek odczytanych w przyszoci. Jest to znacznik relatywny
od momentu wywoania metody startRecording() w obiekcie AudioRecord.

Analiza procesu rejestracji wideo


Od wersji 1.5 rodowiska Android SDK wprowadzono moliwo rejestrowania obrazu wideo
za pomoc struktury multimedialnej. Zasada dziaania jest podobna do procesu rejestracji
dwiku i rzeczywicie, nagrany obraz wideo przewanie posiada rwnie ciek dwikow.
W przypadku rejestracji wideo istnieje jednak jedna rnica. Poczwszy od rodowiska Android

Rozdzia 19 Uywanie szkieletu multimedialnego

631

SDK 1.6, wymagane jest, aby na obiekcie Surface by tworzony podgld rejestrowanego obrazu. W prostych aplikacjach nie stanowi to problemu, poniewa uytkownik bdzie chcia widzie podgld tego, co nagrywa. W bardziej zoonych programach moe si pojawi problem.
Nawet jeeli aplikacja nie musi pokazywa uytkownikowi podgldu wideo w trakcie nagrywania, obiekt Surface nadal musi by obecny, gdy takie jest wymaganie klasy camera.
Spodziewamy si, e wymg ten zostanie w przyszych wersjach rodowiska SDK zagodzony,
tak aby aplikacje mogy pracowa bezporednio na buforach wideo bez koniecznoci kopiowania ich do interfejsu UI, na razie jednak musimy korzysta z obiektu Surface i pokaemy,
jak naley to robi. Omawiany przykadowy kod jest dosy dugi, zatem podzielilimy go na
czci, dziki czemu atwiej nam bdzie omawia jego poszczeglne zagadnienia. Najprawdopodobniej Czytelnik zechce pobra ten projekt z naszej strony i zaimportowa go do rodowiska Eclipse. Niezbdne instrukcje znajd si na kocu rozdziau, w podrozdziale Odnoniki. Rozpoczniemy od ukazania na listingu 19.10 wykorzystanego ukadu graficznego.
Listing 19.10. Ukad graficzny aplikacji rejestrujcej dane wideo
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout-land/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="horizontal" >
<LinearLayout
android:orientation="vertical" android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button android:id="@+id/initBtn"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Inicjalizacja rejestratora" android:onClick="doClick"
android:enabled="false" />
<Button android:id="@+id/beginBtn"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Rozpocznij rejestrowanie" android:onClick="doClick"
android:enabled="false" />
<Button android:id="@+id/stopBtn"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Zatrzymaj rejestrowanie" android:onClick="doClick" />
<Button android:id="@+id/playRecordingBtn"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Odtwarzaj nagranie" android:onClick="doClick" />
<Button android:id="@+id/stopPlayingRecordingBtn"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Zatrzymaj odtwarzanie" android:onClick="doClick" />
</LinearLayout>
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="fill_parent" >
<TextView android:id="@+id/recording" android:text=" "
android:textColor="#FF0000"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<VideoView android:id="@+id/videoView"

632 Android 3. Tworzenie aplikacji


android:layout_width="250dip" android:layout_height="200dip" />
</LinearLayout>
</LinearLayout>

Na rysunku 19.6 widzimy powyszy ukad graficzny w penej okazaoci. Zrzut ekranu zosta
wykonany na fizycznym urzdzeniu podczas rejestrowania obrazu wideo, przedstawiajcego
okno robocze rodowiska Eclipse na stacji roboczej.

Rysunek 19.6. Interfejs rejestratora wideo

Ukad ten skada si z dwch umieszczonych obok siebie pojemnikw LinearLayout, znajdujcych si w nadrzdnym kontenerze LinearLayout. Z lewej strony widzimy pi przyciskw,
ktre nasza aplikacja bdzie nam udostpniaa i wyczaa w trakcie analizowania przykadu.
Z prawej strony zosta umieszczony gwny widok VideoView, nad nim za znajduje si napis
REJESTROWANIE, ktry zostaje wywietlony w trakcie nagrywania obrazu. Jak ju si Czytelnik prawdopodobnie domyli, wymuszamy uoenie aplikacji w trybie krajobrazowym poprzez
umieszczenie atrybutu android:screenOrientation="landscape" w znaczniku <activity>
pliku AndroidManifest.xml. Rozpocznijmy analiz aplikacji od klasy MainActivity, zaprezentowanej na listingu 19.11.
Listing. 19.11. Gwna aktywno rejestratora wideo
public class MainActivity extends Activity implements
SurfaceHolder.Callback, OnInfoListener, OnErrorListener {
private
private
private
private
private
private
private
private
private
private
private
private

static final String TAG = "RecordVideo";


MediaRecorder mRecorder = null;
String mOutputFileName;
VideoView mVideoView = null;
SurfaceHolder mHolder = null;
Button mInitBtn = null;
Button mStartBtn = null;
Button mStopBtn = null;
Button mPlayBtn = null;
Button mStopPlayBtn = null;
Camera mCamera = null;
TextView mRecordingMsg = null;

/** Tworzone podczas pierwszego wywoania aktywnoci. */

Rozdzia 19 Uywanie szkieletu multimedialnego

633

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v(TAG, "w metodzie onCreate");
setContentView(R.layout.main);
mInitBtn = (Button) findViewById(R.id.initBtn);
mStartBtn = (Button) findViewById(R.id.beginBtn);
mStopBtn = (Button) findViewById(R.id.stopBtn);
mPlayBtn = (Button) findViewById(R.id.playRecordingBtn);
mStopPlayBtn = (Button) findViewById(R.id.stopPlayingRecordingBtn);
mRecordingMsg = (TextView) findViewById(R.id.recording);
mVideoView = (VideoView)this.findViewById(R.id.videoView);
}

// Pozostaa cz klasy zostaa umieszczona na nastpnych listingach.


}

Korzystamy w tej aplikacji ze standardowej aktywnoci, jednak implementujemy w niej rwnie trzy interfejsy. Pierwszy z nich, SurfaceHolder.Callback, suy do otrzymywania informacji o tym, kiedy obiekt Surface bdzie przygotowany do wywietlania obrazu wideo. Obiekt
Surface w naszym przypadku pochodzi z klasy VideoView. Chcemy by rwnie informowani
o wszelkich komunikatach wychodzcych z klasy MediaRecorder, dlatego implementujemy dwa
pozostae interfejsy: OnInfoListener oraz OnErrorListener. Wkrtce zajmiemy si omwieniem metod wspomnianych interfejsw.
Nasza aktywno zawiera kilka pl czonkowskich, ktre bd potrzebne pniej. Cz z nich
inicjalizujemy w metodzie onCreate(). Na razie zamiecilimy jedynie komentarz wskazujcy,
gdzie zostanie umieszczona reszta klasy MainActivity. Wspomniane metody klasy zostan ukazane na kolejnych listingach, poczwszy od listingu 19.12, na ktrym prezentujemy standardowe metody onResume() i onPause().
Listing 19.12. Kod obsugujcy wstrzymywanie i ponawianie pracy rejestratora wideo
@Override
protected void onResume() {
Log.v(TAG, "w metodzie onResume");
super.onResume();
mInitBtn.setEnabled(false);
mStartBtn.setEnabled(false);
mStopBtn.setEnabled(false);
mPlayBtn.setEnabled(false);
mStopPlayBtn.setEnabled(false);
if(!initCamera())
finish();
}
@Override
protected void onPause() {
Log.v(TAG, "w metodzie onPause");
super.onPause();
releaseRecorder();
releaseCamera();
}

634 Android 3. Tworzenie aplikacji


Na listingu 19.12 przedstawilimy metody bdce czci klasy MainActivity;
umiecilimy je na oddzielnych listingach jedynie w celu lepszego wyjanienia ich
dziaania. Taka sama zasada dotyczy pozostaych listingw czcych si z kodem
aplikacji Rejestrator Wideo.

Jak wida, w prezentowanym kodzie umiecilimy cakowicie standardowe metody. W metodzie onResume() ustawiamy po prostu stan pocztkowy przyciskw, a nastpnie uruchamiamy
kamer (wkrtce zapoznamy si z t metod). W metodzie onPause() musimy zwolni zarwno
obiekt MediaRecorder, jak i Camera. W ten sposb po kadym schowaniu aplikacji rejestrowanie bdzie zatrzymane, a kamera zostanie zwolniona do dyspozycji innych programw. Jeeli
uytkownik powrci do naszej aplikacji, jej dziaanie zostanie wznowione i znowu bdzie mona
rejestrowa obraz wideo. Na listingu 19.13 pokazujemy kod inicjalizujcy kamer, metody
zwrotne interfejsu SurfaceHolder.Callback, a take metody obsugujce zwalnianie obiektw
Camera i MediaRecorder.
Listing 19.13. Metody initCamera() oraz zwalniajce kamer
private boolean initCamera() {
try {
mCamera = Camera.open();
Camera.Parameters camParams = mCamera.getParameters();
mCamera.lock();

//mCamera.setDisplayOrientation(90);
// Mona rwnie ustawi tutaj inne parametry i zastosowa:
//mCamera.setParameters(camParams);
mHolder = mVideoView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
catch(RuntimeException re) {
Log.v(TAG, "Nie mozna zainicjalizowac obiektu Camera");
re.printStackTrace();
return false;
}
return true;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.v(TAG, "w metodzie surfaceCreated");
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (IOException e) {
Log.v(TAG, "Nie mozna uruchomic podgladu");
e.printStackTrace();
}
mInitBtn.setEnabled(true);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {

Rozdzia 19 Uywanie szkieletu multimedialnego

635

Log.v(TAG, "w metodzie surfaceDestroyed");


}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.v(TAG, "surfaceChanged: Szerokosc x Wysokosc = " + width + "x" + height);
}
private void releaseRecorder() {
if(mRecorder != null) {
mRecorder.release();
mRecorder = null;
}
}
private void releaseCamera() {
if(mCamera != null) {
try {
mCamera.reconnect();
} catch (IOException e) {
e.printStackTrace();
}
mCamera.release();
mCamera = null;
}
}

Metoda initCamera() suy do skonfigurowania poczenia z kamer urzdzenia. Od tego


miejsca zaczyna si praca aplikacji. W tej aplikacji korzystamy z domylnych parametrw kamery, moemy jednak w atwy sposb dosta si do biecych parametrw kamery, zaktualizowa je i zapisa. W zaznaczonym jako komentarz fragmencie kodu wida, gdzie mona zmienia
wygld i zachowanie kamery. Po ustawieniu kamery bierzemy si za interfejs SurfaceHolder,
w ktrym bdzie si pojawia obraz wideo.
W metodzie zwrotnej surfaceCreated() prezentujemy obiektowi kamery miejsce do wywietlania biecego widoku, inaczej mwic, podgldu. Po uruchomieniu podgldu moemy uaktywni przycisk inicjalizujcy obiekt MediaRecorder. Podgld kamery jest bardzo przydatn
funkcj, dziki ktrej uytkownik moe zobaczy, na co w danej chwili jest skierowany obiektyw, jeszcze zanim rozpocznie si proces rejestracji. Bez wzgldu na to, czy rejestrujemy obraz
wideo, czy robimy zwyke zdjcia, najlepiej jest zaopatrzy aplikacj w funkcj podgldu dokadnie w zaprezentowany powyej sposb.
W celu zachowania cigoci opisu pokazalimy rwnie metody releaseRecorder() oraz
releaseCamera(). S one wywoywane w metodzie onPause(), co widzielimy na listingu 19.12.
W tym momencie mamy ju skonfigurowan kamer, zainicjalizowane przyciski oraz widoczny
podgld kamery. Uytkownik moe zacz teraz klika przyciski, chocia obecnie jedynym
aktywnym jest Inicjalizacja rejestratora. Po jego wciniciu zostanie wykonany kod widoczny na listingu 19.14. Widzimy w nim pi dziaa, kade powizane z jednym przyciskiem. Po wykonaniu kadego zadania odpowiednie przyciski bd wczane lub wyczane, w zalenoci od
nastpnego dziaania. Na przykad po zainicjalizowaniu rejestratora przycisk Inicjalizacja rejestratora zostanie wyczony, natomiast zostanie uaktywniony przycisk Rozpocznij rejestrowanie.

636 Android 3. Tworzenie aplikacji


Listing 19.14. Kod przetwarzania dziaa przyciskw w rejestratorze wideo
public void doClick(View view) {
switch(view.getId()) {
case R.id.initBtn:
initRecorder();
break;
case R.id.beginBtn:
beginRecording();
break;
case R.id.stopBtn:
stopRecording();
break;
case R.id.playRecordingBtn:
playRecording();
break;
case R.id.stopPlayingRecordingBtn:
stopPlayingRecording();
break;
}
}
private void initRecorder() {
if(mRecorder != null) return;
mOutputFileName = Environment.getExternalStorageDirectory() +
"/videooutput.mp4";
File outFile = new File(mOutputFileName);
if(outFile.exists()) {
outFile.delete();
}
try {
mCamera.stopPreview();
mCamera.unlock();
mRecorder = new MediaRecorder();
mRecorder.setCamera(mCamera);
mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setVideoSize(176, 144);
mRecorder.setVideoFrameRate(15);
mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mRecorder.setMaxDuration(7000); // ograniczenie do 7 sekund
mRecorder.setPreviewDisplay(mHolder.getSurface());
mRecorder.setOutputFile(mOutputFileName);
mRecorder.prepare();
Log.v(TAG, "Obiekt MediaRecorder zainicjalizowany");
mInitBtn.setEnabled(false);
mStartBtn.setEnabled(true);
}
catch(Exception e) {

Rozdzia 19 Uywanie szkieletu multimedialnego

637

Log.v(TAG, "Inicjalizacja obiektu MediaRecorder zakonczona niepowodzeniem");


e.printStackTrace();
}
}
private void beginRecording() {
mRecorder.setOnInfoListener(this);
mRecorder.setOnErrorListener(this);
mRecorder.start();
mRecordingMsg.setText("REJESTROWANIE");
mStartBtn.setEnabled(false);
mStopBtn.setEnabled(true);
}
private void stopRecording() {
if (mRecorder != null) {
mRecorder.setOnErrorListener(null);
mRecorder.setOnInfoListener(null);
try {
mRecorder.stop();
}
catch(IllegalStateException e) {

// Moe si to przytrafi, jeli rejestrator zosta ju zatrzymany.


Log.e(TAG, "Wyjatek IllegalStateException w metodzie stopRecording");
}
releaseRecorder();
mRecordingMsg.setText("");
releaseCamera();
mStartBtn.setEnabled(false);
mStopBtn.setEnabled(false);
mPlayBtn.setEnabled(true);
}
}
private void playRecording() {
MediaController mc = new MediaController(this);
mVideoView.setMediaController(mc);
mVideoView.setVideoPath(mOutputFileName);
mVideoView.start();
mStopPlayBtn.setEnabled(true);
}
private void stopPlayingRecording() {
mVideoView.stopPlayback();
}

W metodzie initRecorder() nastpuje spora cz procesu konfiguracji. Rejestrator musi


otrzyma ciek docelow nagrywanego obrazu, zatem wprowadzamy jej warto. Usuwamy
plik, jeli ju taki istnieje. Warto zwrci uwag, w jaki sposb zatrzymalimy podgld obrazu
kamery, jej odblokowanie i podczenie do obiektu MediaRecorder. Kamera jest w pewien sposb
wraliwa na blokowanie lub odblokowywanie, a czasami trzeba j zablokowa, aby uniemoliwi
innym aplikacjom dostp do niej. Z kolei w innych sytuacjach trzeba j odblokowa, aby wykonywa za jej pomoc wczeniej niedostpne operacje. Teraz wanie musimy j odblokowa
i podczy do obiektu MediaRecorder. Po podczeniu kamery konfigurujemy reszt atrybutw

638 Android 3. Tworzenie aplikacji


klasy MediaRecorder, w tym rdo dwiku i obrazu. Czytelnik pewnie zorientowa si, e
przed chwil podczylimy kamer do tego obiektu. Owszem, podczylimy. Cigle jednak
musimy w jawny sposb ustawi rdo obrazu wideo. Poprzez podczenie kamery do rejestratora unikamy koniecznoci usunicia obiektu Camera tylko po to, aby rejestrator utworzy
nowy obiekt Camera. Przed wywoaniem metody prepare() ustanawiamy jeszcze kodery audio
i wideo, a take ciek do wynikowego pliku na karcie SD. Metoda prepare() zostaje wywoana pod sam koniec i rzeczywicie przygotowuje program do waciwego procesu rejestracji.
Koczymy t metod uaktywnieniem przycisku Rozpocznij rejestrowanie.
Dla porwnania metoda beginRecording() jest do nieskomplikowana. Dodaje ona
obiekty nasuchujce, wywouje metod start(), nastpnie ustanawia komunikat o trwajcej rejestracji oraz zmienia stan przyciskw. Po przetworzeniu kodu tej metody aplikacja powinna ju rejestrowa obraz wideo, a take wywietli napis REJESTROWANIE, widoczny na
rysunku 19.6.
Metoda stopRecording() jest nieco bardziej zoona, czciowo dlatego, e moe zosta
wywoana z kilku miejsc. Za chwil omwimy drugie takie miejsce, na razie jednak zamy, e
przycisk Zatrzymaj rejestrowanie uruchomi t metod. Jeeli cay czas rejestrator pracuje poprawnie, zatrzymujemy metody zwrotne, a nastpnie wywoujemy metod stop(). Poniewa
istnieje moliwo, e metoda stop() zostanie wywoana wobec ju wczeniej zatrzymanego
rejestratora, wywietli si informujcy nas o tym fakcie wyjtek. Nastpnie program zwalnia
rejestrator oraz kamer i wycza napis REJESTROWANIE. Na koniec przyciski rejestrowania
oraz odtwarzania zamieniaj si stanami.
Metoda playRecording() jest rwnie nieskomplikowana. Obiekt MediaRecorder umieszczamy w widoku VideoView, wskazujemy nowy plik i wywoujemy metod start(). Metoda
stopPlayingRecording() jest jeszcze prostsza: zatrzymujemy po prostu odtwarzanie pliku
wideo. Gdy aplikacja znajduje si w trybie odtwarzania, nic si nie stanie, jeeli klikniemy przycisk Odtwarzaj w czasie ogldania wideo lub przycisk Zatrzymaj, gdy odtwarzanie zostao ju
zatrzymane.
Zauwaylimy, e rejestrowanie moe zosta zatrzymane z kilku miejsc. Jedno z ustawie rejestratora definiuje maksymalnie siedmiosekundowy czas nagrywania. Oznacza to, e proces
rejestrowania zostanie po siedmiu sekundach automatycznie zatrzymany i wtedy nastpi wywoanie naszej informacyjnej metody zwrotnej. Spjrzmy teraz na fragment kodu sucy do
obsugi tego mechanizmu, umieszczony na listingu 19.15.
Listing 19.15. Informacyjne metody zwrotne rejestratora wideo
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
Log.i(TAG, "nastapilo zdarzenie rejestrowania");
if(what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
Log.i(TAG, "...osiagniety maksymalny czas rejestrowania");
stopRecording();
Toast.makeText(this, "Zosta osignity limit rejestrowania. Zatrzymywanie.",
Toast.LENGTH_SHORT).show();
}
}
@Override
public void onError(MediaRecorder mr, int what, int extra) {
Log.e(TAG, "nastapil blad rejestrowania");

Rozdzia 19 Uywanie szkieletu multimedialnego

639

stopRecording();
Toast.makeText(this, "Nastpi bd rejestrowania. Zatrzymywanie rejestrowania.",
Toast.LENGTH_SHORT).show();
}
}

Te dwie metody zwrotne s do siebie bardzo podobne. Jedyna rnica pomidzy nimi polega
na okolicznociach, w jakich s wywoywane. W metodzie onInfo() komunikaty nie s uznawane za wyniki bdw. Metoda ta moe by wywoywana w przypadku osignicia limitu dugoci rejestrowanego materiau albo maksymalnego rozmiaru pliku, jeeli takie ograniczenia
znajd si w rejestratorze. W przypadku metody onError() dokumentacja nie wyraa si zbyt
jasno na temat okolicznoci jej wywoywania, ale moe tak by w przypadku, gdy wyczerpuje
si miejsce w magazynie, na ktrym zapisywane s rejestrowane dane. Jeeli metoda onInfo()
zostanie wywoana z powodu osignicia limitu czasowego lub jeli pojawi si jaki bd rejestrowania, zostanie ono zatrzymane.
Podobnie jak w przypadku procesu rejestrowania audio, tak i teraz musimy przydzieli uprawnienia dla rejestracji dwiku (android.permission.RECORD_AUDIO) i dostpu do karty SD
(android.permission.WRITE_EXTERNAL_STORAGE), a dodatkowo musimy jeszcze doda uprawnienie dostpu do kamery (android.permission.CAMERA). Na listingu zamieszczamy kod pliku
AndroidManifest.xml. Zauwamy, e wymuszamy poziom orientacj naszej aplikacji, dlatego
wanie plik ukadu graficznego znajduje si w katalogu /res/layout-land/main.xml.
Listing 19.16. Plik AndroidManifest.xml rejestratora wideo
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.record.video"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="4" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
</manifest>

Klasy CameraProfile i CamcorderProfile


Na listingu 19.14 dostrzeglimy w metodzie initRecorder() szereg bardzo specyficznych
ustawie rejestratora wideo. Pytanie brzmi: skd moemy wiedzie, jakie moliwoci posiada
urzdzenie, na ktrym zostaa uruchomiona nasza aplikacja? Przed wersj 2.2 Androida na to

640 Android 3. Tworzenie aplikacji


pytanie nie byo dobrej odpowiedzi. Fabrycznie instalowana aplikacja Aparat korzysta z nieudokumentowanej klasy SystemProperties. Zatem w starszych wersjach Androida trzeba
byo samodzielnie wybiera wartoci, ktre dziaayby na docelowych urzdzeniach. Nie byo to
wcale satysfakcjonujce rozwizanie, zwaszcza e nowsze urzdzenia byy wyposaane w coraz lepsze aparaty fotograficzne czy kamery. Aby naprawi sytuacj, w wersji 2.2 Androida wprowadzono
dwie nowe klasy: CameraProfile i CamcorderProfile. Klasy te s po prostu pojemnikami na
potrzebne atrybuty aparatu fotograficznego bd kamery. Klasa CameraProfile posiada tylko
jedn warto (JPEG Encoding Quality Parameter), podczas gdy w klasie CamcorderProfile
moemy definiowa czstotliwo odwieania, rozmiar klatki (wysoko i szeroko), a take
inne parametry dwiku i obrazu. To nie wszystko, gdy klasa MediaRecorder akceptuje parametry przechowywane w klasie CamcorderProfile i pozwala w ten sposb na definiowanie rnorodnych ustawie rejestrowania obrazu. Musimy tylko uwaa, aby wywoywa metod set
Profile() po ustanowieniu rde audio i wideo, lecz przed zdefiniowaniem pliku wynikowego.
Wraz z wprowadzeniem wersji 2.3 Androida metody do obsugi kamery lub aparatu fotograficznego uzyskay alternatywne wersje, umoliwiajce korzystanie z identyfikatorw kamer
lub aparatw. Wczeniej wikszo urzdze posiadaa tylko jedn kamer (aparat fotograficzny), zazwyczaj umieszczon w tylnej ciance. W przypadku nowych urzdze, w ktrych
oprcz standardowej kamery czy aparatu fotograficznego dostpna jest rwnie kamera (aparat)
umieszczona z przodu urzdzenia, kod musi w jaki sposb rozrnia obsugiwane kamery
i aparaty fotograficzne. Na przykad w klasie Camera metoda open() przekae obiekt Camera
dla aparatu fotograficznego znajdujcego si z tyu, jeli takowy jest obecny. Mamy do dyspozycji metod open(int cameraid), definiujc okrelony aparat fotograficzny, dziki czemu moemy korzysta z aparatu umieszczonego z przodu, jeli jest dostpny. Aby okreli liczb dostpnych aparatw fotograficznych oraz je rozrnia od siebie, moemy wykorzysta metod
Camera.getNumberOfCameras(), przekazujc liczb dostpnych aparatw, oraz Camera.
getCameraInfo(), wywietlajc informacje o danym aparacie, take o jego umiejscowieniu.

Analiza klasy MediaStore


Dotychczas zajmowalimy si multimediami poprzez bezporednie tworzenie klas odpowiedzialnych za odtwarzanie i rejestrowanie plikw w naszej aplikacji. Jedn z lepszych cech Androida jest moliwo czenia si z innymi aplikacjami, ktre mog wykona ca prac. W klasie MediaStore zosta zaimplementowany interfejs obsugujcy multimedia przechowywane
w urzdzeniu zarwno w jego wntrzu, jak i na zewntrz.
Dostpne s tu rwnie interfejsy API pozwalajce na przeprowadzanie operacji na tych plikach. Moemy na przykad przeszukiwa urzdzenie pod ktem okrelonych formatw plikw
multimedialnych, korzysta z intencji umoliwiajcych rejestrowanie dwiku i wideo w magazynie, tworzy listy odtwarzania i tak dalej. Klasa ta bya dostpna w starszych wersjach rodowiska Android SDK, ale od wersji 1.5 zostaa znacznie usprawniona.
Poniewa klasa MediaStore, podobnie jak klasa MediaRecorder, obsuguje intencje odpowiedzialne za rejestrowanie dwiku i wideo, rodzi si oczywiste pytanie: kiedy naley stosowa
klas MediaStore, a kiedy klas MediaRecorder? Na przykadach aplikacji sucych do rejestracji dwiku oraz rejestracji wideo pokazalimy, e w przypadku klasy MediaRecorder moemy
skonfigurowa rne opcje dotyczce rda rejestrowanych danych. Do dyspozycji mamy takie opcje, jak wejciowe rdo danych audio lub (i) wideo, czstotliwo wywietlania klatek,
rozmiar ramki obrazu, formaty plikw wynikowych i tak dalej. Klasa MediaStore nie posiada
takich moliwoci konfiguracyjnych, jeli jednak nie s nam potrzebne, by moe atwiej bdzie
stosowa intencje klasy MediaStore. Co waniejsze, dane utworzone za pomoc klasy Media

Rozdzia 19 Uywanie szkieletu multimedialnego

641

nie s automatycznie dostpne dla innych aplikacji korzystajcych z magazynu


multimediw. Jeeli korzystamy z klasy MediaRecorder, moemy doda nagranie do magazynu
multimediw za pomoc interfejsw API klasy MediaStore, wic moe prostszym rozwizaniem byoby zastosowanie przede wszystkim klasy MediaStore. Kolejna wana rnica polega na braku koniecznoci przyznawania aplikacji uprawnie do rejestracji dwiku, uzyskania dostpu do obiektu Camera oraz zapisu danych na kart SD w przypadku wywoania klasy
MediaStore poprzez intencj. Jest tak, poniewa aplikacja wywouje oddzieln aktywno, ktra
musi posiada uprawnienia do rejestrowania dwiku, dostpu do klasy Camera i zapisu na karcie SD. Aktywnoci klasy MediaStore maj wbudowane te uprawnienia, zatem aplikacja nie
musi ich posiada. Zobaczmy teraz, w jaki sposb moemy wykorzysta interfejsy API klasy
MediaStore.
Recorder

Rejestrowanie dwiku za pomoc intencji


Pokazalimy, e kod procesu rejestracji dwiku jest prosty, lecz w przypadku wywoania intencji
z klasy MediaStore staje si jeszcze prostszy. Listing 19.17 stanowi demonstracj zastosowania
intencji do rejestracji dwiku.
Listing 19.17. Wykorzystanie intencji do nagrania dwiku
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/recordBtn"
android:text="Rejestruj dwik"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

import
import
import
import
import
import
import
import

android.app.Activity;
android.content.Intent;
android.net.Uri;
android.os.Bundle;
android.util.Log;
android.view.View;
android.view.View.OnClickListener;
android.widget.Button;

public class UsingMediaStoreActivity extends Activity {


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btn = (Button)findViewById(R.id.recordBtn);
btn.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View view) {

642 Android 3. Tworzenie aplikacji

startRecording();
}});
}
public void startRecording() {
Intent intt = new Intent("android.provider.MediaStore.RECORD_SOUND");
startActivityForResult(intt, 0);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 0:
if (resultCode == RESULT_OK) {
Uri recordedAudioPath = data.getData();
Log.v("Demo", "Identyfikator Uri wynosi " + recordedAudioPath.toString());
}
}
}
}

Kod z listingu 19.17 tworzy intencj dajc od systemu przeprowadzenia procesu rejestracji dwiku. Intencja zostaje uruchomiona wobec aktywnoci poprzez wywoanie metody
startActivityForResult() oraz przekazanie tej intencji wraz z obiektem requestCode. Kiedy
dana aktywno wykona swoj prac, zostaje wywoana metoda onActivityResult() wraz
z obiektem requestCode. Jak wynika z kodu metody onActivityResult(), naley wyszuka
obiekt requestCode odpowiadajcy obiektowi przekazanemu klasie startActivityForResult(),
a nastpnie uzyska identyfikator URI zapisanego pliku poprzez wywoanie metody data.
getData(). Moemy nastpnie przekaza otrzymany identyfikator URI do intencji, aby odtworzy nagranie. Utworzony na listingu 19.17 interfejs UI zosta zilustrowany na rysunku 19.7.

Rysunek 19.7. Wbudowany rejestrator dwiku przed nagraniem (po lewej) i po nagraniu (po prawej)

Rozdzia 19 Uywanie szkieletu multimedialnego

643

Na rysunku 19.7 zostay zaprezentowane dwa zrzuty ekranu. Obraz z lewej strony przedstawia
rejestrator dwiku w trakcie procesu nagrywania, a na zrzucie ekranu z prawej strony widoczny
jest interfejs UI aktywnoci po zatrzymaniu procesu rejestracji.
W podobny sposb klasa MediaStore dostarcza intencj umoliwiajc wykonanie zdjcia.
Przykadem jest kod z listingu 19.18.
Listing 19.18. Uruchamianie intencji odpowiedzialnej za wykonywanie zdj
<?xml version="1.0" encoding="utf-8"?>

<!--Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/btn"
android:text="Zrb zdjcie"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="captureImage" />
</LinearLayout>

import
import
import
import
import
import
import
import
import
import

android.app.Activity;
android.content.ContentValues;
android.content.Intent;
android.net.Uri;
android.os.Bundle;
android.provider.MediaStore;
android.provider.MediaStore.Images.Media;
android.view.View;
android.view.View.OnClickListener;
android.widget.Button;

public class MainActivity extends Activity {


Uri myPicture = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
public void captureImage(View view)
{
ContentValues values = new ContentValues();
values.put(Media.TITLE, "Moje prbne zdjcie");
values.put(Media.DESCRIPTION, "Zdjcie wykonane aparatem
fotograficznym za pomoc intencji");

644 Android 3. Tworzenie aplikacji


myPicture = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
i.putExtra(MediaStore.EXTRA_OUTPUT, myPicture);
startActivityForResult(i, 0);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode==0 && resultCode==Activity.RESULT_OK)
{

// Teraz wiemy, e identyfikator URI myPicture odnosi si do wykonanego przed


// chwil zdjcia.
}
}
}

Klasa aktywnoci pokazana na listingu 19.18 definiuje metod captureImage(). Dziki niej
system tworzy intencj, ktrej dziaanie nosi nazw MediaStore.ACTION_IMAGE_CAPTURE.
Po uruchomieniu tej intencji na pierwszym planie ekranu wywietla si okno aplikacji aparatu fotograficznego i uytkownik moe wykona zdjcie. Poniewa wczeniej utworzylimy
identyfikator URI, moemy umieci dodatkowe informacje na temat zdjcia, zanim zostanie
zrobione. Do tego celu suy nam klasa ContentValues. Poza atrybutami TITLE i DESCRIPTION
mona doda do obiektu values rwnie inne parametry. Pen list atrybutw mona znale,
przegldajc klas MediaStore.Images.ImageColumns. Po wykonaniu zdjcia zostaje wywoana metoda zwrotna onActivityResult(). W naszym przykadzie uylimy dostawcy treci
multimediw do utworzenia nowego pliku. Moglibymy rwnie utworzy nowy identyfikator
URI z nowego pliku na karcie SD, tak jak poniej:
myPicture = Uri.fromFile(new
File(Environment.getExternalStoragePublicDirectory(DIRECTORY_DCIM) +
"/100ANDRO/imageCaptureIntent.jpg"));

Jednak utworzenie identyfikatora URI w ten sposb utrudnia zaimplementowanie atrybutw


zdjcia, na przykad TITLE i DESCRIPTION. Istnieje jeszcze inny sposb wywoania intencji aparatu w celu wykonania zdjcia. Jeeli w ogle nie przekaemy adnego identyfikatora URI wraz
z intencj, otrzymamy obiekt mapy bitowej, zwrcony w argumencie intencji dla metody
onActivityResult(). W tym przypadku problemem jest domylne zmniejszenie rozmiaru
otrzymanej mapy bitowej, prawdopodobnie dlatego, e twrcy systemu Android nie zakadaj
przesyania duych iloci danych z aktywnoci aparatu do aktywnoci naszej aplikacji. Mapa
bitowa bdzie miaa rozmiar 50 KB. eby uzyska obiekt typu Bitmap, wprowadzamy nastpujcy wiersz do metody onActivityResult():
Bitmap myBitmap = (Bitmap) data.getExtras().get("data");

W podobny sposb zachowuje si intencja klasy MediaStore zapewniajca obsug rejestracji


wideo. W tym celu stosowany jest obiekt MediaStore.ACTION_VIDEO_CAPTURE.

Dodawanie plikw do magazynu multimediw


Kolejn funkcj dostpn w szkielecie multimediw Androida jest moliwo dodawania informacji o plikach multimedialnych do magazynu za pomoc klasy MediaScannerConnection.

Rozdzia 19 Uywanie szkieletu multimedialnego

645

Innymi sowy, jeli w magazynie multimediw nie znalazy si informacje o nowych plikach,
dane tego typu mona dodawa za pomoc klasy MediaScannerConnection. Informacje te mog
by pniej wykorzystywane przez inne aplikacje. Zobaczmy, jak to dziaa (listing 19.19).
Listing 19.19. Dodawanie pliku do magazynu MediaStore
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText android:id="@+id/fileName"
android:hint="Wprowad nazw nowego pliku"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<Button android:id="@+id/scanBtn"
android:text="Dodaj plik"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="startScan" />
</LinearLayout>

import
import
import
import
import
import
import
import
import
import
import

java.io.File;
android.app.Activity;
android.content.Intent;
android.media.MediaScannerConnection;
android.media.MediaScannerConnection.MediaScannerConnectionClient;
android.net.Uri;
android.os.Bundle;
android.util.Log;
android.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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
editText = (EditText)findViewById(R.id.fileName);
}
public void startScan(View view)

646 Android 3. Tworzenie aplikacji


{
if(conn!=null)
{
conn.disconnect();
}
filename = editText.getText().toString();
File fileCheck = new File(filename);
if(fileCheck.isFile()) {
conn = new MediaScannerConnection(this, this);
conn.connect();
}
else {
Toast.makeText(this,
"Taki plik nie istnieje",
Toast.LENGTH_SHORT).show();
}
}
@Override
public void onMediaScannerConnected() {
conn.scanFile(filename, null);
}
@Override
public void onScanCompleted(String path, Uri uri) {
try {
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
startActivity(intent);
}
else {
Log.e("MediaScannerDemo", "Plik tego typu nie jest obsugiwany");
}
} finally {
conn.disconnect();
conn = null;
}
}
}

Na listingu 19.19 zostaa ukazana klasa aktywnoci umoliwiajca dodawanie pliku do magazynu
MediaStore. Jeli proces dodawania przebiegnie pomylnie, informacja o danym pliku zostanie
wywietlona uytkownikowi poprzez intencj. Poza wzrokiem uytkownika klasa MediaStore
sprawdza typ pliku i inne dotyczce go informacje. W przypadku klasy MediaStore moemy
jako drugi argument metody scanFile() poda typ MIME. Jeeli klasa MediaStore nie potrafi
rozpozna typu pliku po rozszerzeniu jego nazwy, nie zostanie on dodany do magazynu. Jeeli
natomiast plik jest akceptowany przez klas MediaStore, zostaje umieszczony wpis wewntrz
bazy danych dostawcy multimediw. Sam plik nie zostaje przeniesiony. Teraz jednak dostawca
multimediw ma dostp do wszystkich wanych informacji na temat tego pliku. Jeeli dodalimy plik obrazu, moemy uruchomi aplikacj Gallery i go obejrze. W przypadku pliku muzycznego zostanie on odtworzony w aplikacji Music.

Rozdzia 19 Uywanie szkieletu multimedialnego

647

Jeeli chcemy przejrze zawarto bazy danych dostawcy multimediw, otwieramy okno narzdzi, nastpnie uruchamiamy aplikacj adb shell i otwieramy plik /data/data/com.android.
providers.media/databases znajdujcy si na urzdzeniu. Powinny si tu rwnie znajdowa
zewntrzne pliki bazodanowe, kady reprezentujcy kart SD. Poniewa w telefonie obsugujcym system Android mona umieci kilka kart SD, w katalogu tym moe si znajdowa wiele
plikw odpowiadajcych tym kartom. W celu przegldania tabel bazodanowych umieszczonych w tych plikach moemy posuy si aplikacj sqlite3. Istniej oddzielne tabele dla plikw
audio, wideo i obrazw. W rozdziale 4. mona znale dodatkowe informacje na temat korzystania z aplikacji sqlite3.

Podczenie klasy MediaScanner do caej karty SD


W poprzednim przykadowym projekcie wykorzystalimy klas MediaScanner do wyszukania
pojedynczego, okrelonego pliku. Jest to wystarczajce, w przypadku gdy chcemy doda jeden
plik. Co jednak naley zrobi, aby zmieni nazw pliku albo go usun i zaktualizowa klas
MediaStore? Istnieje bardzo proste rozwizanie tego problemu. Jeeli wprowadzimy nastpujcy
fragment kodu do naszej aplikacji, zostanie on rozpoznany przez klas MediaScanner, ktra
przeszuka ca kart SD:
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" +
Environment.getExternalStorageDirectory())));

W ramach wiczenia Czytelnik moe napisa prost aplikacj wykonujc wycznie powysze
polecenie w metodzie onCreate().
Na tym zakoczymy omawianie interfejsw API multimediw. Mamy nadziej, e dla Czytelnika
odtwarzanie i rejestrowanie multimediw nie bdzie skomplikowanym procesem.

Odnoniki
Poniej prezentujemy cza do zasobw dotyczcych zagadnie, ktre Czytelnik moe zechcie
dokadniej zgbi:
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu peen zestaw projektw
bezporednio zwizanych z niniejsz ksik. Projekty dotyczce tego rozdziau
zostay umieszczone w katalogu ProAndroid3_R19_Multimedia. Doczylimy tu take
plik Czytaj.TXT, w ktrym zosta dokadnie opisany sposb importowania tych
projektw do rodowiska Eclipse.
http://developer.android.com/guide/topics/media/jet/jetcreator_manual.html
instrukcja obsugi narzdzia JETCreator. Za jego pomoc moemy utworzy plik
dwikowy JET, ktry nastpnie bdzie odtwarzany poprzez klas JetPlayer.
Narzdzie to jest dostpne wycznie w systemach Windows i Mac OS. Aby sprawdzi
dziaanie klasy JetPlayer, naley wczyta, skompilowa i wczy przykadowy
projekt JetBoy, dostpny w zestawie SDK. Warto wspomnie, e przycisk Fire znajduje
si na rodku podkadki kierunkowej.

648 Android 3. Tworzenie aplikacji

Podsumowanie
W tym rozdziale zajmowalimy si struktur multimediw w Androidzie. Pokazalimy, w jaki
sposb mona odtwarza pliki audio i wideo. Omwilimy take sposoby rejestrowania dwikw
i obrazw wideo zarwno bezporednio, jak i za pomoc intencji.
W nastpnym rozdziale zwrcimy uwag na grafik trjwymiarow poprzez omwienie zastosowania technologii OpenGL w aplikacjach tworzonych dla systemu Android.

R OZDZIA

20
Programowanie grafiki
trjwymiarowej
za pomoc biblioteki OpenGL

W niniejszym rozdziale przyjrzymy si dokadnie sposobom pracy z interfejsem


API biblioteki OpenGL ES w systemie Android. Biblioteka OpenGL ES jest odmian
specyfikacji OpenGL, zoptymalizowan pod ktem systemw wbudowanych
oraz innych urzdze o maej mocy, na przykad telefonw komrkowych.
System Android obsuguje biblioteki OpenGL ES w wersji 1.0 oraz 2.0. Wersja 2.0
zostaa wprowadzona dopiero w interfejsie API poziomu 8., co odpowiada wersji
2.2 Androida. W czasie pisania tej ksiki wystpoway pewne problemy z powizaniami kodu Java ze rodowiskiem OpenGL ES 2.0. Radzimy przejrze uwagi
i zalecenia dotyczce tej wersji rodowiska, ktre zamiecilimy w dalszej czci
rozdziau. Gwnym problemem jest brak obsugi tego rodowiska w emulatorze.
Wersja 3.0 Androida zostaa jeszcze bardziej wzbogacona o moliwoci obsugi
rodowiska OpenGL ES 2.0 poprzez wprowadzenie jzyka Renderscript. Jest to jzyk stworzony z myl o poprawie wydajnoci, w ktrym natywny kod przypomina
nieco jzyki z rodziny C. Taki kod moe by nawet wykonywany przez jednostk
GPU (koprocesor graficzny, z ang. Graphical Processing Unit). Jzyk Renderscript
wykazuje rwnie kompatybilno midzyplatformow. Jeeli wydajno nie ma
krytycznego znaczenia, zalecane jest uywanie powiza Java podczas wikszoci
pracy wykonywanej przez rodowisko OpenGL. Z powodu rnorodnych ogranicze nie omwilimy w tej ksice jzyka Renderscript; na kocu rozdziau zamiecilimy jednak odniesienie do podrcznika programowania w tym jzyku (wydanego przez firm Google).
rodowisko Android SDK zostao wyposaone w wiele przykadowych plikw,
pokazujcych moliwoci biblioteki OpenGL ES, jednak w zestawie SDK niemal
nie istnieje dokumentacja, ktra mogaby posuy jako podrcznik do pracy z bibliotek OpenGL ES. Wynika to z zaoenia, e biblioteka OpenGL ES jest otwartym standardem, ktrego programici mog si uczy z zewntrznych rde.
Wskutek tego w dostpnych rdach internetowych lub przykadowych kodach
traktujcych o korzystaniu z biblioteki OpenGL ES w Androidzie przyjmuje
si zaoenie, e programici s ju zaznajomieni z architektur OpenGL.

650 Android 3. Tworzenie aplikacji


W tym rozdziale pomoemy omin t przeszkod. Po spenieniu kilku warunkw wstpnych
ju pod koniec lektury tego rozdziau programowanie za pomoc biblioteki OpenGL ES stanie
si przyjemnoci. Dokonamy tego niemal bez udziau aparatu matematycznego (przeciwnie
ni w wielu innych ksikach powiconych bibliotece OpenGL).
W pierwszym podrozdziale dokonamy przegldu bibliotek OpenGL, OpenGL ES oraz niektrych konkurencyjnych standardw.
W drugim podrozdziale zajmiemy si czci teoretyczn, dotyczc technologii OpenGL. Jest
to podstawowa sekcja dla osb dopiero rozpoczynajcych przygod z t bibliotek. Omwimy
w niej wsprzdne OpenGL, pojcie kamery oraz podstawy interfejsw API rysowania.
Trzeci podrozdzia jest powicony pracy z interfejsem API OpenGL w Androidzie. Opisujemy
tutaj interfejsy GLSurfaceView oraz Renderer, a take sposb, w jaki wsppracuj ze sob
podczas procesu rysowania. W jednym z prostych przykadw narysujemy trjkt oraz pokaemy, jak na proces rysowania wpywa zmiana interfejsw API konfiguracji sceny.
Pojcie kamery w bibliotece OpenGL jest podobne, lecz nie identyczne z pojciem klasy
Camera z pakietu graficznego Androida, o ktrym bya mowa w rozdziale 6. Podczas
gdy klasa Camera symuluje wywietlanie perspektywy trjwymiarowej poprzez rzutowanie
dwuwymiarowego widoku poruszajcego si w trjwymiarowej przestrzeni, kamera
biblioteki OpenGL jest paradygmatem reprezentujcym wirtualny punkt widzenia.
Innymi sowy, ukazuje ona rzeczywist sceneri, widzian oczami obserwatora patrzcego
przez obiektyw kamery. Wicej informacji znajduje si w punkcie Kamera i wsprzdne,
w podrozdziale Podstawy struktury OpenGL. Obydwa obiekty kamery nie s zwizane
z fizyczn kamer urzdzenia podrcznego, za pomoc ktrej wykonujemy zdjcia
i rejestrujemy filmy.

W czwartym podrozdziale zajmiemy si nieco bardziej zaawansowanymi kwestiami biblioteki


OpenGL i wprowadzimy pojcie ksztatw. Opiszemy rwnie tekstury oraz zaprezentujemy
sposb rysowania wielu figur geometrycznych za pomoc jednej metody draw. Przyjrzymy si
nastpnie obsudze rodowiska OpenGL ES 2.0, dokadniej za jednostkom cieniujcym, ktrym powicimy krtki przykadowy projekt. Z gry przestrzegamy, e funkcje rodowiska
OpenGL 2.0 mog by testowane wycznie na urzdzeniu fizycznym.
Nastpnie rozdzia zamkniemy list rde, z ktrych korzystalimy w trakcie opracowywania
materiau do tego rozdziau.
Zatem przyjrzyjmy si historii i podstawom biblioteki OpenGL.

Historia i podstawy biblioteki OpenGL


Biblioteka OpenGL (jej pierwotna nazwa to Open Graphics Library, czyli otwarta biblioteka
graficzna) jest dwu- oraz trjwymiarowym interfejsem graficznym, zaprojektowanym przez
firm Silicon Graphics, Inc. (SGI) dla produkowanych przez ni stacji roboczych, pracujcych
pod kontrol systemu UNIX. Chocia stworzone przez firm SGI wersje biblioteki OpenGL
istniej ju od dugiego czasu, pierwsza ustandaryzowana specyfikacja tej technologii pojawia si
dopiero w 1992 roku. Obecnie standard OpenGL zosta przystosowany do wszystkich systemw operacyjnych i jest szeroko wykorzystywany w procesie pisania gier, projektowania CAD
(ang. Computer Aided Design projektowanie wspomagane komputerowo), a nawet tworzenia wirtualnych rzeczywistoci.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

651

Standard OpenGL jest obecnie zarzdzany przez konsorcjum nazwane grup Khronos
(www.khronos.org), zaoone w 2000 roku przez takie firmy, jak NVIDIA, Sun Microsystems,
ATI Technologies oraz SGI. Informacje dotyczce specyfikacji technologii OpenGL mona
znale na stronie konsorcjum:
www.khronos.org/opengl/
Pod poniszym adresem jest dostpna oficjalna strona dokumentacji biblioteki OpenGL:
www.opengl.org/documentation/
Po otwarciu powyszej strony uzyskujemy dostp do podrcznikw oraz zasobw internetowych dotyczcych biblioteki OpenGL. Spord nich klasyczn pozycj jest ksika OpenGL
Programming Guide: The Official Guide to Learning OpenGL, Version 1.1, znana take jako
czerwona ksiga technologii OpenGL. Jest ona dostpna pod adresem:
www.glprogramming.com/red/
Podrcznik ten jest cakiem dobrze i przystpnie napisany. Mielimy jednak pewne problemy
ze zrozumieniem natury jednostek i wsprzdnych, niezbdnych podczas rysowania. Sprbujemy wyjani te wane pojcia na podstawie sporzdzanych przez nas i widzianych na
ekranie obiektw za pomoc standardu OpenGL. Pojcia te dotycz konfigurowania kamery
OpenGL i definiowania bryy widzenia (ang. viewing box), zwanej take pojemnoci widzenia
(ang. viewing volume) lub ostrosupem widzenia (ang. frustum).

OpenGL ES
Grupa Khronos jest rwnie odpowiedzialna za dwa dodatkowe standardy powizane z technologi OpenGL: interfejs OpenGL ES oraz interfejs graficzny platformy natywnej EGL (zwany
w skrcie interfejsem EGL). Jak ju wspomnielimy, interfejs OpenGL ES jest mniejsz wersj
standardu OpenGL, przeznaczon dla systemw wbudowanych.
Proces JCP (ang. Java Community Process) rwnie umoliwia zaprojektowanie abstrakcji
obiektowej standardu OpenGL dla urzdze mobilnych. Abstrakcja ta nosi nazw interfejsu
M3G (ang. Mobile 3D Graphics mobilna grafika trjwymiarowa). Omwimy krtko
ten interfejs w punkcie M3G inny standard grafiki trjwymiarowej rodowiska Java.

Zasadniczo standard EGL jest interfejsem czcym system operacyjny z interfejsami renderujcymi, dostpnymi w rodowisku OpenGL ES. Poniewa standardy OpenGL i OpenGL ES
s oglnymi interfejsami sucymi do rysowania, kady system operacyjny musi im zapewni
standardowe rodowisko bazowe umoliwiajce wspdziaanie. Od wersji 1.5 rodowiska Android SDK informacje dotyczce tych parametrw platformy s cakiem skutecznie ukrywane.
Zajmiemy si tym dokadniej w podrozdziale Tworzenie interfejsu pomidzy standardem
OpenGL ES a Androidem.
Docelowymi urzdzeniami standardu OpenGL ES s telefony komrkowe, sprzt RTV, a nawet
pojazdy. Poniewa standard ten musia zosta mocno okrojony w porwnaniu do podstawowej
wersji biblioteki OpenGL, usunito wiele przydatnych funkcji. Na przykad nie ma funkcji bezporedniego rysowania prostoktw; naley w tym celu narysowa dwa trjkty.
Podczas nauki obsugi biblioteki OpenGL w Androidzie naley koncentrowa si przede wszystkim na interfejsie OpenGL ES i jego powizaniach z systemem poprzez jzyki Java oraz EGL.
Dokumentacj dla interfejsu OpenGL ES mona znale tutaj:
www.khronos.org/opengles/documentation/opengles1_0/html/index.html

652 Android 3. Tworzenie aplikacji


Podczas pisania tego rozdziau bez przerwy powracalimy do tego rda, 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 omwimy najwaniejsze z nich.

rodowisko OpenGL ES a Java ME


Podobnie jak biblioteka OpenGL, tak i rodowisko OpenGL ES jest paskim interfejsem opartym na jzyku C. Poniewa zestaw Android SDK jest interfejsem programowania bazujcym na
jzyku Java, wymagane jest powizanie jzyka Java z interfejsem OpenGL ES. W przypadku
rodowiska Java ME takie powizanie zostao ju zdefiniowane w specyfikacji JSR 239: Java
Binding for the OpenGL ES API. Sama specyfikacja JSR 239 opiera si na specyfikacji JSR 231,
stanowicej powizanie jzyka Java z bibliotek OpenGL 1.5. Specyfikacja JSR 239 mogaby zosta
podzbiorem specyfikacji JSR 231, nie wchodzi to jednak w gr, poniewa musi ona zaadaptowa
pewne rozszerzenia interfejsu OpenGL ES, ktrych nie ma w bibliotece OpenGL 1.5.
Dokumentacja specyfikacji JSR 239 jest dostpna tutaj:
http://java.sun.com/javame/reference/apis/jsr239/
Informacje zawarte pod powyszym adresem dadz Czytelnikowi pojcie na temat interfejsw
API dostpnych w bibliotece OpenGL ES. Mona tam rwnie znale cenne informacje dotyczce
nastpujcych pakietw:
javax.microedition.khronos.egl,
javax.microedition.khronos.opengles,
java.nio.
Pakiet nio jest niezbdny, poniewa implementacje rodowiska OpenGL ES przyjmuj dane
wejciowe jedynie w postaci strumieni bajtw, co pozwala zachowa wysok wydajno. W pakiecie tym zdefiniowano wiele narzdzi sucych do przygotowania buforw natywnych pod
ktem korzystania z nich przez bibliotek OpenGL. Niektre z tych interfejsw API zostan zastosowane w podpunkcie glVertexPointer i okrelanie wierzchokw rysowania podrozdziau
Podstawy struktury OpenGL.
Pod poniszym adresem mona znale dokumentacj (niestety, bardzo ubog) dotyczc obsugi biblioteki OpenGL w rodowisku Android:
http://developer.android.com/guide/topics/graphics/opengl.html
Pod tym adresem mona znale informacj wskazujc na fakt, e implementacja tej biblioteki
w Androidzie pokrywa si w wikszoci ze specyfikacj JSR 239, jednak w kilku miejscach
mog si pojawi odstpstwa.

M3G inny standard grafiki trjwymiarowej


rodowiska Java
Specyfikacja JSR 239 jest jedynie powizaniem jzyka Java z natywnym standardem OpenGL ES.
Wspomnielimy pobienie w punkcie OpenGL ES, e rodowisko Java posiada inny interfejs
API obsugujcy trjwymiarow grafik w urzdzeniach mobilnych M3G. Ten standard
obiektowy jest zdefiniowany w specyfikacjach JST 184 i nowszej wersji JSR 297. W przypadku
specyfikacji JSR 184 technologia M3G stanowi uniwersalny, obiektowy, interaktywny interfejs
grafiki trjwymiarowej dla urzdze mobilnych.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

653

Obiektowa natura interfejsu M3G izoluje go od rodowiska OpenGL ES. Ze szczegami mona
si zapozna na poniszej stronie specyfikacji JSR 184:
www.jcp.org/en/jsr/detail?id=184
Interfejsy API dla rodowiska M3G s dostpne w pakiecie Java noszcym nazw javax.
microedition.m3g.*.
W rodowisku M3G zaimplementowano interfejs API wyszego poziomu w stosunku do rodowiska OpenGL ES, wic jego nauka powinna przebiega atwiej. Jednak na razie trudno oceni, jak ta technologia bdzie si sprawowaa na handheldach. Na razie Android nie obsuguje
technologii M3G.
Dotychczas wymienilimy opcje dostpne w przestrzeni OpenGL pod ktem urzdze przenonych. Omwilimy rodowisko OpenGL ES i wspomnielimy o standardzie M3G. Przejdmy
teraz do zapoznania si z podstawami biblioteki OpenGL.

Podstawy struktury OpenGL


W tym ustpie pomoemy zrozumie pojcia kryjce si w interfejsach API OpenGL i OpenGL ES.
Omwimy wszystkie zasadnicze interfejsy API. W razie potrzeby na kocu rozdziau znajduje
si punkt Dodatkowe rda dotyczce rodowiska OpenGL ES 2.0, w ktrym zostaa zamieszczona lista zasobw zawierajcych dodatkowe informacje. Wrd tych rde znajduj
si odnoniki do czerwonej ksigi, dokumentacji specyfikacji JSR 239 oraz interfejsw API
grupy Khronos.
Podczas korzystania z zasobw dotyczcych OpenGL mona zauway, e w wersji
OpenGL ES brakuje czci interfejsw API. W takich momentach przydaje si podrcznik
referencyjny rodowiska OpenGL ES (ang. OpenGL ES Reference Manual) grupy Khronos.

Wymienione poniej interfejsy API, jako niezbdne do zrozumienia dziaania bibliotek OpenGL
i OpenGL ES, zostan szczegowo omwione:
glVertexPointer,
glDrawElements,
glColor,
glClear,
gluLookAt,
glFrustum,
glViewport.
Podczas omawiania tych interfejsw przedstawimy:
wykorzystywanie podstawowych interfejsw API rysowania za pomoc rodowiska
OpenGL ES,
czyszczenie palety,
okrelanie kolorw,
uywanie wsprzdnych i kamery biblioteki OpenGL.

654 Android 3. Tworzenie aplikacji

Podstawy rysowania za pomoc biblioteki OpenGL


W rodowisku OpenGL rysujemy w trjwymiarowej przestrzeni. Rozpoczynamy od okrelenia
szeregu punktw zwanych wierzchokami. Kady punkt posiada trzy wartoci: pierwsza odpowiada wsprzdnej x, druga wsprzdnej y, a trzecia wsprzdnej z.
Punkty te s ze sob czone w celu uzyskania ksztatu. Za ich pomoc mona uzyska w rodowisku OpenGL ES rne ksztaty, ktre s zwane prostymi ksztatami; zaliczamy do nich punkty,
linie i trjkty. Zwrmy uwag, e do prostych ksztatw zaliczane s rwnie prostokty
i wielokty. Po pewnym czasie pracy z bibliotekami OpenGL i OpenGL ES zauwaymy, e ta
druga udostpnia mniej funkcji ni pierwsza. Inny przykad: biblioteka OpenGL pozwala na oddzielne okrelanie kadego punktu, natomiast w bibliotece OpenGL ES moliwe jest jedynie
okrelanie zbioru punktw za jednym razem. Jednak czsto moemy symulowa brakujce opcje
poprzez korzystanie z innych, prostszych funkcji. Na przykad moemy utworzy prostokt
poprzez zoenie dwch trjktw.
Biblioteka OpenGL ES zawiera dwie podstawowe metody upraszczajce proces rysowania:
glVertexPointer,
glDrawElements.
W przypadku biblioteki OpenGL ES uywamy zamiennie poj interfejs API i metoda.

Metoda glVertexPointer suy do okrelania zbioru punktw lub wierzchokw, a dziki


interfejsowi glDrawElements s one rysowane za pomoc jednego z wspomnianych wczeniej prostych ksztatw. Opiszemy te metody dokadniej, ustalmy jednak najpierw nomenklatur nazewnictwa stosowan w przypadku biblioteki OpenGL.
Wszystkie nazwy interfejsw OpenGL rozpoczynaj si od przedrostka gl. Po nim wystpuje
nazwa metody. Po jej nazwie moe pojawi si cyfra, na przykad 3, wskazujca albo na liczb
wymiarw (x, y, z), albo na liczb argumentw. Kolejnym segmentem nazwy jest litera symbolizujca typ danych, na przykad litera f reprezentuje dane typu float (w internetowych
zasobach dotyczcych biblioteki OpenGL mona znale opis rnych typw danych i odpowiadajce im symbole literowe).
Istnieje jeszcze jedna konwencja. Jeeli metoda przyjmuje argumenty o typach danych byte (b)
lub float (f), to bdzie posiadaa dwie nazwy: jedna bdzie koczya si liter b, a druga
liter f.
Przyjrzyjmy si teraz poszczeglnym metodom sucym do rysowania, poczwszy od interfejsu glVertexPointer.

glVertexPointer i okrelanie wierzchokw rysowania


Metoda glVertexPointer suy do definiowania tablicy rysowanych punktw. Kady punkt
jest umiejscowiony w trzech wymiarach przestrzeni, zatem bdzie posiada trzy wartoci: x, y i z.
Na listingu 20.1 pokazano tablic ze zdefiniowanymi trzema punktami.
Listing 20.1. Przykadowe wsprzdne wierzchokw trjkta utworzonego dziki bibliotece OpenGL
float[] coords = {
-0.5f, -0.5f, 0,

//p1: (x1,y1,z1)

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

0.5f, -0.5f, 0,
0.0f, 0.5f, 0

655

//p2: (x1,y1,z1)
//p3: (x1,y1,z1)

};

Struktura przedstawiona na listingu 20.1 jest cigym zbiorem liczb zmiennoprzecinkowych,


przechowywanych w macierzy opartej na jzyku Java. Nie przejmujmy si na razie pisaniem lub
kompilowaniem tego kodu najpierw wyjanimy zasady dziaania tych metod. Po zaprojektowaniu rodowiska testowego sucego do rysowania prostych ksztatw pokaemy kilka
dziaajcych przykadowych projektw. Umiecilimy take na kocu rozdziau odnonik do
gotowego projektu.
Moemy si zastanawia, jakie jednostki zostay uyte na listingu 20.1 dla wsprzdnych
punktw p1, p2 i p3. Odpowied jest krtka: podczas modelowania przestrzeni trjwymiarowej te jednostki wsprzdnych mog by dowolne. Pniej jednak trzeba zdefiniowa obiekt
noszcy nazw bryy okalajcej (ang. bounding box) lub objtoci okalajcej (ang. bounding
volume), dziki ktremu te jednostki zostaj ilociowo wyraone.
Na przykad moemy okreli bry okalajc jako szecian o boku wynoszcym 5 lub 2 cale.
Te wsprzdne nosz rwnie nazw wsprzdnych wiata (ang. world coordinates), poniewa
okrelamy dziki nim wiat niezaleny od fizycznych ogranicze urzdzenia. Wsprzdne wiata
objanimy dokadniej w punkcie Kamera i wsprzdne. Na razie zamy, e korzystamy
z szecianu o dugoci boku 2 cale, ktrego rodkiem s wsprzdne (x=0, y=0, z=0). Inaczej
mwic, rodek jest w centrum szecianu, ktrego ciany znajduj si w odlegoci jednej jednostki.
Terminy objto okalajca, brya okalajca, objto widzenia, brya widzenia oraz ostrosup
widzenia dotycz tego samego pojcia: trjwymiarowej objtoci w ksztacie piramidy,
dziki ktrej okrelamy, co jest widoczne na ekranie. Wicej informacji na ten temat
mona znale w podpunkcie glFrustum i objto widzenia, w punkcie Kamera
i wsprzdne.

Moemy rwnie zaoy, e pocztek ukadu wsprzdnych znajduje si porodku wywietlanego obrazu. O z posiada wartoci ujemne w kierunku dalszego planu (oddala si od widza),
a dodatnie w stron pierwszego planu (zblia si w kierunku widza). Wartoci osi x s dodatnie
w kierunku prawym, a ujemne w kierunku lewym. Jednak te wsprzdne zale rwnie od
kierunku, z jakiego ogldamy scen.
Aby narysowa punkty widoczne na listingu 20.1, musimy przekaza je bibliotece OpenGL ES poprzez metod glVertexPointer. Jednak w celu zachowania wydajnoci metoda glVertexPointer
przyjmuje natywny bufor niezaleny od jzyka programowania, a nie macierz wartoci zmiennoprzecinkowych. W tym celu musimy przeksztaci macierz jzyka Java do akceptowanego
bufora natywnego, przypominajcego struktur jzyka C. Dokonujemy tego za pomoc klas
java.nio. Na listingu 20.2 zosta zaprezentowany przykad wykorzystania buforw nio.
Listing 20.2. Tworzenie zmiennoprzecinkowych buforw nio
jva.nio.ByteBuffer vbb = java.nio.ByteBuffer.allocateDirect(3 * 3 * 4);
vbb.order(ByteOrder.nativeOrder());
java.nio.FloatBuffer mFVertexBuffer = vbb.asFloatBuffer();

656 Android 3. Tworzenie aplikacji


Na listingu 20.2 obiekt ByteBuffer stanowi bufor pamici zdefiniowany w bajtach. Kady
punkt posiada trzy wartoci zmiennoprzecinkowe, co wynika z umiejscowienia go w trjwymiarowym ukadzie wsprzdnych, natomiast kada warto zmiennoprzecinkowa jest czterobajtowa. Zatem na kady punkt przypada 43 bajty. Ponadto naley pamita, e trjkt posiada trzy punkty. Potrzebujemy wic 334 bajty do przechowania informacji o wszystkich
trzech zmiennoprzecinkowych wierzchokach trjkta.
Po umieszczeniu punktw w buforze natywnym moemy wywoa metod glVertexPointer
zgodnie z kodem pokazanym na listingu 20.3.
Listing 20.3. Definicja interfejsu API glVertexPointer
glVertexPointer(

// Ilu wsprzdnych uywamy dla kadego punktu


3,

// Kada warto jest zmiennoprzecinkowa w buforze


GL10.GL_FLOAT,

// Nie ma przestrzeni pomidzy dwoma punktami


0,

// Wskanik pocztku bufora


mFVertexBuffer);

Zatrzymajmy si na chwil przy argumentach metody glVertexPointer. Pierwszy argument


wskazuje bibliotece OpenGL ES, ile wymiarw przypada na punkt lub wierzchoek. W naszym
przypadku podajemy warto 3 dla wsprzdnych x, y i z. Istnieje rwnie moliwo wpisania
wartoci 2 dla wymiarw x i y. Wtedy parametr z przyjmuje warto 0. Pamitajmy, e pierwszym argumentem nie jest liczba punktw w buforze, lecz liczba uwzgldnianych wymiarw.
Zatem jeeli chcemy przekaza 20 punktw do narysowania wikszej liczby trjktw, nie wpisujemy wartoci 20 w pierwszym argumencie; umieszczamy w nim warto 2 lub 3, w zalenoci
od liczby uywanych wymiarw.
Drugi argument okrela, e wsprzdne musz by interpretowane jako liczby zmiennoprzecinkowe. Trzeci argument, noszcy nazw stride, wskazuje, ile bajtw oddziela kady punkt
od siebie. W naszym przypadku podajemy warto 0, poniewa jeden punkt zostaje umieszczony
tu przy drugim. Czasami moemy dodawa atrybuty kolorw jako cz bufora po kadym
punkcie. W tym celu uywamy atrybutu stride do omijania tych czci bufora, w ktrych
umieszczono punkty specyfikacj wierzchokw. Ostatnim argumentem jest wskanik bufora
zawierajcego punkty.
Skoro wiemy ju, w jaki sposb konfigurowa tablic rysowanych punktw, dowiedzmy si, co
naley zrobi, aby narysowa te punkty za pomoc metody glDrawElements.

glDrawElements
Po okreleniu zbioru punktw za pomoc metody glVertexPointer stosujemy metod
glDrawElements do narysowania tych punktw w postaci jednego z prostych ksztatw dopuszczalnych przez bibliotek OpenGL ES. Odnotujmy fakt, e biblioteka OpenGL jest maszyn
stanow. Zapamituje w sposb narastajcy wartoci ustanowione przez jedn metod podczas
wywoywania kolejnej metody. Nie musimy wic jawnie przekazywa punktw ustanowionych
przez metod glVertexPointer metodzie glDrawElements. Ta ostatnia bdzie z nich korzystaa
w sposb niejawny. Listing 20.4 ukazuje przykad zastosowania tej metody wraz z dopuszczalnymi argumentami.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

657

Listing 20.4. Przykad metody glDrawElements


glDrawElements(

// Rodzaj ksztatu
GL10.GL_TRIANGLE_STRIP,

// Liczba indeksw
3,

// Rozmiar kadego indeksu


GL10.GL_UNSIGNED_SHORT,

// Bufor zawierajcy trzy indeksy


mIndexBuffer);

Pierwszy argument definiuje typ rysowanego ksztatu geometrycznego: GL_TRIANGLE_STRIP


oznacza pas trjktw. Pozostaymi wartociami mog by same punkty (GL_POINTS), pasy
linii (GL_LINE_STRIP), same linie (GL_LINES), ptle linii (GL_LINE_LOOP), same trjkty
(GL_TRIANGLES) lub wachlarze trjktw (GL_TRIANGLE_FAN).
Koncepcja pasa (ang. strip) w argumentach GL_LINE_STRIP i GL_TRIANGLE_STRIP polega na
dodawaniu nowych punktw podczas korzystania z punktw ju istniejcych. W ten sposb
moemy unikn definiowania wszystkich punktw dla kadego nowego obiektu. Jeli na przykad okrelimy cztery punkty w macierzy, moemy wykorzysta pasy do utworzenia pierwszego
trjkta z wierzchokw (1, 2, 3), a drugiego z wierzchokw (2, 3, 4). Kady nowy punkt spowoduje dodanie kolejnego trjkta (szczegy mona znale w czerwonej ksidze biblioteki
OpenGL). Moemy rwnie zrnicowa te parametry, aby zobaczy, w jaki sposb bd rysowane trjkty po dodaniu nowych punktw.
Koncepcja wachlarza (ang. fan) w argumencie GL_TRIANGLE_FAN polega na wykorzystaniu
pierwszego punktu jako punktu zaczepienia dla wszystkich trjktw. Zatem tworzymy w istocie obiekt w ksztacie wachlarza lub koa, ktrego pierwszy wierzchoek znajduje si w rodku.
Zamy, e posiadamy sze punktw w macierzy: (1, 2, 3, 4, 5, 6). Uycie argumentu wachlarza spowoduje narysowanie trjktw o wierzchokach (1, 2, 3), (1, 3, 4), (1, 4, 5) i (1, 5, 6). Kady
nowy punkt tworzy dodatkowy trjkt, co mona przypomina rozwijanie wachlarza lub talii kart.
Pozostae argumenty metody glDrawElements su do okrelania moliwoci ponownego wykorzystania charakterystyki punktu. Na przykad kwadrat skada si z czterech punktw. Kady
kwadrat mona narysowa jako kombinacj dwch trjktw. Czy w celu narysowania dwch
trjktw tworzcych kwadrat musimy wyznaczy sze punktw? Nie. Wystarczy okrelenie
jedynie czterech punktw i szeciokrotne odniesienie si do nich w celu narysowania dwch
trjktw. Proces ten nosi nazw indeksowania do bufora punktu.
Przykad:
Punkty: (p1, p2, p3, p4)
Rysuj indeksy (p1, p2, p3,

p2, p3, p4)

Zauwamy, e pierwszy trjkt skada si z punktw p1, p2, p3, a drugi z punktw p2, p3, p4.
Dziki tej wiedzy moemy poprzez drugi argument metody glDrawElements okreli liczb indeksw w buforze indeksw.
Trzeci argument metody glDrawElements (listing 20.4) wskazuje typ wartoci w macierzy
indeksw czy jest to typ unsigned short (GL_UNSIGNED_SHORT), czy unsigned byte
(GL_UNSIGNED_BYTE).

658 Android 3. Tworzenie aplikacji


Ostatni argument metody glDrawElements wskazuje bufor indeksu. Aby go wypeni, musimy
przeprowadzi podobn operacj jak w przypadku bufora wierzchoka. Rozpoczynamy od tablicy Java i konwertujemy j za pomoc pakietu java.nio do bufora natywnego.
Na listingu 20.5 zosta przedstawiony przykadowy kod sucy do konwersji krtkiej tablicy
zawierajcej elementy {0, 1, 2} do bufora natywnego, nadajcego si do przekazania go metodzie glDrawElements.
Listing 20.5. Konwertowanie tablicy Java na posta bufora nio
//Okrelamy sposb rozmieszczenia punktw
short[] myIndecesArray = {0,1,2};

//Uzyskujemy bufor typu short


java.nio.ShortBuffer mIndexBuffer;

//Wyznaczamy po 2 bajty dla kadej wartoci indeksu


ByteBuffer ibb = ByteBuffer.allocateDirect(3 * 2);
ibb.order(ByteOrder.nativeOrder());
mIndexBuffer = ibb.asShortBuffer();

//Umieszczamy go w buforze
for (int i=0;i<3;i++)
{
mIndexBuffer.put(myIndecesArray[i]);
}

Skoro wiemy ju, jak dziaa metoda mIndexBuffer (listing 20.5), moemy cofn si do listingu
20.4, aby lepiej zrozumie ide tworzenia bufora indeksu i jego przeksztacania.
Bufor indeksu, zamiast tworzy nowe punkty, indeksuje jedynie macierz punktw
wskazan przez metod glVertexPointer. Jest to moliwe, poniewa biblioteka
OpenGL zapamituje za pomoc stanw zasoby ustanowione przez poprzednie wywoania.

Zajmijmy si teraz dwiema powszechnie wykorzystywanymi metodami biblioteki OpenGL:


glClear i glColor.

glClear
Metod glClear stosujemy do czyszczenia powierzchni rysowania. Za jej pomoc moemy wyzerowa kolor, gbi oraz rodzaj wykorzystywanych szablonw. Zerowany element okrelamy
poprzez odpowiedni sta: GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT lub GL_STENCIL_
BUFFER_BIT.
Bufor koloru jest odpowiedzialny za widoczne na ekranie piksele, zatem jego wyczyszczenie
spowoduje zniknicie wszelkich kolorw z powierzchni. Bufor gbi jest zwizany z pikselami
widzianymi w trjwymiarowej scenie, w zalenoci od odlegoci obiektu od kamery.
Bufor szablonowy jest nieco zbyt skomplikowany, aby go tutaj omwi, wystarczy jednak wiedzie, e jest uywany do tworzenia efektw graficznych na podstawie pewnych dynamicznych
kryteriw, a metoda glClear umoliwia jego wyczyszczenie.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

659

Szablon jest obiektem, dziki ktremu moemy wielokrotnie powiela proces rysowania.
Jeli na przykad uywamy aplikacji Microsoft Office Visio, wszystkie obiekty zapisywane
jako pliki *.vss s szablonami. W wiecie rzeczywistym tworzymy szablon poprzez
wycicie wzoru w papierze lub innej paskiej powierzchni. Nastpnie przerysowujemy
za pomoc szablonu jego obwiedni na arkuszu, a po zdjciu szablonu powstaje
wraenie powielenia rysunku. Widziany obraz zaley od aktywnych szablonw. Wyczyszczenie
wszystkich szablonw spowoduje, e wszystkie rysowane elementy bd widoczne.

Dla naszych celw moemy zastosowa poniszy kod do wyczyszczenia bufora koloru:
//Czyci powierzchni ze wszelkich kolorw
gl.glClear(gl.GL_COLOR_BUFFER_BIT);

Zastanwmy si teraz nad sposobem doczenia koloru do rysowanego obiektu.

glColor
Metoda glColor jest uywana do ustawienia domylnego koloru dla nastpnego rysowanego
obiektu. W poniszym segmencie kodu metoda glColor4f generuje kolor czerwony:
//Ustanawia biecy kolor
glColor4f(1.0f, 0, 0, 0.5f);

Przypomnijmy sobie nomenklatur metod: oznaczenie 4f odnosi si do czterech argumentw,


posiadajcych wartoci zmiennoprzecinkowe, pobieranych przez metod. Czterema argumentami
s skadowe barwy czerwonej, zielonej, niebieskiej oraz wspczynnika alfa (gradient koloru).
Wartoci pocztkow dla tych argumentw jest (1, 1, 1, 1). W naszym przykadzie wybralimy
kolor czerwony z poow gradientu (okrelonego przez ostatni argument alfa).
Chocia omwilimy podstawowe interfejsy API rysowania, musimy jeszcze przedstawi kilka
kwestii zwizanych ze wsprzdnymi punktw okrelanych w trjwymiarowej przestrzeni.
W nastpnym punkcie wyjanimy, w jaki sposb biblioteka OpenGL modeluje rzeczywiste
sceny w perspektywie widzenia operatora kamery.

Kamera i wsprzdne
W procesie rysowania w przestrzeni trjwymiarowej musimy w pewnym momencie rzutowa
trjwymiarowy widok na dwuwymiarowy ekran tak samo jak w wiecie rzeczywistym podczas rejestrowania trjwymiarowej sceny za pomoc kamery. Taka symbolika jest formalnie
uznana w standardzie OpenGL, zatem wiele koncepcji jest w nim wyjanianych za pomoc pojcia kamery.
Jak zobaczymy w tym punkcie, widoczna cz obiektu rysowanego zaley od pooenia kamery,
kierunku ustawienia jej obiektywu, orientacji kamery (moe by na przykad postawiona do gry
nogami lub przechylona), poziomu przyblienia oraz rozmiaru kliszy.
Wymienione aspekty rzutowania obrazu trjwymiarowego na dwuwymiarowy ekran s kontrolowane przez trzy metody:
gluLookAt kontroluje kierunek, w jakim jest ustawiony obiektyw kamery.
glFrustum kontroluje objto widzenia, poziom powikszenia lub odlego
(od bd do obiektu).
glViewport kontroluje rozmiar ekranu lub kliszy.

660 Android 3. Tworzenie aplikacji


Bez zrozumienia znaczenia tych trzech interfejsw API nie mona niczego zaprogramowa
w standardzie OpenGL. Rozwiniemy dalej symbolik dotyczc kamery, aby wyjani, w jaki sposb te trzy metody wpywaj na obraz widziany na ekranie. Rozpoczniemy od metody gluLookAt.

gluLookAt i symbolika kamery


Wyobramy sobie, e fotografujemy krajobraz, ktrego elementem s kwiaty, drzewa, strumienie i gry. Przybywamy na k; sceneria roztaczajca si przed naszymi oczami jest rwnowana temu, co chcemy narysowa w standardzie OpenGL. Moemy rysowa obiekty tak
due jak gry lub tak mae jak kwiaty dopki zachowamy proporcje pomidzy nimi. Jak ju
wczeniej wspomnielimy, wsprzdne stosowane w przypadku tych obiektw s nazywane
wsprzdnymi wiata. Za ich pomoc ustanawiamy na osi x lini o dugoci 4 jednostek poprzez wprowadzenie punktw od (1, 0, 0) do (4, 0, 0).
Podczas przygotowa do wykonania zdjcia znajdujemy miejsce, w ktrym umiecimy statyw.
Nastpnie na statywie montujemy aparat. Pooenie aparatu nie statywu, a samego urzdzenia jest punktem pocztkowym. Musimy wic wzi kartk i zaznaczy t lokalizacj, noszc nazw punktu ocznego (ang. eye point).
Jeeli nie zdefiniujemy punktu ocznego, kamera bdzie si znajdowa w punkcie o wsprzdnych (0, 0, 0), ktry jest umieszczony dokadnie na rodku ekranu. Chcemy przewanie odsun
si od pocztku ukadu wsprzdnych, aby zobaczy paszczyzn (x, y), dla ktrej warto osi
z wynosi 0. Zamy, e umiecimy aparat w punkcie (0, 0, 5). Zostanie on przesunity w nasz
stron o 5 jednostek.
Na rysunku 20.1 zostaa ukazana pozycja aparatu.

Rysunek 20.1. Analogia kamery w koncepcji widzenia w standardzie OpenGL

Przygldajc si rysunkowi 20.1, moemy si zastanawia, dlaczego mamy do czynienia z osiami x i z, a nie x i y. Stosujemy tu standardow konwencj biblioteki OpenGL, zgodnie z ktr
kamera jest skierowana na o z, gdy paszczyzn scenerii tworz osie x i y. Taka konwencja
sprawdza si, poniewa zazwyczaj o z zostaje powizana z osi gbi.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

661

Po umieszczeniu aparatu na statywie musimy sprawdzi, jaki fragment sceny chcemy uchwyci
w obiektywie. Skierujmy obiektyw urzdzenia w stron, w ktr patrzymy. Ten odlegy punkt,
na ktry spogldamy, nazywany jest punktem widoku (ang. viewing point) lub punktem spogldania (ang. look-at point). Okrelajc ten punkt, definiujemy w rzeczywistoci kierunek patrzenia. Jeli nasz punkt widoku bdzie posiada wsprzdne (0, 0, 0), to aparat bdzie spoglda z odlegoci 5 przy zaoeniu, e wsprzdne aparatu wynosz (0, 0, 5) jednostek
wzdu osi z na pocztek ukadu wsprzdnych. Zostao to ukazane na rysunku 20.1.
Wyobramy sobie dalej, e w punkcie pocztkowym ukadu wsprzdnych zosta umieszczony
prostopadocienny budynek. Chcemy zrobi mu zdjcie nie w pozycji wertykalnej, a w horyzontalnej. Co robimy? Oczywicie nie zmieniamy pooenia ani kierunku spogldania aparatu,
musimy go jednak obrci o 90 stopni (analogicznie do przechylania gowy na boki). Jest to
orientacja aparatu spogldajcego na okrelony punkt widoku. Orientacja taka nosi nazw
wektora gry (ang. up vector).
Wektor gry w prosty sposb okrela orientacj aparatu (gra, d, lewo, prawo lub pod ktem).
Orientacja aparatu jest rwnie okrelana za pomoc punktu. Wyobramy sobie lini odchodzc od rodka ukadu wsprzdnych nie od rodka aparatu, a od rodka ukadu wsprzdnych wiata do tego punktu. Kt utworzony pomidzy osiami a t lini wyznacza wanie orientacj aparatu.
Na przykad wektor gry dla aparatu moe przybra warto (0, 1, 0), a nawet (0, 15, 0), co da
ten sam efekt. Punkt (0, 1, 0) wskazuje punkt odchodzcy od pocztku ukadu wsprzdnych
po osi y w gr. Oznacza to, e ustawimy aparat w pozycji pionowej. W przypadku wektora
(0, 1, 0) obrcimy urzdzenie do gry nogami. W obydwu przypadkach aparat umieszczony
jest cigle w tym samym punkcie (0, 0, 5) i spoglda na rodek ukadu wsprzdnych (0, 0, 0).
Wymienione wsprzdne moemy podsumowa w nastpujcy sposb:
(0, 0, 5) punkt oczny (pooenie kamery).
(0, 0, 0) punkt spogldania (kierunek, w ktrym kamera jest zwrcona).
(0, 1, 0) wektor gry (orientacja pozioma, pionowa lub nachylona).
Wszystkie trzy punkty punkt oczny, punkt spogldania oraz wektor gry mog zosta
zdefiniowane w metodzie gluLookAt w nastpujcy sposb:
gluLookAt(gl, 0,0,5,

0,0,0,

0,1,0);

Argumenty wystpuj w kolejnoci: pierwszy zbir wsprzdnych odpowiada punktowi ocznemu,


drugi zestaw wsprzdnych naley do punktu spogldania, natomiast pozostae trzy wsprzdne okrelaj wektor gry w odniesieniu do pocztku ukadu wsprzdnych.
Przyjrzyjmy si teraz objtoci widzenia.

glFrustum i objto widzenia


Mona zauway, e aden z punktw opisujcych pooenie aparatu za pomoc metody
gluLookAt nie okrela rozmiaru obrazu. Definiuj one jedynie lokalizacj, kierunek i orientacj.
W jaki sposb aparat ma ustawia ostro? Jak daleko znajduje si obiekt, ktry fotografujemy?
Do okrelenia interesujcego nas obszaru sceny wykorzystujemy metod glFrustum.
Gdybymy siedzieli w teatrze na przedstawieniu, scena stanowiaby nasz objto widzenia.
Nie musimy wiedzie, co si dzieje za kurtynami. Wane s jednak dla nas rozmiary sceny, poniewa chcemy widzie dokadnie wszystko, co si na niej dzieje.

662 Android 3. Tworzenie aplikacji


Wyobramy sobie obszar sceny otoczony przez bry, zwan take ostrosupem widzenia lub
objtoci widzenia (ostrosup widzenia jest zaznaczony pogrubion lini w rodkowej czci
rysunku 20.1). Wszystkie elementy umieszczone wewntrz bryy zostaj zarejestrowane, a obiekty
znajdujce si na zewntrz zostaj wycite i zignorowane. Zatem w jaki sposb okrelamy bry
widzenia? Najpierw wyznaczamy bliski punkt (ang. near point) lub odlego pomidzy aparatem a pocztkiem bryy. Nastpnie zaznaczamy daleki punkt (ang. far point), definiujcy dystans pomidzy aparatem a kocem bryy. Odlego pomidzy punktem bliskim a punktem
dalekim wzdu osi z stanowi gbi bryy. Jeeli zdefiniujemy punkt bliski o wartoci 50 i punkt
daleki o wartoci 200, uchwycimy wszystkie obiekty znajdujce si pomidzy tymi punktami,
a gbia bryy bdzie posiadaa warto 150. Bdziemy musieli okreli rwnie lew stron bryy,
jej praw stron, a take jej gr i d za pomoc wyimaginowanego promienia, czcego
aparat z punktem spogldania.
W bibliotece OpenGL istniej dwa sposoby odwzorowania tej wyimaginowanej bryy. Jeden
z nich nosi nazw rzutowania perspektywicznego i wykorzystuje omwione przed chwil
pojcie ostrosupa widzenia. Widok ten, symulujcy prac normalnej kamery, wykorzystuje
struktur piramidow, ktrej podstaw stanowi daleka paszczyzna, a kamera jest jej wierzchokiem. Paszczyzna bliska odcina szczyt piramidy, co powoduje powstanie ostrosupa citego pomidzy paszczyzn blisk a dalek.
Drugi sposb wyobraenia tej bryy wymaga postrzegania jej w postaci szecianu. Ten drugi scenariusz nosi nazw rzutowania ortograficznego i jest wykorzystywany do rysowania obiektw
geometrycznych, ktre musz zachowa rozmiary bez wzgldu na odlego od kamery.
Listing 20.6 prezentuje nam sposb, w jaki definiujemy ostrosup widzenia dla naszego przykadu.
Listing 20.6. Definiowanie ostrosupa widzenia za pomoc metody glFrustum
//Oblicza najpierw proporcje obrazu
float ratio = (float) w / h;

//Wskazuje na fakt, e wymagamy rzutowania perspektywicznego


glMatrixMode(GL10.GL_PROJECTION);

//Definiuje ostrosup widzenia: objto widzenia


gl.glFrustumf(
-ratio,
ratio,
1,
-1,
3,
7);

// Lewa strona bryy widzenia


// Prawa strona bryy widzenia
// Szczyt bryy widzenia
// Spd bryy widzenia
// Odlego przedniej ciany bryy od aparatu
// Odlego tylnej ciany bryy od aparatu

Poniewa w kodzie z listingu 20.6 przypisalimy szczytowi bryy warto 1, a jej spodniej cianie
warto -1, wysoko przedniej ciany wynosi 2 jednostki. Rozmiary lewej i prawej strony ostrosupa okrelamy za pomoc proporcjonalnych liczb, biorc pod uwag proporcj obrazu. Z tego
wanie powodu kod wykorzystuje wysoko i szeroko okna do okrelenia proporcji. Zakada
on rwnie, e obszar dziaania bdzie znajdowa si pomidzy 3. a 7. jednostk wzdu osi
z. Wszystkie obiekty narysowane poza tymi wsprzdnymi, wzgldnymi wobec aparatu, bd
niewidoczne.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

663

Poniewa umiecilimy aparat w punkcie (0, 0, 5) i skierowalimy go w stron punktu (0, 0, 0),
punkt zlokalizowany trzy jednostki od aparatu w stron pocztku ukadu wsprzdnych znajduje si w punkcie (0, 0, 2), a siedem jednostek od kamery znajduje si punkt (0, 0, 2). W ten
sposb paszczyzna pocztku ukadu wsprzdnych znajduje si dokadnie porodku trjwymiarowej bryy.
Teraz ju wiemy, jak wielka jest nasza objto widzenia. Istnieje jeszcze jeden interfejs API,
odwzorowujcy te rozmiary na ekranie glViewport.

glViewport i rozmiar ekranu


Metoda glViewport pozwala na zdefiniowanie prostoktnego obszaru ekranu, na ktry bdzie
rzutowana objto widzenia. Przyjmuje ona cztery argumenty okrelajce prostoktne pole:
wsprzdne x i y lewego dolnego rogu figury oraz szeroko i wysoko. Na listingu 20.7 zaprezentowano przykad okrelenia widoku jako celu rzutowania.
Listing 20.7. Definiowanie wziernika za pomoc metody glViewport
glViewport(0,
0,
width,
height);

// Wsprzdna x lewej dolnej krawdzi prostokta


// Wsprzdna y lewej dolnej krawdzi prostokta
// Szeroko prostokta na ekranie
// Wysoko prostokta na ekranie

Jeeli wysoko naszego okna lub widoku wynosi 100 pikseli, a wysoko ostrosupa widzenia
wynosi 10 pikseli, to kada jednostka logiczna wsprzdnych zostanie przetumaczona na
10 pikseli wsprzdnych wiata.
Dotychczas omwilimy niektre podstawowe, istotne pojcia dotyczce grafiki OpenGL. Zrozumienie tych podstaw przyda si podczas nauki programowania za pomoc biblioteki OpenGL.
Po spenieniu tego warunku moemy rozpocz omawianie elementw wymaganych do wywoania opisanych powyej interfejsw API.

Tworzenie interfejsu pomidzy standardem


OpenGL ES a Androidem
Jak ju zdylimy wspomnie, standard OpenGL ES jest obsugiwany na wielu rodzajach
platform. U jego podstaw znajduje si, przypominajcy struktur jzyka C, interfejs API, ktry
zapewnia obsug wszystkich aspektw rysowania. Jednak platformy i systemy operacyjne rni si midzy sob pod wzgldem implementacji wywietlania, buforw ekranu i tym podobnych elementw. Te specyficzne dla kadego systemu operacyjnego aspekty s przetwarzane
i dokumentowane przez te systemy. Android nie jest pod tym wzgldem wyjtkiem.
Poczwszy od wersji 1.5 rodowiska SDK, w Androidzie uproszczono procesy interakcji z funkcjami OpenGL oraz inicjalizacji rysowania w standardzie OpenGL. Jest to moliwe dziki pakietowi android.opengl. Gwn klas zawierajc wiele funkcji jest GLSurfaceView, ktra
posiada wewntrzny interfejs GLSurfaceView.Renderer. Znajomo tych dwch elementw
wystarczy do poczynienia znacznych postpw na polu programowania w standardzie OpenGL
w Androidzie.

664 Android 3. Tworzenie aplikacji

Stosowanie klasy GLSurfaceView i klas pokrewnych


Poczwszy od wersji 1.5 rodowiska SDK, powszechny wzorzec stosowania biblioteki OpenGL
zosta znacznie uproszczony. Podczas rysowania za pomoc klas biblioteki OpenGL stosujemy
zazwyczaj nastpujcy algorytm:
1. Zaimplementuj interfejs Renderer.
2. Skonfiguruj w implementacji interfejsu Renderer ustawienia klasy Camera.
3. W implementacji wprowad do metody onDrawFrame kod odpowiedzialny za rysowanie.
4. Skonstruuj widok GLSurfaceView.
5. Skonfiguruj silnik renderujcy, utworzony w punktach 1. 3., wewntrz klasy
GLSurfaceView.
6. Okrel, czy jest wymagana animacja w klasie GLSurfaceView.
7. Skonfiguruj kontrolk GLSurfaceView w aktywnoci jako widok treci. Moemy
rwnie uywa tego widoku wszdzie tam, gdzie korzystamy ze zwykego widoku.
Zacznijmy od implementacji silnika renderujcego.

Implementacja klasy Renderer


Sygnatura tego interfejsu zostaa ukazana na listingu 20.8.
Listing 20.8. Interfejs Renderer
public static interface GLSurfaceView.Renderer
{
void onDrawFrame(GL10 gl);
void onSurfaceChanged(GL10 gl, int width, int height);
void onSurfaceCreated(GL10 gl, EGLConfig config);
}

Gwny proces rysowania przebiega w metodzie onDrawFrame(). Zawsze podczas tworzenia


nowej powierzchni dla tego widoku zostaje wywoana metoda onSurfaceCreated(). Moemy
wywoa wiele interfejsw API biblioteki OpenGL, takich jak roztrzsanie (ang. dithering),
kontrola gbi oraz inne, ktre mona wywoa bezporednio spoza metody onDrawFrame().
Analogicznie w przypadku zmiany powierzchni, na przykad szerokoci i wysokoci okna, zostaje wywoana metoda onSurfaceChanged(). Dziki niej moemy konfigurowa kamer oraz
objto widzenia.
Nawet w metodzie onDrawFrame() istnieje wiele elementw, ktre mog by wsplne dla okrelonego kontekstu rysowania. Moemy wykorzysta t powszednio i umieci te metody na
kolejnym poziomie abstrakcji, zwanym AbstractRenderer, zawierajcym tylko jedn niezaimplementowan metod draw().
Na listingu 20.9 zosta zaprezentowany kod klasy AbstractRenderer.
Listing 20.9. Klasa AbstractRenderer
//nazwa pliku: AbstractRenderer.java
import android.opengl.*;

//Za pomoc rodowiska Eclipse wprowadmy pozostae instrukcje importu

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

665

public abstract class AbstractRenderer


implements android.opengl.GLSurfaceView.Renderer
{
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
gl.glDisable(GL10.GL_DITHER);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
GL10.GL_FASTEST);
gl.glClearColor(.5f, .5f, .5f, 1);
gl.glShadeModel(GL10.GL_SMOOTH);
gl.glEnable(GL10.GL_DEPTH_TEST);
}
public void onSurfaceChanged(GL10 gl, int w, int h) {
gl.glViewport(0, 0, w, h);
float ratio = (float) w / h;
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
}
public void onDrawFrame(GL10 gl)
{
gl.glDisable(GL10.GL_DITHER);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
draw(gl);
}
protected abstract void draw(GL10 gl);
}

Klasa ta jest bardzo poyteczna, poniewa pozwala na skupienie si jedynie na metodach rysowania. Wykorzystamy j do utworzenia naszej prostej klasy SimpleTriangleRenderer; listing
20.10 ukazuje jej kod rdowy.
Listing 20.10. Klasa SimpleTriangleRenderer
//nazwa pliku: SimpleTriangleRenderer.java
public class SimpleTriangleRenderer extends AbstractRenderer
{

//Liczba uywanych punktw lub wierzchokw


private final static int VERTS = 3;

//Nieskompresowany, natywny bufor przechowujcy wsprzdne punktw


private FloatBuffer mFVertexBuffer;

//Nieskompresowany, natywny bufor przechowujcy indeksy


//pozwalajce na wielokrotne wykorzystywanie punktw.
private ShortBuffer mIndexBuffer;
public SimpleTriangleRenderer(Context context)
{

666 Android 3. Tworzenie aplikacji


ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
vbb.order(ByteOrder.nativeOrder());
mFVertexBuffer = vbb.asFloatBuffer();
ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
ibb.order(ByteOrder.nativeOrder());
mIndexBuffer = ibb.asShortBuffer();
float[] coords = {
-0.5f, -0.5f, 0,

// (x1, y1, z1)

0.5f, -0.5f, 0,
0.0f, 0.5f, 0
};
for (int i = 0; i < VERTS; i++) {
for(int j = 0; j < 3; j++) {
mFVertexBuffer.put(coords[i*3+j]);
}
}
short[] myIndecesArray = {0,1,2};
for (int i=0;i<3;i++)
{
mIndexBuffer.put(myIndecesArray[i]);
}
mFVertexBuffer.position(0);
mIndexBuffer.position(0);
}

//przesonita metoda
protected void draw(GL10 gl)
{
gl.glColor4f(1.0f, 0, 0, 0.5f);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, VERTS,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
}
}

Chocia wydaje si, e powyszy listing ma znaczne rozmiary, wikszo kodu suy do definiowania wierzchokw i konwertowania ich z buforw kodu jzyka Java do buforw nio. Sama
metoda draw skada si jedynie z trzech wierszy: ustanowienia koloru, ustanowienia wierzchokw i rysowania.
W naszym kodzie nigdy nie uwalniamy buforw nio, chocia przydzielamy im pami.
Zatem w jaki sposb s one uwalniane? Jak wykorzystanie tej pamici wpywa na
bibliotek OpenGL?
Na podstawie bada stwierdzilimy, e pakiet java.nio przydziela przestrze pamici
spoza stosu Java. Pami ta moe by bezporednio wykorzystana przez takie systemy,
jak OpenGL, File I/O i tak dalej. W rzeczywistoci bufory nio s obiektami Java, ktre
ostatecznie wskazuj na bufor natywny. Te obiekty nio s odmiecane (ang. garbage
collection gc). Oznacza to, e po wykonaniu pracy oczyszczaj pami natywn.
Programy Java nie musz przeprowadza adnych specjalnych operacji, aby zwolni
pami.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

667

Jednak proces odmiecania nie zostanie przeprowadzony, dopki w stosie Java istnieje
wykorzystywana pami. Oznacza to, e mimo wykorzystania pamici natywnej proces
gc moe nie zosta uruchomiony. W internecie mona znale informacje na temat
wyjtku braku pamici uruchamiajcego odmiecanie. Po wystpieniu tego wyjtku
mona sprawdzi, czy pami staa si ju dostpna.
W warunkach standardowych to jest istotne w przypadku biblioteki OpenGL
moemy przydziela bufory natywne i nie musimy si martwi jawnym zwalnianiem
przydzielonej pamici, poniewa czyszczenie pamici wykonuje proces gc.

Skoro uzyskalimy ju przykadowy silnik renderujcy, zobaczmy, w jaki sposb moemy go


dostarczy klasie GLSurfaceView i wywietli w aktywnoci.

Zastosowanie klasy GLSurfaceView z poziomu aktywnoci


Na listingu 20.11 przedstawiono typow aktywno, wykorzystujc klas GLSurfaceView wraz
z odpowiednim silnikiem renderujcym.
Listing 20.11. Proste rodowisko testowe biblioteki OpenGL, nazwane OpenGLTestHarnessActivity
public class OpenGLTestHarnessActivity extends Activity {
private GLSurfaceView mTestHarness;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTestHarness = new GLSurfaceView(this);
mTestHarness.setEGLConfigChooser(false);
mTestHarness.setRenderer(new SimpleTriangleRenderer(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

//mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
setContentView(mTestHarness);
}
@Override
protected void onResume() {
super.onResume();
mTestHarness.onResume();
}
@Override
protected void onPause() {
super.onPause();
mTestHarness.onPause();
}
}

Wyjanijmy kilka kluczowych elementw tego kodu rdowego. Fragment, ktry tworzy
widok GLSurfaceView, wyglda nastpujco:
mTestHarness = new GLSurfaceView(this);

Nastpny wiersz oznacza, e nie wymagamy wyboru specjalnej konfiguracji biblioteki EDL i e
wystarcz ustawienia domylne:
mTestHarness.setEGLConfigChooser(false);

668 Android 3. Tworzenie aplikacji


Kolejnym etapem jest skonfigurowanie silnika renderowania:
mTestHarness.setRenderer(new SimpleTriangleRenderer(this));

Jedna z dwch metod umieszczonych w dalszej czci kodu umoliwia proces animacji:
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
//mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

W przypadku wyboru pierwszego z powyszych dwch wierszy proces rysowania zostanie


wywoany tylko jeden raz, a cilej mwic, jeden raz po kadym wywoaniu metody. Wybr
drugiej opcji spowoduje, e kod rysowania bdzie wykonywany nieprzerwanie, co mona wykorzysta do animowania obiektw rysowanych.
To tyle na temat korzystania z interfejsw biblioteki OpenGL w Androidzie.
Teraz Czytelnik posiada wszystkie elementy niezbdne do przetestowania procesu rysowania.
Aktywno zaprezentowalimy na listingu 20.11; abstrakcyjny silnik renderujcy pokazalimy
na listingu 20.9, a sam klas SimpleTriangleRenderer na listingu 20.10. Teraz musimy
jedynie wywoa klas aktywnoci poprzez dowolny element menu w sposb przedstawiony
poniej:
private void invokeSimpleTriangle()
{
Intent intent = new Intent(this,OpenGLTestHarnessActivity.class);
startActivity(intent);
}

Oczywicie niezbdne jest zarejestrowanie aktywnoci w pliku manifecie Androida, na przykad tak:
<activity android:name=".OpenGLTestHarnessActivity"
android:label="rodowisko testowe OpenGL"/>

Chocia utworzenie samodzielnej aktywnoci, takiej jak OpenGLTestHarnessActivity z listingu 20.11, jest cakowicie rozsdnym zachowaniem, chcielibymy zaproponowa alternatyw,
ktra bdzie o wiele lepiej pasowaa do tematyki poruszanej w niniejszym rozdziale.
Nasza propozycja jest zwizana z umieszczeniem wielu przykadowych fragmentw kodu w obrbie rozdziau. Gdybymy osobno przedstawiali aktywnoci dla poszczeglnych aplikacji, zapenilibymy rozdzia kodami bardzo podobnymi do przedstawionego na listingu 20.11, ktre
nie wprowadzayby niczego nowego. Ponadto kada taka aktywno musi by zarejestrowana
w pliku manifecie.
Majc to na uwadze, utworzymy ogln aktywno pozwalajc na testowanie wszystkich omawianych tu przykadowych projektw. Jej kod zosta umieszczony na listingu 20.12. Moe si on
wydawa do rozbudowany w porwnaniu do pierwotnej wersji, jeeli jednak przyjrzymy si
odpowiedzi menu zawartej w zasobie R.id.mid_OpenGL_SimpleTriangle, zauwaymy, e zasadniczo zachowanie aplikacji nie ulega zmianie. Wraz ze zwikszaniem liczby elementw menu
ronie liczba instrukcji if, po jednej dla kadego przykadu.
Pozostae opcje menu bd omawiane w dalszej czci rozdziau. Po kodzie z listingu 20.12 zaprezentujemy zawarto pliku main_menu.xml, po czym dokadniej omwimy nasz wielozadaniow aktywno.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

669

Listing 20.12. Aktywno MultiViewTestHarness


//nazwa pliku: MultiViewTestHarnessActivity.java
public class MultiViewTestHarnessActivity extends Activity {
private GLSurfaceView mTestHarness;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTestHarness = new GLSurfaceView(this);
mTestHarness.setEGLConfigChooser(false);
Intent intent = getIntent();
int mid = intent.getIntExtra("com.ai.menuid", R.id.mid_OpenGL_Current);
if (mid == R.id.mid_OpenGL_SimpleTriangle)
{
mTestHarness.setRenderer(new SimpleTriangleRenderer(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setContentView(mTestHarness);
return;
}
if (mid == R.id.mid_OpenGL_Current)
{

//Wywouje inny silnik renderujcy OpenGL


//i
//wraca;
}

//w przeciwnym wypadku wykonuje ponisz czynno


mTestHarness.setRenderer(new SimpleTriangleRenderer(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
setContentView(mTestHarness);
return;
}
@Override
protected void onResume() {
super.onResume();
mTestHarness.onResume();
}
@Override
protected void onPause() {
super.onPause();
mTestHarness.onPause();
}
}

Kod menu z listingu 20.13 obsuguje aktywno widoczn na listingu 20.12. Dokadniej mwic,
jest to plik res/menu/main_menu.xml. Wyprzedzilimy nieco fakty i utworzylimy wszystkie
elementy menu dotyczce przykadw zamieszczonych w tym rozdziale.
Listing 20.13. Plik gwnego menu
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa wykorzystuje domyln kategori. -->


<group android:id="@+id/menuGroup_Main">

670 Android 3. Tworzenie aplikacji


<item android:id="@+id/mid_OpenGL_SimpleTriangle"
android:title="Prosty trjkt" />
<item android:id="@+id/mid_OpenGL_SimpleTriangle2"
android:title="Dwa trjkty" />
<item android:id="@+id/mid_OpenGL_AnimatedTriangle"
android:title="Animowany trjkt" />
<item android:id="@+id/mid_rectangle"
android:title="Prostokt" />
<item android:id="@+id/mid_square_polygon"
android:title="Kwadrat" />
<item android:id="@+id/mid_polygon"
android:title="Wielokt" />
<item android:id="@+id/mid_textured_square"
android:title="Teksturowany kwadrat" />
<item android:id="@+id/mid_textured_polygon"
android:title="Teksturowany wielokt" />
<item android:id="@+id/mid_multiple_figures"
android:title="Wiele figur" />
<item android:id="@+id/mid_OpenGL_Current"
android:title="Bieca" />
<item android:id="@+id/mid_es20_triangle"
android:title="Trjkt w wersji ES20" />
</group>
</menu>

Przygldajc si zawartoci pliku menu, moemy si domyla, jakie silniki renderujce zostan
zademonstrowane. Jeeli cofniemy si do aktywnoci z listingu 20.12, stwierdzimy, e przecza
ona silniki renderowania na podstawie zdefiniowanych w tym pliku identyfikatorw menu.
W jaki sposb ta aktywno uzyskuje identyfikatory poszczeglnych elementw menu? Odbywa
si to dziki nastpujcemu fragmentowi kodu (zosta on skopiowany z listingu 20.12):
Intent intent = getIntent();
int mid = intent.getIntExtra("com.ai.menuid",
R.id.mid_OpenGL_Current);

Dziki powyszemu fragmentowi kodu intencja przywoujca aktywno przekazuje dodatkowe


dane, zwane com.ai.menuid. Jeeli dane te s nieobecne, kod uyje identyfikatora menu
mid_opengl_current, ktry stanie si domylnym identyfikatorem.
Co powoduje wstawianie dodatkowych danych do intencji? Gdzie si znajduje przywoujca,
sterujca aktywno? Zostaa ona zaprezentowana na listingu 20.14.
Listing 20.14. Aktywno TestOpenGLMainDriver
public class TestOpenGLMainDriverActivity extends Activity {

/** Wywoywana podczas pierwszego uruchomienia aktywnoci. */


@Override

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

671

public void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu){
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater(); //z aktywnoci
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
this.invokeMultiView(item.getItemId());
return true;
}
private void invokeMultiView(int mid)
{
Intent intent =
new Intent(this,MultiViewTestHarnessActivity.class);
intent.putExtra("com.ai.menuid", mid);
startActivity(intent);
}
}

Do umoliwienia procesu kompilacji potrzebny jest nam jeszcze plik ukadu graficznego.
Zosta on zaprezentowany na listingu 20.15.
Listing 20.15. Ukad graficzny aktywnoci TestOpenGLMainDriver (layout/main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Prosta aktywno gwna. Kliknij przycisk menu, aby kontynuowa"
/>
</LinearLayout>

Oczywicie, w systemie Android konieczny jest jeszcze plik manifest. Zapoznamy si z jego kodem na listingu 20.16.
Listing 20.16. Plik AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.OpenGL"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon"

672 Android 3. Tworzenie aplikacji


android:label="rodowisko testowe OpenGL"
android:debuggable="true">
<activity android:name=".TestOpenGLMainDriverActivity"
android:label="rodowisko testowe OpenGL">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="MultiViewTestHarnessActivity"
android:label="rodowisko testowe OpenGL wiele widokw"/>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

Podsumowujc, do skompilowania i uruchomienia naszego programu wymagane bd nastpujce pliki:


TestOpenGLMainDriverActivity.java (gwna aktywno sterujca; listing 20.14),
AbstractRenderer.java (listing 20.9),
SimpleTriangleRenderer.java (listing 20.10),
MultiViewTestHarnessActivity.java (listing 20.12),
res/menu/main_menu.xml (plik menu; listing 20.13),
layout/main.xml (plik ukadu graficznego; listing 20.15).
Po skompilowaniu i uruchomieniu kodu zobaczymy wywietlon aktywno sterujc. Moemy
klikn przycisk menu, aby zostaa wywietlona lista dostpnych opcji, co zostao zaprezentowane na rysunku 20.2.
Jeeli klikniemy teraz element Prosty trjkt, ujrzymy trjkt przedstawiony na rysunku 20.3.

Zmiana ustawie kamery


Aby lepiej zrozumie znaczenie wsprzdnych biblioteki OpenGL, poeksperymentujmy
z metodami definiujcymi kamer i sprawdmy, w jaki sposb wpywaj one na wygld trjkta
z rysunku 20.3. Zapamitajmy wsprzdne jego wierzchokw: (-0.5, -0.5, 0; 0.5, -0.5,
0; 0, 0.5, 0). Za ich pomoc ponisze trzy metody, uyte w obiekcie AbstractRenderer
(listing 20.9), wygeneroway trjkt widoczny na rysunku 20.3:
//Spoglda na ekran (pocztek ukadu wsprzdnych) z odlegoci 5 jednostek od
//przedniej czci ekranu
GLU.gluLookAt(gl, 0,0,5, 0,0,0, 0,1,0);

//Ustanawia 2 jednostki wysokoci i 4 jednostki gbi


gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);

//Definiuje standardowe okno


gl.glViewport(0, 0, w, h);

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

673

Rysunek 20.2. Interfejs aktywnoci sterujcej dla rodowiska testowego OpenGL

Rysunek 20.3. Prosty trjkt utworzony za pomoc biblioteki OpenGL

Zamy teraz, e przypisujemy przeciwny zwrot wektorowi gry kamery:


GLU.gluLookAt(gl, 0,0,5,

0,0,0,

0,-1,0);

Jeli przeprowadzimy tak operacj, ujrzymy odwrcony trjkt z rysunku 20.4. Aby wprowadzi
tak zmian, powinnimy znale waciw metod w pliku AbstractRenderer.java (listing 20.9).

674 Android 3. Tworzenie aplikacji

Rysunek 20.4. Trjkt rejestrowany przez odwrcon kamer

Spjrzmy teraz, co si stanie, jeli zmienimy ostrosup widzenia (zwany take objtoci lub
bry widzenia). Dziki poniszej linii kodu zwiksza si wysoko i szeroko bryy widzenia
o wspczynnik 4 (wymiary zostay zilustrowane na rysunku 20.1). Przypominamy, e pierwsze
cztery argumenty klasy glFrustum definiuj przedni cian bryy widzenia. Mnoc kad
warto przez 4, powikszylimy bry widzenia czterokrotnie, tak jak poniej:
gl.glFrustumf(-ratio * 4, ratio * 4, -1 * 4, 1 *4, 3, 7);

W wyniku tego kodu trjkt zostaje zmniejszony, poniewa jego rozmiary nie ulegy zmianie,
ale brya widzenia zostaa powikszona (rysunek 20.5). Wywoanie tej metody pojawia si w klasie
AbstractRenderer.java (listing 20.9).

Rysunek 20.5. Trjkt umieszczony w czterokrotnie powikszonej bryle widzenia

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

675

Wykorzystanie indeksw do dodania kolejnego trjkta


Omwienie tych prostych przykadw z trjktem zakoczymy opisem dziedziczenia z klasy
AbstractRenderer i utworzenia jeszcze jednego trjkta poprzez wstawienie dodatkowego
punktu i skorzystanie z indeksw. W teorii zdefiniujemy cztery punkty (-1,-1; 1,-1; 0,1;
1,1). Nastpnie za pomoc biblioteki OpenGL zaprogramujemy narysowanie wierzchokw
(0,1,2; 0,2,3). Listing 20.17 przedstawia odpowiedzialny za to kod (zwrmy uwag, e
zmienilimy rozmiary trjkta).
Listing 20.17. Klasa SimpleTriangleRenderer2
//nazwa pliku: SimpleTriangleRenderer2.java
public class SimpleTriangleRenderer2 extends AbstractRenderer
{
private final static int VERTS = 4;
private FloatBuffer mFVertexBuffer;
private ShortBuffer mIndexBuffer;
public SimpleTriangleRenderer2(Context context)
{
ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
vbb.order(ByteOrder.nativeOrder());
mFVertexBuffer = vbb.asFloatBuffer();
ByteBuffer ibb = ByteBuffer.allocateDirect(6 * 2);
ibb.order(ByteOrder.nativeOrder());
mIndexBuffer = ibb.asShortBuffer();
float[] coords = {
-1.0f, -1.0f, 0,

// (x1, y1, z1)

1.0f, -1.0f, 0,
0.0f, 1.0f, 0,
1.0f, 1.0f, 0

};
for (int i = 0; i < VERTS; i++) {
for(int j = 0; j < 3; j++) {
mFVertexBuffer.put(coords[i*3+j]);
}
}
short[] myIndecesArray = {0,1,2, 0,2,3};
for (int i=0;i<6;i++)
{
mIndexBuffer.put(myIndecesArray[i]);
}
mFVertexBuffer.position(0);
mIndexBuffer.position(0);

protected void draw(GL10 gl)


{
gl.glColor4f(1.0f, 0, 0, 0.5f);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, 6, GL10.GL_UNSIGNED_SHORT,
mIndexBuffer);
}
}

676 Android 3. Tworzenie aplikacji


Po utworzeniu klasy SimpleTriangleRenderer2 moemy doda instrukcj warunkow if z listingu 20.18 do aktywnoci OpenGLTestHarnessActivity (listing 20.12).
Listing 20.18. Stosowanie klasy SimpleTriangleRenderer2
if (mid == R.id.mid_OpenGL_SimpleTriangle2)
{
mTestHarness.setRenderer(new SimpleTriangleRenderer2(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setContentView(mTestHarness);
return;
}

Po dodaniu tego fragmentu moemy ponownie uruchomi aplikacj i wybra tym razem opcj
Dwa trjkty (rysunek 20.6). Zwrmy uwag, e dziki konstrukcji klasy MultiViewTestHarness
nie ma ju koniecznoci tworzenia nowej aktywnoci, a tym samym rwnie rejestrowania jej
w pliku manifecie. W dalszym cigu bdziemy wykorzystywa ten mechanizm poprzez ustawiczne dodawanie klauzuli if dla kadego nastpnego przykadu.

Rysunek 20.6. Dwa trjkty utworzone za pomoc czterech punktw

Animowanie prostego trjkta w bibliotece OpenGL


Zmiany trybu renderowania widoku GLSurfaceView pozwalaj na proste dostosowywanie
animacji tworzonych za pomoc biblioteki OpenGL. Na listingu 20.19 przedstawilimy przykadowy kod.
Listing 20.19. Zdefiniowanie trybu cigego renderowania
//Pobiera widok GLSurfaceView
GLSurfaceView openGLView;

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

677

//Ustanawia tryb cigego rysowania


openGLView.setRenderingMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

Zwracamy uwag na tryb renderowania, poniewa w poprzednim przykadzie okrelilimy


tryb RENDERMODE_WHEN_DIRTY (na listingu 20.18). Jak ju wspomnielimy, tryb RENDERMODE_
CONTINUOUSLY jest domylnym ustawieniem, zatem animacja jest dostpna domylnie.
Jeeli wybrano tryb rysowania cigego, zjawiska wpywajce na animacj s zalene od metody
onDraw silnika renderujcego. W celach demonstracyjnych obrmy wok wasnej osi utworzony wczeniej trjkt (listing 20.10 i rysunek 20.3).

AnimatedSimpleTriangleRenderer
Klasa AnimatedSimpleTriangleRenderer bardzo przypomina klas SimpleTriangleRenderer
(listing 20.10), nie liczc tego, co si dzieje w metodzie onDraw. W metodzie tej definiujemy
nowy kt dla obrotu wykonywanego co cztery sekundy. Poniewa obraz bdzie systematycznie
przerysowywany, odniesiemy wraenie powoli obracajcego si trjkta. Listing 20.20 zawiera
pen implementacj klasy AnimatedSimpleTriangleRenderer.
Listing 20.20. Kod rdowy klasy AnimatedSimpleTriangleRenderer
//nazwa pliku: AnimatedSimpleTriangleRenderer.java
public class AnimatedSimpleTriangleRenderer extends AbstractRenderer
{
private int scale = 1;

//Liczba uywanych punktw lub wierzchokw


private final static int VERTS = 3;

//Nieskompresowany bufor natywny, przechowujcy wsprzdne punktu


private FloatBuffer mFVertexBuffer;

//Nieskompresowany bufor natywny, przechowujcy indeksy


//pozwalajce na wielokrotne wykorzystywanie punktw.
private ShortBuffer mIndexBuffer;
public AnimatedSimpleTriangleRenderer(Context context)
{
ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
vbb.order(ByteOrder.nativeOrder());
mFVertexBuffer = vbb.asFloatBuffer();
ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
ibb.order(ByteOrder.nativeOrder());
mIndexBuffer = ibb.asShortBuffer();
float[] coords = {
-0.5f, -0.5f, 0,

// (x1, y1, z1)

0.5f, -0.5f, 0,
0.0f, 0.5f, 0
};
for (int i = 0; i < VERTS; i++) {
for(int j = 0; j < 3; j++) {
mFVertexBuffer.put(coords[i*3+j]);

678 Android 3. Tworzenie aplikacji


}
}
short[] myIndecesArray = {0,1,2};
for (int i=0;i<3;i++)
{
mIndexBuffer.put(myIndecesArray[i]);
}
mFVertexBuffer.position(0);
mIndexBuffer.position(0);
}

//przesonita metoda
protected void draw(GL10 gl)
{
long time = SystemClock.uptimeMillis() % 4000L;
float angle = 0.090f * ((int) time);
gl.glRotatef(angle, 0, 0, 1.0f);
gl.glColor4f(1.0f, 0, 0, 0.5f);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, VERTS,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
}
}

Po utworzeniu klasy AnimatedSimpleTriangleRenderer moemy wstawi widoczn na listingu


20.21 instrukcj warunkow if do klasy MultiViewTestHarness z listingu 20.12.
Listing 20.21. Korzystanie z klasy AnimatedSimpleTriangleRenderer
if (mid == R.id.mid_OpenGL_AnimatedTriangle)
{
mTestHarness.setRenderer(new AnimatedSimpleTriangleRenderer(this));
setContentView(mTestHarness);
return;
}

Po dodaniu tego kodu moemy ponownie uruchomi aplikacj i wybra z menu opcj Animowany trjkt, aby ujrze obracajcy si trjkt widoczny na rysunku 20.3. <<F1-p>>

Stawianie czoa bibliotece OpenGL


ksztaty i tekstury
W dotychczas ukazanych przykadach definiowalimy wierzchoki trjkta w sposb jawny.
Takie podejcie staje si niewygodne w przypadku rysowania kwadratw, picioktw, szecioktw i tak dalej. W ich przypadku potrzebne bd wysokopoziomowe abstrakcje obiektw, takie
jak ksztaty, a nawet grafy sceny, w przypadku ktrych ksztaty decyduj o ich wsprzdnych.
W taki sposb pokaemy algorytm rysowania dowolnego wielokta w dowolnym miejscu.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

679

W tym podrozdziale zajmiemy si rwnie teksturami biblioteki OpenGL. Umoliwiaj one


umieszczanie map bitowych oraz innych obrazw na powierzchni narysowanego obiektu.
W celach demonstracyjnych pokaemy, w jaki sposb umieci tekstury na znanych nam ju
wieloktach. Przy tej okazji przedstawimy kolejn bardzo istotn czynno wykonywan w rodowisku OpenGL rysowanie wielu figur geometrycznych lub ksztatw za pomoc potoku
rysowania.
Znajomo wymienionych powyej tematw powinna przybliy nas do skutecznego tworzenia trjwymiarowych obiektw i scen.

Rysowanie prostokta
Zanim przejdziemy do pojcia ksztatw, musimy rozszerzy znajomo rysowania za pomoc
jawnego definiowania wierzchokw na przykadzie prostokta utworzonego z dwch trjktw.
W ten sposb rwnie Czytelnik przygotuje si do rozwijania trjkta do dowolnego wieloboku.
Mamy ju wystarczajc wiedz na temat podstawowego trjkta, pokaemy wic teraz opatrzony krtkim komentarzem kod sucy do utworzenia prostokta (listing 20.22).
Listing 20.22. Silnik renderujcy prostokt
public class SimpleRectangleRenderer extends AbstractRenderer
{

//Liczba uywanych punktw lub wierzchokw


private final static int VERTS = 4;

//Nieskompresowany bufor natywny, przechowujcy wsprzdne punktu


private FloatBuffer mFVertexBuffer;

//Nieskompresowany bufor natywny, przechowujcy indeksy


//pozwalajce na wielokrotne wykorzystywanie punktw.
private ShortBuffer mIndexBuffer;
public SimpleRectRenderer(Context context)
{
ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
vbb.order(ByteOrder.nativeOrder());
mFVertexBuffer = vbb.asFloatBuffer();
ByteBuffer ibb = ByteBuffer.allocateDirect(6 * 2);
ibb.order(ByteOrder.nativeOrder());
mIndexBuffer = ibb.asShortBuffer();
float[] coords = {
-0.5f, -0.5f, 0,

// (x1, y1, z1)

0.5f, -0.5f, 0,
0.5f, 0.5f, 0,
-0.5f, 0.5f, 0,
};
for (int i = 0; i < VERTS; i++) {
for(int j = 0; j < 3; j++) {
mFVertexBuffer.put(coords[i*3+j]);
}

680 Android 3. Tworzenie aplikacji


}
short[] myIndecesArray = {0,1,2,0,2,3};
for (int i=0;i<6;i++)
{
mIndexBuffer.put(myIndecesArray[i]);
}
mFVertexBuffer.position(0);
mIndexBuffer.position(0);
}

//przesonita metoda
protected void draw(GL10 gl)
{
gl.glColor4f(1.0f, 0, 0, 0.5f);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, 6,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
}
}

Zauwamy, e prostokt jest rysowany w sposb bardzo przypominajcy rysowanie trjkta.


Zamiast trzech wierzchokw okrelilimy cztery. Wykorzystalimy nastpnie indeksy:
short[] myIndecesArray = {0,1,2,0,2,3};

Dwukrotnie wykorzystalimy ponumerowane wierzchoki (od 0 do 3) w taki sposb, eby


kada trjka wierzchokw utworzya trjkt. Zatem wierzchoki (0, 1, 2) tworz jeden trjkt,
a punkty (0, 2, 3) tworz drugi. Narysowanie tych dwch trjktw za pomoc prostego obiektu
GL_TRIANGLES w efekcie pozwolio na uzyskanie prostokta.
Po utworzeniu tego silnika renderujcego moemy doda w klasie
(listing 20.12) instrukcj if, zaprezentowan na listingu 20.23.

MultiViewTestHarness

Listing 20.23. Zastosowanie klasy SimpleRectangleRenderer


if (mid == R.id.mid_rectangle)
{
mTestHarness.setRenderer(new SimpleRectangleRenderer(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setContentView(mTestHarness);
return;
}

Po dodaniu powyszego kodu moemy uruchomi ponownie nasz program i tym razem wybra opcj Prostokt, aby ujrze figur geometryczn ukazan na rysunku 20.7.

Praca z ksztatami
Jawne okrelanie wierzchokw figur geometrycznych moe by nuc czynnoci. Jeeli na
przykad chcemy narysowa dwudziestobok, musimy zdefiniowa 20 wierzchokw, gdzie kada
definicja wymaga okrelenia do trzech parametrw. cznie narysowanie dwudziestoboku
wymaga podania 60 wartoci. W przypadku bardziej skomplikowanych rysunkw staje si to
niewykonalne.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

681

Rysunek 20.7. Prostokt zoony z dwch trjktw, narysowany w rodowisku OpenGL

Wielobok foremny jako ksztat


Lepszym sposobem rysowania takich figur, jak trjkt lub kwadrat, jest zdefiniowanie abstrakcyjnego wieloboku poprzez zdefiniowanie jego rnych parametrw, na przykad wsprzdnych rodka i dugoci promienia, co spowoduje utworzenie macierzy jego wierzchokw oraz
ewentualnie macierzy indeksw (abymy mogli rysowa pojedyncze trjkty). Suca do tego
klasa nosi nazw RegularPolygon. Po utworzeniu takiego obiektu moemy go uy w kodzie
z listingu 20.24 do wygenerowania rnych wielobokw foremnych.
Listing 20.24. Zastosowanie klasy RegularPolygon
//Czworobok o promieniu 0.5
//zlokalizowany w punkcie (0, 0, 0) wsprzdnych (x, y, z)
RegularPolygon square = new RegularPolygon(0,0,0,0.5f,4);

//Wielobok zwraca wsprzdne wierzchokw


mFVertexBuffer = square.getVertexBuffer();

//Wielobok zwraca trjkty


mIndexBuffer = square.getIndexBuffer();

//Wiersz wymagany do metody glDrawElements


numOfIndices = square.getNumberOfIndices();

//Przypisuje bufory do pocztku ukadu


this.mFVertexBuffer.position(0);
this.mIndexBuffer.position(0);

//Ustanawia wskanik wierzchokw


gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);

682 Android 3. Tworzenie aplikacji


//Rysuje obiekt za pomoc danej liczby indeksw
gl.glDrawElements(GL10.GL_TRIANGLES, numOfIndices,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);

Zobaczmy, w jaki sposb uzyskalimy niezbdne wierzchoki i indeksy z ksztatu square. Chocia nie omwilimy idei uzyskiwania wierzchokw i indeksw wobec prostego ksztatu, moliwe
jest, e klasa RegularPolygon moe pochodzi od takiego podstawowego ksztatu, definiujcego
interfejs dla takiego prostego kontraktu. Na listingu 20.25 pokazalimy stosowny przykad:
Listing 20.25. Interfejs Shape
public interface Shape
{
FloatBuffer
ShortBuffer
int
}

getVertexBuffer();
getIndexBuffer();
getNumberOfIndices();

Okrelenie sposobu definiowania podstawowego interfejsu dla ksztatu pozostawiamy jako


wiczenie dla Czytelnika. Na razie wbudowalimy te metody bezporednio do klasy Regular
Polygon.

Implementacja ksztatu RegularPolygon


Jak ju stwierdzilimy, klasa RegularPolygon w bibliotece OpenGL okrela wartoci parametrw potrzebnych do rysowania figur metod definiowania wierzchokw. Najpierw trzeba
utworzy mechanizm definiujcy ten ksztat oraz jego umiejscowienie w geometrii.
W przypadku wielokta foremnego mona tego dokona na wiele sposobw. W omawianym
przykadzie definiujemy wielobok foremny za pomoc okrelenia liczby bokw i odlegoci
wierzchokw od rodka figury geometrycznej. Nazwalimy t odlego promieniem, poniewa wierzchoki wielokta foremnego mieszcz si na obwodzie okrgu, ktrego rodek pokrywa si ze rodkiem wieloboku. Zatem promie takiego okrgu oraz liczba bokw wystarcz
nam do opisania takiego wielokta. Poprzez podanie wsprzdnych jego rodka moemy take
umieci ten wielokt w dowolnym miejscu naszej geometrii.
Zadaniem klasy RegularPolygon jest wygenerowanie na podstawie wartoci wsprzdnych
punktu rodkowego i liczby bokw wielokta wsprzdnych wszystkich jego wierzchokw.
Mamy do dyspozycji wiele sposobw wykonania tej czynnoci. Jakikolwiek aparat matematyczny
(na poziomie licealnym) zastosujemy, najwaniejsze, aby uzyskiwa wsprzdne wierzchokw.
W naszym przykadzie zaoylimy najpierw, e promie wynosi 1 jednostk. Zdefiniowalimy
kty kadej linii czcej rodek z wierzchokami wieloboku. Umiecilimy wartoci tych ktw
w macierzy. Dla kadego kta obliczylimy rzutowanie na o x i nazwalimy ten wspczynnik
macierz wielokrotnoci osi x (jest to macierz wielokrotnoci, poniewa rozpoczlimy od
jednostki promienia). Gdy bdziemy ju zna rzeczywisty promie, pomnoymy omawiane
wartoci przez jego warto i otrzymamy rzeczywist wsprzdn w osi x. Nastpnie te wsprzdne zostaj zachowane w tak zwanej macierzy osi x. Taka sama procedura jest wykonywana
dla wsprzdnych w osi y.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

683

Skoro ju zarysowalimy sposb dziaania naszej implementacji klasy RegularPolygon, zademonstrujemy kod rdowy odpowiedzialny za implementacj tych dziaa. Listing 20.26 prezentuje cay kod tego obiektu (zwrmy uwag, e mieci si on na kilku stronach). Aby zachowa przejrzysto, zaznaczylimy nazwy funkcji oraz zamiecilimy komentarze na pocztku
kadej z nich.
Definiujemy najwaniejsze funkcje, ktrych lista zostaa umieszczona po listingu 20.26. Wane
jest, aby zrozumie proces okrelania i przekazywania wierzchokw. Jeeli nasz przykad okae
si zbyt trudny, Czytelnik nie powinien mie problemu z napisaniem wasnej wersji kodu definiujcego wierzchoki. Zauwamy take, e w kodzie tym zostay rwnie umieszczone funkcje przeprowadzajce proces nakadania tekstury. Zostan one omwione w punkcie Praca
z teksturami.
Listing 20.26. Implementacja ksztatu RegularPolygon
public class RegularPolygon
{

//Przestrze przechowujca wsprzdne (x, y, z) rodka: cx, cy, cz


//oraz promie r
private float cx, cy, cz, r;
private int sides;

//Macierz wsprzdnych: punkty wierzchokw (x, y)


private float[] xarray = null;
private float[] yarray = null;

//Macierz tekstury: punkty (x, y), zwane take punktami (s, t)


//Figura zostanie odwzorowana na mapie bitowej tekstury
private float[] sarray = null;
private float[] tarray = null;

//**********************************************
// Constructor
//**********************************************
public RegularPolygon(float incx, float incy, float incz,
float inr,

// promie
int insides) // liczba bokw
{
cx = incx;
cy = incy;
cz = incz;
r = inr;
sides = insides;

//Przydziela pami dla macierzy


xarray = new float[sides];
yarray = new float[sides];

//Przydziela pami dla macierzy punktw tekstur


sarray = new float[sides];
tarray = new float[sides];

// rodek (x, y, z)

684 Android 3. Tworzenie aplikacji


//Oblicza wierzchoki
calcArrays();

//Oblicza punkty tekstury


calcTextureArrays();
}

//**********************************************
//Pobiera i konwertuje wsprzdne wierzchokw
//na podstawie rodka i promienia.
//Dziaania logiczne na ktach s przeprowadzane wewntrz funkcji
//getMultiplierArray()
//**********************************************
private void calcArrays()
{

//Definiuje wierzchoki na podstawie okrgu


//o promieniu rwnym 1 i rodku w pocztku ukadu wsprzdnych
float[] xmarray = this.getXMultiplierArray();
float[] ymarray = this.getYMultiplierArray();

//Oblicza xarray: otrzymuje wierzchoek


//poprzez dodanie skadowej x do pocztku ukadu wsprzdnych
//Mnoy wsprzdn przez promie (skal)
for(int i=0;i<sides;i++)
{
float curm = xmarray[i];
float xcoord = cx + r * curm;
xarray[i] = xcoord;
}
this.printArray(xarray, "xarray");

//Oblicza yarray: wykonuje te same czynnoci dla wsprzdnej y


for(int i=0;i<sides;i++)
{
float curm = ymarray[i];
float ycoord = cy + r * curm;
yarray[i] = ycoord;
}
this.printArray(yarray, "yarray");
}

//**********************************************
//Oblicza macierze tekstury
//Wicej informacji mona znale w punkcie powiconym teksturom
//Bardzo podobne rozwizanie.
//Tutaj wielokt musi zosta odwzorowany na kwadratowej przestrzeni
//**********************************************
private void calcTextureArrays()
{
float[] xmarray = this.getXMultiplierArray();
float[] ymarray = this.getYMultiplierArray();

//Oblicza xarray
for(int i=0;i<sides;i++)

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

685

{
float curm = xmarray[i];
float xcoord = 0.5f + 0.5f * curm;
sarray[i] = xcoord;
}
this.printArray(sarray, "sarray");

//Oblicza yarray
for(int i=0;i<sides;i++)
{
float curm = ymarray[i];
float ycoord = 0.5f + 0.5f * curm;
tarray[i] = ycoord;
}
this.printArray(tarray, "tarray");
}

//**********************************************
//Konwertuje macierz java wierzchokw
//do zmiennoprzecinkowego bufora nio
//**********************************************
public
{
int
int
int
int

FloatBuffer getVertexBuffer()
vertices = sides + 1;
coordinates = 3;
floatsize = 4;
spacePerVertex = coordinates * floatsize;

ByteBuffer vbb = ByteBuffer.allocateDirect(spacePerVertex * vertices);


vbb.order(ByteOrder.nativeOrder());
FloatBuffer mFVertexBuffer = vbb.asFloatBuffer();

//Umieszcza pierwsz wsprzdn (x, y, z: 0, 0, 0)


mFVertexBuffer.put(cx); //x
mFVertexBuffer.put(cy); //y
mFVertexBuffer.put(0.0f); //z
int totalPuts = 3;
for (int i=0;i<sides;i++)
{
mFVertexBuffer.put(xarray[i]);
mFVertexBuffer.put(yarray[i]);
mFVertexBuffer.put(0.0f);

//x
//y

//z

totalPuts += 3;
}
Log.d("cznie wstawiono:",Integer.toString(totalPuts));
return mFVertexBuffer;
}

//**********************************************
//Konwertuje bufor tekstury do bufora nio
//**********************************************
public FloatBuffer getTextureBuffer()
{

686 Android 3. Tworzenie aplikacji


int
int
int
int

vertices = sides + 1;
coordinates = 2;
floatsize = 4;
spacePerVertex = coordinates * floatsize;

ByteBuffer vbb = ByteBuffer.allocateDirect(spacePerVertex * vertices);


vbb.order(ByteOrder.nativeOrder());
FloatBuffer mFTextureBuffer = vbb.asFloatBuffer();

//Umieszcza pierwsz wsprzdn (x, y (s, t):0, 0)


mFTextureBuffer.put(0.5f); //x lub s
mFTextureBuffer.put(0.5f); //y lub t
int totalPuts = 2;
for (int i=0;i<sides;i++)
{
mFTextureBuffer.put(sarray[i]);
mFTextureBuffer.put(tarray[i]);

//x
//y

totalPuts += 2;
}
Log.d("czna liczba wstawionych tekstur:",Integer.toString(totalPuts));
return mFTextureBuffer;
}

//**********************************************
//Oblicza indeksy tworzce wiele trjktw.
//Rozpoczyna od rodkowego wierzchoka (punkt 0)
//Nastpnie numeruje je zgodnie z kierunkiem ruchu wskazwek zegara, na przykad
//0, 1, 2; 0, 2, 3; 0, 3, 4... itd.
//**********************************************
public ShortBuffer getIndexBuffer()
{
short[] iarray = new short[sides * 3];
ByteBuffer ibb = ByteBuffer.allocateDirect(sides * 3 * 2);
ibb.order(ByteOrder.nativeOrder());
ShortBuffer mIndexBuffer = ibb.asShortBuffer();
for (int i=0;i<sides;i++)
{
short index1 = 0;
short index2 = (short)(i+1);
short index3 = (short)(i+2);
if (index3 == sides+1)
{
index3 = 1;
}
mIndexBuffer.put(index1);
mIndexBuffer.put(index2);
mIndexBuffer.put(index3);
iarray[i*3 + 0]=index1;
iarray[i*3 + 1]=index2;
iarray[i*3 + 2]=index3;
}
this.printShortArray(iarray, "index array");
return mIndexBuffer;

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

//**********************************************
//Std jest pobierana macierz ktw
//dla kadego wierzchoka i zostaje obliczony ich wspczynnik rzutowania
//na o x
//**********************************************
private float[] getXMultiplierArray()
{
float[] angleArray = getAngleArrays();
float[] xmultiplierArray = new float[sides];
for(int i=0;i<angleArray.length;i++)
{
float curAngle = angleArray[i];
float sinvalue = (float)Math.cos(Math.toRadians(curAngle));
float absSinValue = Math.abs(sinvalue);
if (isXPositiveQuadrant(curAngle))
{
sinvalue = absSinValue;
}
else
{
sinvalue = -absSinValue;
}
xmultiplierArray[i] = this.getApproxValue(sinvalue);
}
this.printArray(xmultiplierArray, "xmultiplierArray");
return xmultiplierArray;
}

//**********************************************
//Std jest pobierana macierz ktw
//dla kadego wierzchoka i zostaje obliczony ich wspczynnik rzutowania
//na o y
//**********************************************
private float[] getYMultiplierArray() {
float[] angleArray = getAngleArrays();
float[] ymultiplierArray = new float[sides];
for(int i=0;i<angleArray.length;i++) {
float curAngle = angleArray[i];
float sinvalue = (float)Math.sin(Math.toRadians(curAngle));
float absSinValue = Math.abs(sinvalue);
if (isYPositiveQuadrant(curAngle)) {
sinvalue = absSinValue;
}
else {
sinvalue = -absSinValue;
}
ymultiplierArray[i] = this.getApproxValue(sinvalue);
}
this.printArray(ymultiplierArray, "ymultiplierArray");
return ymultiplierArray;
}

//**********************************************
//Ta funkcja moe by niepotrzebna

687

688 Android 3. Tworzenie aplikacji


//Naley j samodzielnie sprawdzi i usun, jeli nie okae si przydatna
//**********************************************
private boolean isXPositiveQuadrant(float angle) {
if ((0 <= angle) && (angle <= 90)) { return true; }
if ((angle < 0) && (angle >= -90)) { return true; }
return false;
}

//**********************************************
//Ta funkcja moe by niepotrzebna
//Naley j samodzielnie sprawdzi i usun, jeli nie okae si przydatna
//**********************************************
private boolean isYPositiveQuadrant(float angle) {
if ((0 <= angle) && (angle <= 90)) { return true; }
if ((angle < 180) && (angle >= 90)) {return true;}
return false;
}

//**********************************************
//Tutaj s obliczane kty
//dla kadej linii wychodzcej ze rodka do wierzchoka
//**********************************************
private float[] getAngleArrays() {
float[] angleArray = new float[sides];
float commonAngle = 360.0f/sides;
float halfAngle = commonAngle/2.0f;
float firstAngle = 360.0f - (90+halfAngle);
angleArray[0] = firstAngle;
float curAngle = firstAngle;
for(int i=1;i<sides;i++)
{
float newAngle = curAngle - commonAngle;
angleArray[i] = newAngle;
curAngle = newAngle;
}
printArray(angleArray, "angleArray");
return angleArray;
}

//**********************************************
//Opcjonalne zaokrglanie
//**********************************************
private float getApproxValue(float f) {
return (Math.abs(f) < 0.001) ? 0 : f;
}

//**********************************************
//Zwraca liczb potrzebnych indeksw
//na podstawie liczby bokw
//Jest to liczba trjktw potrzebnych do
//pomnoenia wielokta przez warto 3
//Tak si skada, e liczba trjktw jest
// rwna liczbie bokw
//**********************************************
public int getNumberOfIndices() {

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

689

return sides * 3;
}
public static void test() {
RegularPolygon triangle = new RegularPolygon(0,0,0,1,3);
}
private void printArray(float array[], String tag) {
StringBuilder sb = new StringBuilder(tag);
for(int i=0;i<array.length;i++) {
sb.append(";").append(array[i]);
}
Log.d("hh",sb.toString());
}
private void printShortArray(short array[], String tag) {
StringBuilder sb = new StringBuilder(tag);
for(int i=0;i<array.length;i++) {
sb.append(";").append(array[i]);
}
Log.d(tag,sb.toString());
}
}

Poniej przedstawiamy najwaniejsze elementy kodu:


Constructor konstruktor klasy RegularPolygon jako dane wejciowe pobiera
wsprzdne rodka, promie i liczb bokw.
getAngleArrays jest to kluczowa metoda, obliczajca kty pomidzy bokami
wielokta foremnego przy zaoeniu, e jeden z jego bokw jest pooony rwnolegle
do osi x.
getXMultiplierArray, getYMultiplierArray metody te pobieraj kty od metody
getAngleArrays i rzutuj je na osie x oraz y w celu uzyskania odpowiednich
wsprzdnych przy zaoeniu, e dugo boku wynosi 1 jednostk dugoci.
calcArrays metoda ta wykorzystuje metody getXMultiplierArray
i getYMultiplierArray do pobrania wierzchokw i dopasowania ich do okrelonego
promienia i rodka wieloboku. Po zakoczeniu pracy tej metody obiekt
RegularPolygon bdzie posiada odpowiednie wsprzdne, lecz w formacie
zmiennoprzecinkowych macierzy Java.
getVertexBuffer ta metoda pobiera macierze wsprzdnych Java i przetwarza je
na bufory nio, wymagane przez metody rysowania w bibliotece OpenGL.
getIndexBuffer metoda ta pobiera pogrupowane wierzchoki i umieszcza je
w takiej kolejnoci, e kady trjkt bdzie czci tworzonego wieloboku.
Pozostae metody, obsugujce tekstury, s wykorzystywane wedug bardzo podobnego algorytmu i stan si jeszcze bardziej zrozumiae po przeczytaniu punktu powiconego teksturom.

Renderowanie kwadratu za pomoc klasy RegularPolygon


Skoro ju poznalimy podstawowe bloki budulcowe, zobaczmy, w jaki sposb moemy narysowa kwadrat za pomoc czterobocznego obiektu klasy RegularPolygon. Listing 20.27
przedstawia kod klasy SquareRenderer.

690 Android 3. Tworzenie aplikacji


Listing 20.27. Silnik SquareRenderer
public class SquareRenderer extends AbstractRenderer
{

//Nieskompresowany bufor natywny, przechowujcy wsprzdne punktw


private FloatBuffer mFVertexBuffer;

//Nieskompresowany bufor natywny, przechowujcy indeksy


//umoliwiajce wielokrotne wykorzystywanie punktw
private ShortBuffer mIndexBuffer;
private int numOfIndices = 0;
private int sides = 4;
public SquareRenderer(Context context)
{
prepareBuffers(sides);
}
private void prepareBuffers(int sides)
{
RegularPolygon t = new RegularPolygon(0,0,0,0.5f,sides);
//RegularPolygon t = new RegularPolygon(1,1,0,1,sides);
this.mFVertexBuffer = t.getVertexBuffer();
this.mIndexBuffer = t.getIndexBuffer();
this.numOfIndices = t.getNumberOfIndices();
this.mFVertexBuffer.position(0);
this.mIndexBuffer.position(0);
}

//przesonita metoda

protected void draw(GL10 gl)


{
prepareBuffers(sides);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, this.numOfIndices,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
}

Ten kod powinien by cakowicie zrozumiay. Pochodzi z klasy AbstractRenderer (listing


20.9), przesoni metod draw i wykorzysta klas RegularPolygon do narysowania kwadratu.
Po utworzeniu tego silnika renderujcego moemy doda instrukcj warunkow if (listing
20.28) do klasy MultiViewTestHarness, zaprezentowanej na listingu 20.12.
Listing 20.28. Zastosowanie klasy SimpleRectangleRenderer
if (mid == R.id.mid_square_polygon)
{
mTestHarness.setRenderer(new SquareRenderer(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setContentView(mTestHarness);
return;
}

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

691

Po dodaniu powyszego fragmentu kodu moemy ponownie uruchomi program i wybra element menu Kwadrat, aby ujrze figur przedstawion na rysunku 20.8.

Rysunek 20.8. Kwadrat narysowany za pomoc klasy RegularPolygon

Animowanie obiektw RegularPolygon


Po zaprezentowaniu oglnej zasady rysowania ksztatw za pomoc klasy RegularPolygon
przejdmy do bardziej skomplikowanych kwestii. Sprawdmy, czy moemy utworzy animacj,
ktra rozpoczyna si od narysowania trjkta, a koczy si po przeksztaceniu go na okrg. W tym
celu najpierw narysujemy wielokt, ktremu co cztery sekundy bd dodawane kolejne boki.
Odpowiedni kod znajdziemy na listingu 20.29).
Listing 20.29. Klasa PolygonRenderer
public class PolygonRenderer extends AbstractRenderer
{

//Liczba wykorzystanych punktw lub wierzchokw


private final static int VERTS = 4;

//Nieskompresowany bufor natywny, przechowujcy wsprzdne punktw


private FloatBuffer mFVertexBuffer;

//Nieskompresowany bufor natywny, przechowujcy indeksy


//umoliwiajce wielokrotne wykorzystywanie punktw
private ShortBuffer mIndexBuffer;
private int numOfIndices = 0;
private long prevtime = SystemClock.uptimeMillis();
private int sides = 3;

692 Android 3. Tworzenie aplikacji


public PolygonRenderer(Context context)
{
prepareBuffers(sides);
}
private void prepareBuffers(int sides)
{
RegularPolygon t = new RegularPolygon(0,0,0,1,sides);
this.mFVertexBuffer = t.getVertexBuffer();
this.mIndexBuffer = t.getIndexBuffer();
this.numOfIndices = t.getNumberOfIndices();
this.mFVertexBuffer.position(0);
this.mIndexBuffer.position(0);
}

//przesonita metoda
protected void draw(GL10 gl)
{
long curtime = SystemClock.uptimeMillis();
if ((curtime - prevtime) > 2000)
{
prevtime = curtime;
sides += 1;
if (sides > 20)
{
sides = 3;
}
this.prepareBuffers(sides);
}
gl.glColor4f(1.0f, 0, 0, 0.5f);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, this.numOfIndices,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
}
}

Powyszy kod jedynie zmienia warto zmiennej sides co cztery sekundy. Animacja powstaje
dziki temu, e klasa Renderer jest rejestrowana z widokiem powierzchni.
Skoro ju posiadamy t klas renderujc, musimy doda kod widoczny na listingu 20.30 do
klasy MultiViewTestHarness.
Listing 20.30. Element menu umoliwiajcy testowanie wielokta
if (mid == R.id.mid_polygon)
{
mTestHarness.setRenderer(new PolygonRenderer(this));
setContentView(mTestHarness);
return;
}

Jeeli ponownie uruchomimy nasz program i wybierzemy opcj Wielokt, ujrzymy zbir przeksztacajcych si figur geometrycznych, ktrych liczba wierzchokw bdzie ustawicznie rosa.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

693

Warto si jednak przyjrze, w jaki sposb wielokty ulegaj przeksztaceniom. Na rysunku 20.9
zosta pokazany pocztek cyklu przeksztacania wielokta.

Rysunek 20.9. Pocztek cyklu rysowania wielokta

Na rysunku 20.10 wida koniec cyklu transformacji wielokta.

Rysunek 20.10. Koo narysowane za pomoc klasy RegularPolygon

Moemy rozwin koncepcj ksztatw na bardziej zoone figury geometryczne, a nawet graf
sceny, skadajcy si z duej liczby innych obiektw, ktre mog zosta zdefiniowane w jzyku
XML, a nastpnie renderowane w rodowisku OpenGL poprzez te wanie ksztaty.

694 Android 3. Tworzenie aplikacji


Zajmijmy si teraz teksturami, aby dowiedzie si, w jaki sposb powiza obrazy z takimi
powierzchniami, jak kwadraty i wielokty.

Praca z teksturami
Jednym z podstawowych poj stosowanych w terminologii OpenGL s tekstury. W rodowisku
OpenGL wi si one z wieloma niuansami. Omwimy tu jedynie podstawowe koncepcje,
umoliwiajce rozpoczcie pracy z teksturami w rodowisku OpenGL. W celu pogbienia wiedzy
na temat tekstur mona skorzysta z listy zasobw umieszczonej w kocowej czci rozdziau.

Tekstury
Tekstur OpenGL nazywamy map bitow umieszczan na danej powierzchni w rodowisku
OpenGL (w tym rozdziale zajmujemy si jedynie powierzchniami). Moemy na przykad uy
obrazu znaczka pocztowego i umieci go na powierzchni kwadratu, dziki czemu uzyskamy
obraz znaczka pocztowego. Moemy take wykorzysta map bitow przedstawiajc wizerunek cegy, umieci j na powierzchni prostokta i poprzez powielanie takich obrazw cegy
utworzy obraz muru.
Proces przyczania mapy bitowej tekstury do powierzchni w rodowisku OpenGL przypomina
proces naklejania fragmentu tapety (kwadratowej) na boku obiektu posiadajcego regularny
lub nieregularny ksztat. Ksztat powierzchni nie ma znaczenia, dopki wymiary papieru
umoliwiaj cakowite pokrycie powierzchni.
Aby jednak umieci papier w odpowiedniej orientacji, pozwalajcej na waciwe uformowanie
obrazu, musimy pobra kady wierzchoek ksztatu i dokadnie zaznaczy go na tapecie, co
spowoduje idealne dopasowanie tapety do ksztatu obiektu. Jeeli mamy do czynienia z niestandardowym ksztatem posiadajcym wiele wierzchokw, kady z nich musi zosta zaznaczony
na tapecie.
Mona to sobie wyobrazi rwnie w inny sposb: kadziemy obiekt na ziemi, przedni cian
skierowan do gry, rozkadamy na tej cianie tapet i obracamy j, dopki nie zostanie zorientowana we waciwym kierunku. Teraz dziurkami zaznaczamy na tapecie kady wierzchoek ksztatu. cigamy tapet, sprawdzamy pooenie wierzchokw i zapisujemy ich wsprzdne, przy zaoeniu, e tapeta jest wykalibrowana. S to tak zwane wsprzdne tekstury.

Znormalizowane wsprzdne tekstury


Jednym z nierozwizanych i niezdefiniowanych szczegw s rozmiary obiektu i tapety.
W rodowisku OpenGL rozwizuje si ten problem za pomoc normalizacji. Tekstura jest tu
zawsze kwadratem o wymiarach 11, ktrego pocztek posiada wsprzdne (0, 0), prawy grny
rg (1, 1). Nastpnie naley zmniejszy powierzchni obiektu, tak aby miecia si w tych
wymiarach 11. Zatem zadaniem programisty jest okrelenie wierzchokw powierzchni
obiektu o rozmiarach 11.
W projekcie demonstrujcym klas RegularPolygon z listingu 20.26 w podobny sposb rysowalimy wielokt za pomoc okrgu o promieniu 1. Nastpnie okrelalimy pooenie kadego
wierzchoka. Gdybymy zaoyli, e okrg zajmuje powierzchni 11 kwadratu, to ten kwadrat
mgby by nasz tapet. Zatem okrelenie wsprzdnych tekstury jest analogiczn czynnoci
do procesu wyznaczania wsprzdnych wierzchokw wielokta. Dlatego na listingu 20.26
znalaza si nastpujca funkcja, obliczajca wsprzdne tekstury:

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

695

calcTextureArray()
getTextureBuffer()

Jeeli przyjrzymy si uwaniej, zauwaymy, e wszystkie pozostae funkcje s wspdzielone


pomidzy metodami calcTextureArray i calcArray. Taka wsplno pomidzy wsprzdnymi wierzchokw i wsprzdnymi tekstur stanowi wany wniosek podczas nauki obsugi
rodowiska OpenGL.

Analiza procesu standardowej obsugi tekstury


Po zrozumieniu zwizku pomidzy wsprzdnymi tekstury i wsprzdnymi wierzchokw
oraz po okreleniu wsprzdnych mapy tekstury reszta czynnoci jest ju wystarczajco prosta
(w rodowisku OpenGL nic nie moe by wyranie uznane za cakiem proste!). Kolejnymi
etapami s wczytanie mapy bitowej tekstury do pamici i przydzielenie identyfikatora, umoliwiajcego wielokrotne jej uytkowanie. Nastpnie wykorzystujemy mechanizm ustanawiania
biecej tekstury za pomoc jej identyfikatora, co pozwala na jednoczesne wczytanie wielu tekstur. W potoku rysowania okrelamy wsprzdne tekstury oraz wsprzdne rysowania. Na
koniec pozostaje sam proces rysowania.
Poniewa proces wczytywania tekstur jest dosy powszechnie stosowany, wydzielilimy go
poprzez utworzenie abstrakcyjnej klasy AbstractSingleTextureRenderer, wywodzcej si
z klasy AbstractRenderer.
Na listingu 20.31 zosta umieszczony kod rdowy pozwalajcy na pen konfiguracj pojedynczej tekstury.
Listing 20.31. Wydzielanie obsugi procesu nakadania pojedynczej tekstury
public abstract class AbstractSingleTexturedRenderer
extends AbstractRenderer
{
int mTextureID;
int mImageResourceId;
Context mContext;
public AbstractSingleTexturedRenderer(Context ctx,
int imageResourceId) {
mImageResourceId = imageResourceId;
mContext = ctx;
}
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
super.onSurfaceCreated(gl, eglConfig);
gl.glEnable(GL10.GL_TEXTURE_2D);
prepareTexture(gl);
}
private void prepareTexture(GL10 gl)
{
int[] textures = new int[1];
gl.glGenTextures(1, textures, 0);
mTextureID = textures[0];
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_NEAREST);

696 Android 3. Tworzenie aplikacji


gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
GL10.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_CLAMP_TO_EDGE);
gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
GL10.GL_REPLACE);
InputStream is = mContext.getResources()
.openRawResource(this.mImageResourceId);
Bitmap bitmap;
try {
bitmap = BitmapFactory.decodeStream(is);
} finally {
try {
is.close();
} catch(IOException e) {

// Ignorujemy.
}
}
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
}
public void onDrawFrame(GL10 gl)
{
gl.glDisable(GL10.GL_DITHER);
gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
GL10.GL_MODULATE);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glActiveTexture(GL10.GL_TEXTURE0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
GL10.GL_REPEAT);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_REPEAT);
draw(gl);
}
}

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

697

W powyszym kodzie pojedyncza tekstura (mapa bitowa) zostaa wczytana i przygotowana


w metodzie onSurfaceCreated. Tak samo jak w przypadku klasy AbstractRenderer, metoda
onDrawFrame konfiguruje wymiary naszej przestrzeni rysowania, dziki czemu wsprzdne
nabieraj sensu. W zalenoci od sytuacji moemy zmienia ten kod, aby uzyska optymaln
objto widzenia.
Spjrzmy take, w jaki sposb konstruktor przyjmuje map bitow tekstury i przygotowuje j
do pniejszego uytku. W zalenoci od liczby dostpnych tekstur moemy odpowiednio
projektowa abstrakcyjne klasy.
Jak zostao pokazane na listingu 20.31, do przetwarzania tekstur wymagane s specyficzne interfejsy API:
glGenTextures ta metoda tworzy niepowtarzalny identyfikator tekstur, za pomoc
ktrego mona uzyskiwa do nich odniesienia. Po wczytaniu mapy bitowej tekstury
za pomoc narzdzia GLUtils.texImage2D powiemy t tekstur z okrelonym
identyfikatorem. Dopki tekstura nie zostanie powizana z identyfikatorem
wygenerowanym przez metod glGenTextures, jest on jedynie cigiem znakw.
W literaturze dotyczcej standardu OpenGL identyfikatory te s okrelane jako
nazwy tekstur.
glBindTexture ta metoda jest stosowana do wizania biecej tekstury
z identyfikatorem uzyskanym z metody glGenTextures.
glTexParameter podczas wstawiania tekstury mona wykorzysta wiele opcjonalnych
parametrw. Omawiany interfejs pozwala na ich zdefiniowanie. Za przykady mog
posuy parametry GL_REPEAT, GL_CLAMP i tak dalej. Parametr GL_REPEAT suy do
wielokrotnego powielania mapy bitowej, w przypadku gdy obiekt jest duo wikszy.
Pen list dostpnych parametrw mona znale pod adresem:
www.khronos.org/opengles/documentation/opengles1_0/html/glTexParameter.html.
glTexEnv niektre opcje zwizane z teksturami s dostpne w metodzie
glTexEnv. Wrd wartoci mona znale GL_DECAL, GL_MODULATE, GL_BLEND,
GL_REPLACE i tak dalej. Na przykad w przypadku parametru GL_DECAL tekstura
pokrywa obiekt. Jak sama nazwa wskazuje, metoda GL_MODULATE moduluje kolory,
zamiast je zamienia. Pen list opcji dostpnych w tym interfejsie API mona
znale pod nastpujcym adresem:
www.khronos.org/opengles/documentation/opengles1_0/html/glTexEnv.html.
GLUtils.texImage2D jest to interfejs API Androida, pozwalajcy na wczytanie
mapy bitowej penicej rol tekstury. Interfejs ten wywouje wewntrznie metod
glTexImage2D.
glActiveTexture ustanawia identyfikator danej tekstury jako aktywn struktur.
glTexCoordPointer ta metoda rodowiska OpenGL jest uywana do okrelania
wsprzdnych tekstury. Kada wsprzdna musi pasowa do wsprzdnej
zdefiniowanej w metodzie glVertexPointer.
Wikszo informacji na temat wymienionych interfejsw API mona znale w dokumentacji
rodowiska OpenGL ES dostpnej pod adresem:
www.khronos.org/opengles/documentation/opengles1_0/html/index.html

698 Android 3. Tworzenie aplikacji

Rysowanie za pomoc tekstur


Po wczytaniu mapy bitowej i skonfigurowaniu jej jako tekstury powinnimy mie moliwo
zastosowania klasy RegularPolygon i wykorzysta wsplnie wsprzdne tekstury ze wsprzdnymi wierzchokw do narysowania wieloboku foremnego pokrytego tekstur. Listing 20.32
prezentuje rzeczywist klas rysujc teksturowany kwadrat.
Listing 20.32. Klasa TexturedSquareRenderer
public class TexturedSquareRenderer extends AbstractSingleTexturedRenderer
{

//Liczba wykorzystywanych punktw lub wierzchokw


private final static int VERTS = 4;

//Nieskompresowany bufor natywny, przechowujcy wsprzdne punktw


private FloatBuffer mFVertexBuffer;

//Nieskompresowany bufor natywny, przechowujcy wsprzdne punktw


private FloatBuffer mFTextureBuffer;

// Nieskompresowany bufor natywny, przechowujcy indeksy


//pozwalajce na wielokrotne wykorzystywanie punktw
private ShortBuffer mIndexBuffer;
private int numOfIndices = 0;
private int sides = 4;
public TexturedSquareRenderer(Context context)
{
super(context,com.androidbook.OpenGL.R.drawable.robot);
prepareBuffers(sides);
}
private void prepareBuffers(int sides)
{
RegularPolygon t = new RegularPolygon(0,0,0,0.5f,sides);
this.mFVertexBuffer = t.getVertexBuffer();
this.mFTextureBuffer = t.getTextureBuffer();
this.mIndexBuffer = t.getIndexBuffer();
this.numOfIndices = t.getNumberOfIndices();
this.mFVertexBuffer.position(0);
this.mIndexBuffer.position(0);
this.mFTextureBuffer.position(0);
}

//przesonita metoda
protected void draw(GL10 gl)
{
prepareBuffers(sides);
gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mFTextureBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, this.numOfIndices,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
}
}

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

699

Jak wida, wikszo pracy wykonuje abstrakcyjna klasa silnika renderowania tekstury i obiekt
RegularPolygon, obliczajcy wierzchoki odwzorowania tekstury (listing 20.26).
Po utworzeniu tej klasy renderujcej, aby przetestowa teksturowany kwadrat, musimy doda
kod z listingu 20.33 do aktywnoci MultiViewTestHarness z listingu 20.12.
Listing 20.33. Odpowied na element menu Teksturowany kwadrat
if (mid == R.id.mid_textured_square)
{
mTestHarness.setRenderer(new TexturedSquareRenderer(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setContentView(mTestHarness);
return;
}

Ponowne uruchomienie programu i wybranie elementu menu Teksturowany kwadrat spowoduje wywietlenie figury geometrycznej widocznej na rysunku 20.11.

Rysunek 20.11. Teksturowany kwadrat

Rysowanie wielu figur geometrycznych


Wszystkie przykadowe projekty omwione w tym rozdziale byy do siebie podobne: rysowalimy prost figur geometryczn za pomoc standardowego wzorca. Wzorzec ten wyglda nastpujco: konfigurowanie wierzchokw, wczytanie tekstury, skonfigurowanie jej wsprzdnych,
rysowanie pojedynczego obiektu. A jak postpi w przypadku, gdy chcemy narysowa dwie
figury geometryczne? Co zrobi, jeli chcemy narysowa trjkt tradycyjnym sposobem
definiowania wierzchokw, a nastpnie utworzy wielokt za pomoc ksztatu, na przykad RegularPolygon? W jaki sposb powiemy ze sob wierzchoki zdefiniowane dwoma
rnymi sposobami? Czy musimy jednorazowo okrela wierzchoki dla obydwu obiektw,
a nastpnie wywoywa metod rysowania?

700 Android 3. Tworzenie aplikacji


Okazuje si, e pomidzy dwoma wywoaniami metody draw() w interfejsie silnika renderowania rodowisko OpenGL umoliwia wstawienie wielu metod glDraw. Pomidzy wywoaniami
tych metod moemy okrela nowe tekstury i wierzchoki. Wyniki otrzymywane za pomoc tych
wszystkich metod zostan odzwierciedlone na ekranie po zakoczeniu dziaania metody draw().
Moemy skorzysta rwnie z innej sztuczki rodowiska OpenGL pozwalajcej na rysowanie
wielu obiektw. Zastanwmy si nad dotychczas tworzonymi wieloktami. Mog by wywietlane w dowolnym punkcie pocztkowym po okreleniu wsprzdnych tego punktu jako parametru rysowanych figur. W rodowisku OpenGL jest to dokonywane natywnie, co pozwala
nam zawsze definiowa obiekt RegularPolygon w punkcie (0, 0, 0) i wykorzysta mechanizm
translacji w rodowisku OpenGL, dziki ktremu punkt pocztkowy mona przesun na
dan pozycj. Tak sam czynno moemy przeprowadzi dla kolejnego wielokta i przesun go na inn pozycj, w wyniku czego zostan narysowane dwa wielokty w dwch rnych
miejscach ekranu.
Na listingu 20.34 pokazano kod ilustrujcy te koncepcje poprzez wielokrotne narysowanie teksturowanego wielokta.
Listing 20.34. Silnik renderujcy teksturowany wielobok
public class TexturedPolygonRenderer extends AbstractSingleTexturedRenderer
{

//Liczba wykorzystywanych punktw lub wierzchokw


private final static int VERTS = 4;

// Nieskompresowany bufor natywny, przechowujcy wsprzdne punktw


private FloatBuffer mFVertexBuffer;

// Nieskompresowany bufor natywny, przechowujcy wsprzdne punktw


private FloatBuffer mFTextureBuffer;

// Nieskompresowany bufor natywny, przechowujcy indeksy


//pozwalajce na wielokrotne wykorzystywanie punktw
private ShortBuffer mIndexBuffer;
private int numOfIndices = 0;
private long prevtime = SystemClock.uptimeMillis();
private int sides = 3;
public TexturedPolygonRenderer(Context context)
{
super(context,com.ai.android.OpenGL.R.drawable.robot);
prepareBuffers(sides);
}
private void prepareBuffers(int sides)
{
RegularPolygon t = new RegularPolygon(0,0,0,0.5f,sides);
this.mFVertexBuffer = t.getVertexBuffer();
this.mFTextureBuffer = t.getTextureBuffer();
this.mIndexBuffer = t.getIndexBuffer();
this.numOfIndices = t.getNumberOfIndices();
this.mFVertexBuffer.position(0);

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

701

this.mIndexBuffer.position(0);
this.mFTextureBuffer.position(0);
}

//przesonita metoda
protected void draw(GL10 gl)
{
long curtime = SystemClock.uptimeMillis();
if ((curtime - prevtime) > 2000)
{
prevtime = curtime;
sides += 1;
if (sides > 20)
{
sides = 3;
}
this.prepareBuffers(sides);
}
gl.glEnable(GL10.GL_TEXTURE_2D);

//Rysuje jednokrotnie po lewej stronie


gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mFTextureBuffer);
gl.glPushMatrix();
gl.glScalef(0.5f, 0.5f, 1.0f);
gl.glTranslatef(0.5f,0, 0);
gl.glDrawElements(GL10.GL_TRIANGLES, this.numOfIndices,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);

//Rysuje ponownie po stronie prawej


gl.glPopMatrix();
gl.glPushMatrix();
gl.glScalef(0.5f, 0.5f, 1.0f);
gl.glTranslatef(-0.5f,0, 0);
gl.glDrawElements(GL10.GL_TRIANGLES, this.numOfIndices,
GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
gl.glPopMatrix();
}
}

W tym przykadowym kodzie ukazalimy nastpujce koncepcje:


rysowanie za pomoc ksztatw,
rysowanie wielu ksztatw za pomoc macierzy transformacji,
wprowadzanie tekstur,
animacje.
Gwny kod na listingu 20.34, umoliwiajcy wielokrotne rysowanie, znajduje si w metodzie
draw(). Pogrubion czcionk zaznaczylimy odpowiednie wiersze. Zauwamy, e wewntrz
metody draw() wywoalimy dwukrotnie metod glDrawElements. Za kadym razem konfigu-

rujemy proste obiekty rysowane, niezalene od siebie.

702 Android 3. Tworzenie aplikacji


Wyjanijmy jeszcze zastosowanie macierzy transformacji. Za wywoaniem metody glDraw
Elements() korzysta ona ze specyficznej macierzy transformacji. Gdybymy chcieli wykorzysta t macierz do zmiany pooenia figury geometrycznej (lub innego jej aspektu), musielibymy przywrci jej pierwotne ustawienia, aby nastpny obiekt mg zosta poprawnie narysowany. Dokonujemy tego poprzez operacje PUSH i POP dostpne na matrycach rodowiska OpenGL.
Po utworzeniu tej klasy renderujcej musimy doda kod z listingu 20.35 do aktywnoci Multi
ViewTestHarness z listingu 20.12, aby mc przetestowa rysowanie wielu figur geometrycznych.
Listing 20.33. Odpowied na element menu Wiele figur
if (mid == R.id.mid_multiple_figures)
{
mTestHarness.setRenderer(new TexturedPolygonRenderer(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
setContentView(mTestHarness);
return;
}

Ponowne uruchomienie programu i wybranie elementu menu Wiele figur spowoduje narysowanie na pocztku animacji dwch zestaww zmieniajcych si wieloktw (widocznych na rysunku 20.12). Zwrmy uwag, e zosta zdefiniowany cigy tryb renderowania.

Rysunek 20.12. Para teksturowanych wieloktw

Na rysunku 20.13 zosta ukazany ten sam przykad w poowie procesu animacji.
Na tym zakoczymy omawianie kolejnego istotnego aspektu rodowiska OpenGL. W tym
podrozdziale pokazalimy, w jaki sposb mona zebra wiele rnych figur geometrycznych lub
scen i narysowa je razem, dziki czemu mona otrzyma dosy zoon sceneri w rodowisku OpenGL.
Zajmiemy si teraz obsug rodowiska OpenGL ES 2.0 w Androidzie.

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

703

Rysunek 20.13. Para teksturowanych k

OpenGL ES 2.0
Dobre wieci s takie, e oprcz samej obsugi rodowiska OpenGL ES 2.0 system Android od
wersji 2.2 (poziom 8. interfejsw API) posiada take odpowiednie powizania jzyka Java.
Musimy jednak pamita o nastpujcych ograniczeniach:
rodowisko OpenGL ES 2.0 nie jest jeszcze obsugiwane przez emulator.
rodowisko OpenGL ES 2.0 rni si znacznie od poprzedniej wersji, wic wikszo
ksiek powiconych rodowisku OpenGL posiada nowe wydania, w ktrych omwiono
ten aspekt. Programowanie w OpenGL odbywa si w obrbie jednostki GPU, co powoduje,
e emulowanie takiego kodu staje si skomplikowan czynnoci. Z tego powodu nie
jest nawet pewne, czy emulator bdzie kiedykolwiek obsugiwa rodowisko OpenGL
ES 2.0.
Jedynym sposobem testowania czy te poznawania rodowiska OpenGL ES 2.0
w zestawie Android SDK jest wykorzystanie fizycznego urzdzenia. Wkrtce wikszo
urzdze bdzie pracowaa pod kontrol wersji 2.2 Androida, jednak istnieje pewne
prawdopodobiestwo, e cz z nich nie bdzie obsugiwaa nowej wersji rodowiska
OpenGL.
rodowisko OpenGL ES 2.0 jest znaczco odmienne od wersji 1.x. Dodatkowe komplikacje
wynikaj z braku wstecznej kompatybilnoci. Pocztkujcym programistom najwicej problemu sprawi jego inicjalizowanie oraz nauka rysowania najprostszych obiektw.
Dokadna analiza rodowiska OpenGL ES 2.0 wymaga skrupulatnego przeczytania wielu stron
rnorodnych informacji. Zamiast tego zapoznamy Czytelnika z tematem w stopniu umoliwiajcym korzystanie z tego rodowiska. Po utworzeniu podstawowego rodowiska testowego
Czytelnik bdzie mg skorzysta z odnonikw umieszczonych na kocu rozdziau, w ktrych
znajdzie informacje pozwalajce na wdroenie rodowiska OpenGL ES 2.0 do struktury aplikacji.

704 Android 3. Tworzenie aplikacji


Potga bibliotek OpenGL ES 2.0 polega na moliwoci pisania dla jednostki graficznej programw, ktre s kompilowane na bieco, w czasie dziaania, i ktre pozwalaj na interpretowanie
rysowanych wierzchokw i fragmentw. Nosz one nazw jednostek cieniujcych (ang. shader).
Niestety, wspomniane fragmenty kodu s wymagane nawet w przypadku najprostszych programw rodowiska OpenGL ES 2.0. Wynika z tego, e zapoznanie si z pojciem jednostek cieniujcych jest niezbdne do korzystania z bibliotek OpenGL ES 2.0.
Pomog nam w tym rnorodne rda zamieszczone na kocu rozdziau.

Powizania rodowiska Java z bibliotekami OpenGL ES 2.0


Powizania rodowiska Java z tym interfejsem API s dostpne w pakiecie android.opengl.
GLES20. Wszystkie funkcje tej klasy s statyczne i posiadaj swoje odpowiedniki w okrelonych interfejsach API jzyka C, zdefiniowanych w specyfikacji grupy Khronos (adres URL
znajdziemy na kocu rozdziau).
Omwione w tym rozdziale klasa GLSurfaceView oraz odpowiadajca jej klasa abstrakcyjna
uywane w bibliotekach OpenGL ES 1.0, znajduj rwnie zastosowanie w wersji
2.0. Wkrtce omwimy to zagadnienie. Wicej informacji na ten temat znajdziemy rwnie
w dokumentacji interfejsu API, w punkcie dotyczcym funkcji GLSurfaceView.setEGL
ContextClientVersion.
Renderer,

Sprawdmy najpierw za pomoc kodu zawartego na listingu 20.36, czy dane urzdzenie lub
emulator obsuguje wersj 2.0 rodowiska OpenGL ES.
Listing 20.36. Wykrywanie dostpnoci rodowiska OpenGL ES 2.0
private boolean detectOpenGLES20() {
ActivityManager am =
(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo info = am.getDeviceConfigurationInfo();
return (info.reqGlEsVersion >= 0x20000);
}

Po wprowadzeniu tej funkcji (detectOpenGLES20) moemy zacz korzysta w aktywnoci


z klasy GLSurfaceView, tak jak zostao to ukazane na listingu 20.37.
Listing 20.37. Korzystanie z klasy GLSurfaceView w rodowisku OpenGL ES 2.0
if (detectOpenGLES20())
{
GLSurfaceView glview = new GLSurfaceView(this);

// glview.setEGLConfigChooser(false);
glview.setEGLContextClientVersion(2);
glview.setRenderer(new YourGLES20Renderer(this));
glview.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setContentView(glview);
}

Zwrmy uwag, e klasa SurfaceView zostaa skonfigurowana do obsugi nowej wersji


bibliotek OpenGL poprzez ustanowienie wersji 2 klienta. Tworzona tu klasa TwojaKlasa

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

705

RenderujcaGLES bdzie podobna do omawianych wczeniej klas typu Renderer. Jednak


w ciele klasy renderujcej bdziemy korzysta z interfejsw GLES20, a nie GL10.

W tworzonym przez nas przykadzie klasa renderujca bdzie nosia nazw ES20Simple
TriangleRenderer. Wkrtce zajmiemy si jej omwieniem, jednak tymczasem przyjrzyjmy si aktywnoci z listingu 20.38, do ktrej wstawilimy fragmenty kodw z listingw
20.36 oraz 20 37.
Listing 20.38. Aktywno OpenGL20MultiViewTestHarness
public class OpenGL20MultiViewTestHarnessActivity extends Activity
{
final String tag="es20";
private GLSurfaceView mTestHarness;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (detectOpenGLES20())
{
mTestHarness = new GLSurfaceView(this);

//NIE wywoujmy poniszej funkcji


//mTestHarness.setEGLConfigChooser(false);
mTestHarness.setEGLContextClientVersion(2);
}
else
{
throw new RuntimeException("wersja 2.0 nieobslugiwana");
}
Intent intent = getIntent();
int mid = intent.getIntExtra("com.ai.menuid", R.id.MenuId_OpenGL15_Current);
if (mid == R.id.mid_es20_triangle)
{
mTestHarness.setRenderer(new ES20SimpleTriangleRenderer(this));
mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setContentView(mTestHarness);
return;
}
return;
}
private boolean detectOpenGLES20() {
ActivityManager am =
(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo info = am.getDeviceConfigurationInfo();
return (info.reqGlEsVersion >= 0x20000);
}
@Override
protected void onResume() {
super.onResume();
mTestHarness.onResume();
}
@Override
protected void onPause() {
super.onPause();

706 Android 3. Tworzenie aplikacji


mTestHarness.onPause();
}
}

Zaprezentowana na listingu 20.38 aktywno rodowiska testowego dla biblioteki OpenGL ES


2.0 bardzo przypomina aktywno przeznaczon dla wersji 1.0 OpenGL ES z listingu 20.12.
Czytelnik zastanawia si zapewne, czy nie mona by po prostu uy tamtej wczeniejszej
aktywnoci i po prostu doda kolejn opcj menu. Do wybrania prezentowanego rozwizania skoniy nas dwa powody.
Po pierwsze, nie mamy pewnoci, czy moemy wykorzystywa t sam klas SurfaceView pomidzy wywoaniami rnych wersji bibliotek OpenGL ES. Przezorny zawsze ubezpieczony.
Drugi powd wynika z rnic w inicjalizowaniu obydwu bibliotek nie chcemy komplikowa
kodu poprzez umieszczanie dwch rnych rozwiza wewntrz jednej klasy. Przykadowo
podczas inicjalizacji rodowiska OpenGL ES 2.0 sprawdzamy dostpno obsugiwanej wersji
bibliotek itd.; tego typu kod mgby zaburzy dziaanie prostszego systemu inicjalizacji rodowiska OpenGL ES 1.0, widocznego na listingu 20.12.
Tak czy inaczej, motywy kierujce nami podczas tworzenia rodowiska testowego dla nowej
wersji biblioteki OpenGL s takie same jak poprzednio.
Aby mc korzysta z funkcji rodowiska OpenGL ES 2.0 w aktywnociach, takich jak zaprezentowana na listingu 20.38, musimy wprowadzi znacznik <uses-feature> wewntrz wza aplikacji (listing 20.39).
Listing 20.39. Korzystanie z funkcji rodowiska OpenGL ES 2.0
<application>
inne wzy
<uses-feature android:glEsVersion="0x00020000" />
</application>

Nasza aplikacja musi posiada wczony tryb debugowania za pomoc odpowiedniego atrybutu
w wle aplikacji, poniewa bdziemy mogli testowa aplikacje wykorzystujce rodowisko
OpenGL ES 2.0 wycznie na urzdzeniach fizycznych (listing 20.40).
Listing 20.40. Definiowanie aplikacji wczonej w trybie debugowania
<application android:icon="@drawable/icon"
android:label="rodowisko testowe OpenGL"
android:debuggable="true">

W celu przywoania nowej aktywnoci rodowiska testowego musimy zmieni aktywno sterujc z listingu 20.14 w taki sposb, aby wygldaa jak na listingu 20.41.
Listing 20.41. Nowa gwna aktywno sterujca
public class TestOpenGLMainDriverActivity extends Activity {

/** Wywoywana podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

707

super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu){
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater(); //z aktywnoci
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId() >= R.id.mid_es20_triangle)
{
this.invoke20MultiView(item.getItemId());
return true;
}
this.invokeMultiView(item.getItemId());
return true;
}
private void invokeMultiView(int mid)
{
Intent intent = new Intent(this,MultiViewTestHarnessActivity.class);
intent.putExtra("com.ai.menuid", mid);
startActivity(intent);
}
private void invoke20MultiView(int mid)
{
Intent intent = new Intent(this,OpenGL20MultiViewTestHarnessActivity.class);
intent.putExtra("com.ai.menuid", mid);
startActivity(intent);
}
}

Na listingu 20.41 wstawilimy dwa dodatkowe elementy: metod wywoujc aktywno


OpenGL20MultiViewTestHarnessActivity, ktra zostanie przywoana, gdy identyfikator
menu znajdzie si powyej lub na rwni wartoci elementu mid_es20_triangle. Nasz pomys
polega na tym, e ten element bdzie uruchamia wersje demonstracyjne funkcji rodowiska
OpenGL ES 2.0. Tym razem jednak korzystamy tylko z jednego przykadu.

Etapy renderowania
Renderowanie figury geometrycznej w rodowisku OpenGL ES 2.0 skada si z nastpujcych
etapw:
1. Zaprogramuj jednostki cieniujce, ktre, dziaajc we wntrzu jednostki GPU,
wykorzystuj takie elementy, jak wsprzdne rysowania oraz macierze modelu lub
widoku bd rzutowania pobrane z pamici klienta, i rysuj dane obiekty. W rodowisku
OpenGL ES 1.0 nie znajdziemy odpowiednika tego etapu. W uproszczonym ujciu
mamy tu do czynienia z kolejnym etapem porednim pomidzy rysowaniem
wierzchokw i powierzchni.
2. Skompiluj w ukadzie GPU jednostki cieniujce utworzone w punkcie 1.

708 Android 3. Tworzenie aplikacji


3. Pocz skompilowane jednostki z punktu 2. w obiekt programu, wykorzystywany
w procesie rysowania.
4. Odczytaj procedury obsugi adresu z programu utworzonego w punkcie 3., dziki czemu
bdziemy mogli przydziela dane do tych wskanikw.
5. Zdefiniuj bufory wierzchokw.
6. Zdefiniuj macierze widoku modelu (dokonasz tego poprzez konfiguracj ostrosupa
widzenia, pooenia kamery itd.; w bardzo podobny sposb wykonuje si te czynnoci
dla rodowiska OpenGL ES 1.1).
7. Za pomoc procedur obsugi przeka elementy z punktw 5. i 6. do programu.
8. Ostatnim etapem jest sam proces rysowania.
Pokaemy kady z wymienionych etapw na przykadzie fragmentw kodw, a nastpnie
zaprezentujemy silnik renderowania odpowiadajcy klasie SimpleTriangleRenderer, ktr
opisalimy w czci powiconej rodowisku OpenGL ES 1.0. Rozpocznijmy od najwikszej
nowoci w bibliotece OpenGL ES 2.0, mianowicie od jednostek cieniujcych.

Jednostki cieniujce
Nawet najprostsze obiekty w rodowisku OpenGL 2.0 wymagaj wykorzystania fragmentw
kodw nazywanych jednostkami cieniujcymi. Stanowi one rdze biblioteki OpenGL ES 2.0.
Przekaemy teraz minimaln ilo informacji wymaganych do utworzenia prostego trjkta;
zalecamy zapoznanie si z materiaami rdowymi wymienionymi na kocu rozdziau.
Kady obiekt zawierajcy wierzchoki podlega przetwarzaniu przez jednostki cieniujce wierzchoki (ang. vertex shader). Z kolei kady obiekt zwizany z fragmentami, czyli przestrzeni
pomidzy wierzchokami, bdzie objty dziaaniem jednostek cieniujcych fragmenty (ang.
fragment shader). Zatem jednostka cieniujca wierzchoki przetwarza wycznie wsprzdne
wierzchokw, mimo e jednostka cieniujca fragmenty przetwarza kady piksel.
Listing 20.42 stanowi prosty przykad jednostki cieniujcej wierzchoki.
Listing 20.42. Prosta jednostka cieniujca wierzchoki
uniform mat4 uMVPMatrix;
attribute vec4 aPosition;
void main() {
gl_Position = uMVPMatrix * aPosition;
}

Jest to kod zapisany w jzyku cieniowania. Z pierwszego wiersza dowiadujemy si, e zmienna
uMVPMatrix jest zmienn wejciow programu, zadeklarowan dla typu mat4 (macierz 44).
Jest to rwnie macierz typu uniform, poniewa zostaa ona zdefiniowana dla wszystkich
wierzchokw, a nie tylko jednego.
Z drugiej strony mamy zmienn aPosition, ktra suy do definiowania wsprzdnych wierzchoka. Zostaa ona okrelona jako atrybut wierzchoka i jest niepowtarzalna dla kadego z nich.
Wrd innych atrybutw wierzchoka znajdziemy takie, jak kolor, tekstura itp. Rwnie zmienna aPosition jest wektorem o wartoci rwnej 4. Nastpnie sam program (listing 20.42) pobiera
wsprzdne wierzchoka i przeksztaca je za pomoc macierzy MVP (ang. Model View

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

709

Projection rzutowanie widoku modelu), ktra bdzie ustanawiana przez program wywoujcy, po czym przemnaa wsprzdne wierzchoka, aby uzyska ostateczne pooenie, okrelane przez zarezerwowan zmienn gl_Position jednostki cieniujcej.
Jednostka cieniujca zapewnia rysowanie lub pozycjonowanie wierzchokw. Na przykad program wywoujcy ustanowi bufor dla wierzchokw trjkta w sposb zaprezentowany na
listingu 20.43.
Listing 20.43. Ustanawianie danych wierzchokw
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mFVertexBuffer);

Bufor wierzchoka jest ostatnim argumentem tej metody GLES20. Bardzo przypomina ona
metod glVertexPointer z biblioteki OpenGL ES 1.0, oprcz pierwszego argumentu, ktry
tutaj stanowi uchwyt pooenia (positionHandle). Argument ten wskazuje atrybut aPosition
z jednostki cieniujcej, zamieszczonej na listingu 20.42. Taki uchwyt uzyskujemy za pomoc
kodu podobnego do przedstawionego poniej:
positionHandle = GLES20.glGetAttribLocation(shaderProgram, "aPosition");

Zasadniczo jednostka cieniujca ma przekaza uchwyt do zmiennej wejciowej. Sam obiekt


shaderProgram musi zosta skonstruowany poprzez przekazanie fragmentw kodu jednostce
GPU, w ktrej nastpuj ich kompilacja i czenie. Aby napisa program, w ktrym moemy
zacz rysowa, potrzebna nam bdzie rwnie jednostka cieniujca fragmenty. Listing 20.44
stanowi przykad takiej jednostki cieniujcej.
Listing 20.44. Przykad jednostki cieniujcej fragmenty
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

Ponownie pobieramy zarezerwowan zmienn gl_FragColor i przypisujemy jej czerwony


kolor. Zamiast przypisywa jej kolor w kodzie, moemy przekazywa wartoci kolorw od programu uytkownika poprzez jednostk cieniujc wierzchoki a po jednostk cieniujc fragmenty. Opisanie tego mechanizmu wykracza poza zakres ksiki, jednak odpowiednie informacje mona znale w rnorodnych rdach wymienionych na kocu tego rozdziau.
Obydwa rodzaje jednostek cieniujcych s niezbdne do rozpoczcia procesu rysowania.

Kompilowanie jednostek cieniujcych w programie


Po napisaniu segmentw jednostek cieniujcych, ktrych przykady znajdziemy na listingach
20.42 i 20.44, moemy wykorzysta kod zawarty na listingu 20.45 do skompilowania i wczytania
danej jednostki cieniujcej.
Listing 20.45. Kompilowanie i wczytywanie jednostki cieniujcej
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
if (shader != 0) {

710 Android 3. Tworzenie aplikacji


GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Nie mozna skompilowac jednostki cieniujacej " + shaderType
+ ":");
Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}

W tym fragmencie kodu wartoci argumentu shaderType moe by GLES20.GL_VERTEX_


SHADER albo GLES20.GL_FRAGMENT_SHADER. W zmiennej source musimy umieci cig znakw zawierajcy rdo, takie jak zaprezentowane na listingach 20.42 lub 20.44.
Listing 20.46 ukazuje nam sposb, w jaki funkcja loadShader (listing 20.45) jest wykorzystywana podczas konstruowania obiektu programu.
Listing 20.46. Tworzenie programu i uzyskiwanie uchwytw zmiennych
private int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
Log.d(TAG,"utworzono jednostke cieniujaca wierzcholki");
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
Log.d(TAG,"utworzono jednostke cieniujaca fragmenty");
int program = GLES20.glCreateProgram();
if (program != 0) {
Log.d(TAG,"utworzono program");
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Nie mozna dolaczyc programu: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

711

Uzyskiwanie dostpu do zmiennych jednostek cieniowania


Po skonfigurowaniu programu mona wykorzysta jego uchwyt do uzyskania dostpu do zmiennych wejciowych wymaganych przez jednostki cieniujce. Na listingu 20.47 widzimy, w jaki
sposb moemy osign ten cel.
Listing 20.47. Uzyskiwanie uchwytw zmiennych aPosition i uMVPMatrix
int maPositionHandle =
GLES20.glGetAttribLocation(mProgram, "aPosition");
int muMVPMatrixHandle =
GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

Prosty trjkt napisany w rodowisku OpenGL ES 2.0


Omwilimy ju wszystkie podstawy wymagane do utworzenia struktury podobnej do zaprezentowanej w przypadku wersji 1.0 rodowiska OpenGL. Zbierzemy teraz w cao wszystkie
informacje dotyczce abstrakcyjnego silnika renderujcego, ktry obsuy cay proces inicjalizacji
(na przykad utworzy jednostki cieniujce, programy itp.). Na listingu 20.48 zaprezentowalimy
jego kod.
Listing 20.48. Klasa ES20AbstractRenderer
public abstract class ES20AbstractRenderer
implements android.opengl.GLSurfaceView.Renderer
{
public static String TAG = "ES20AbstractRenderer";
private
private
private
private

float[]
float[]
float[]
float[]

mMMatrix = new float[16];


mProjMatrix = new float[16];
mVMatrix = new float[16];
mMVPMatrix = new float[16];

private int mProgram;


private int muMVPMatrixHandle;
private int maPositionHandle;
public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig)
{
prepareSurface(gl,eglConfig);
}
public void prepareSurface(GL10 gl, EGLConfig eglConfig)
{
Log.d(TAG,"przygotowywanie powierzchni");
mProgram = createProgram(mVertexShader, mFragmentShader);
if (mProgram == 0) {
return;
}
Log.d(TAG,"Uzyskiwanie uchwytu polozenia:aPosition");
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition");
if (maPositionHandle == -1) {
throw new RuntimeException("Nie mozna uzyskac atr. polozenia dla aPosition");

712 Android 3. Tworzenie aplikacji


}
Log.d(TAG,"Uzyskiwanie uchwytu macierzy:uMVPMatrix");
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
checkGlError("glGetUniformLocation uMVPMatrix");
if (muMVPMatrixHandle == -1) {
throw new RuntimeException("Nie mozna uzyskac atr. polozenia dla uMVPMatrix");
}
}
public void onSurfaceChanged(GL10 gl, int w, int h)
{
Log.d(TAG,"powierzchnia zmieniona. Ustanawianie macierzy ostrosl. widzenia:
macierz rzutowania");
GLES20.glViewport(0, 0, w, h);
float ratio = (float) w / h;
Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
public void onDrawFrame(GL10 gl)
{
Log.d(TAG,"Ustanawianie macierzy widzenia");
Matrix.setLookAtM(mVMatrix, 0, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
Log.d(TAG,"podstawowa funkcja drawframe");
GLES20.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(mProgram);
checkGlError("glUseProgram");
draw(gl,this.maPositionHandle);
}
private int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
Log.d(TAG,"utworzono jednostke cieniujaca wierzcholki");
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
Log.d(TAG,"utworzono jednostke cieniujaca fragmenty");
int program = GLES20.glCreateProgram();
if (program != 0) {
Log.d(TAG,"utworzono program");
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Nie mozna dolaczyc programu: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

713

}
}
return program;
}
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
if (shader != 0) {
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Nie mozna skompilowac jednostki cieniujacej "
+ shaderType + ":");
Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
private final String mVertexShader =
"uniform mat4 uMVPMatrix;\n" +
"attribute vec4 aPosition;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
"}\n";
private final String mFragmentShader =
"void main() {\n" +
" gl_FragColor = vec4(0.5, 0.25, 0.5, 1.0);\n" +
"}\n";
protected void checkGlError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e(TAG, op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
}
}
protected void setupMatrices()
{
Matrix.setIdentityM(mMMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
}
protected abstract void draw(GL10 gl, int positionHandle);
}

Wikszo powyszego kodu stanowi poczenie uprzednio omawianych koncepcji, za wyjtkiem jednego szczegu. Funkcja setupMatrices ukazuje nam sposb, w jaki klasa Matrix jest
wykorzystywana do czenia wielu macierzy w jedn wspln, zwan mMVPMatrix, poprzez
przemnoenie przez inne macierze, poczwszy od macierzy jednostkowej.

714 Android 3. Tworzenie aplikacji


Zatem zmienna mMMatrix jest macierz jednostkow. Warto zmiennej mVMatrix uzyskujemy poprzez zastosowanie interfejsu punktu ocznego lub punktu spogldania kamery. Macierz
rzutowania mProjMatrix jest dostpna za pomoc specyfikacji ostrosupa widzenia. Obydwie
koncepcje punktu ocznego i ostrosupa widzenia s tak samo zdefiniowane jak w przypadku rodowiska OpenGL 1.0. Macierz MVP stanowi jedynie iloczyn tych macierzy. W kocu,
wywoanie funkcji glUniformMatrix4fv powoduje ustanowienie tej macierzy jako zmiennej
w jednostce cieniowania wierzchokw, dziki czemu poprzez przemnoenie wsprzdnych
wierzchoka przez t macierz uzyskujemy ostateczne pooenie (listing 20.42).
Na listingu 20.49 widzimy kod klasy GS20SimpleTriangleRenderer rozszerzajcej abstrakcyjny
silnik renderowania, w ktrej zostaa umieszczona minimalna liczba algorytmw umoliwiajcych zdefiniowanie punktw oraz ich narysowanie.
Listing 20.49. Klasa GS20SimpleTriangleRenderer
public class ES20SimpleTriangleRenderer extends ES20AbstractRenderer
{

//Nieprzetworzony, natywny bufor, przechowujcy wsprzdne punktu


private FloatBuffer mFVertexBuffer;
private static final int FLOAT_SIZE_BYTES = 4;
private final float[] mTriangleVerticesData = {

// X, Y, Z
-1.0f, -0.5f, 0,
1.0f, -0.5f, 0,
0.0f, 1.11803399f, 0 };
public ES20SimpleTriangleRenderer(Context context)
{
ByteBuffer vbb = ByteBuffer.allocateDirect(mTriangleVerticesData.length
* FLOAT_SIZE_BYTES);
vbb.order(ByteOrder.nativeOrder());
mFVertexBuffer = vbb.asFloatBuffer();
mFVertexBuffer.put(mTriangleVerticesData);
mFVertexBuffer.position(0);
}

protected void draw(GL10 gl, int positionHandle)


{
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false,
0, mFVertexBuffer);
checkGlError("glVertexAttribPointer maPosition");
GLES20.glEnableVertexAttribArray(positionHandle);
checkGlError("glEnableVertexAttribArray maPositionHandle");
this.setupMatrices();
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
checkGlError("glDrawArrays");
}

Jeeli teraz wywoamy aktywno z listingu 20.38, ujrzymy trjkt rysowany w danym kierunku.
Aby aplikacja zadziaaa, potrzebne nam bd dodatkowe pliki:
ES20AbstractRenderer.java (listing 20.48),
ES20SimpleTriangleRenderer.java (listing 20.49),
OpenGL20MultiveTestHarnessActivity.java (listing 20.38).

Rozdzia 20 Programowanie grafiki trjwymiarowej za pomoc biblioteki OpenGL

715

Po skompilowaniu tych plikw moemy ponownie uruchomi program i wybra z menu opcj
Trjkt ES20. Zostanie wywietlony jeden trjkt, taki jak ten na rysunku 20.3.
Jednak, jak ju stwierdzilimy, powyszy przykadowy projekt nie zadziaa na emulatorze.
W celu jego przetestowania musimy podczy fizyczne urzdzenie do rodowiska Eclipse.
Sprawdzalimy go na pierwszym modelu Motorola Droid firmy Verizon. Instrukcje dotyczce
podczenia urzdzenia znajdziemy w rozdziale 2. W podrozdziale z odnonikami zamiecilimy
take adres URL do witryny, w ktrej zamierzamy dodawa zaktualizowane informacje powizane
rwnie z innymi urzdzeniami.

Dodatkowe rda dotyczce rodowiska OpenGL ES 2.0


W podrozdziale Odnoniki znajdziemy informacje o zasobach zwizanych z bibliotek
OpenGL ES 2.0. Gdy ju zrozumiemy koncepcje jednostek cieniujcych, mechanizmy dziaania
rodowiska OpenGL ES 1.0 oraz podstawowe wytyczne na temat jego wersji 2.0, moemy
przetestowa przykady znajdujce si w zestawie Android SDK, pod warunkiem e posiadamy
fizyczne urzdzenie.
Zamiecilimy cay kod rdowy aplikacji generujcej trjkt w rodowisku Eclipse wewntrz
projektu, ktry mona pobra z naszej oficjalnej strony. Projekt ten zawiera wszystkie etapy
niezbdne do uruchomienia rodowiska OpenGL ES 2.0.

Instrukcje zwizane z kompilowaniem kodu


Najlepszym rozwizaniem w kwestii testowania kodw umieszczonych w tym rozdziale jest
pobranie pliku ZIP utworzonego specjalnie na potrzeby tego rozdziau. Adres URL tego pliku
zosta umieszczony w podrozdziale Odnoniki. Znajdziemy w tym pliku kod kadej omwionej w tym rozdziale klasy. Jeeli chcemy utworzy aplikacj bezporednio z listingw, znajdziemy w tym rozdziale wszystkie potrzebne pliki. By moe Czytelnik bdzie musia uwzgldni
kilka wasnych zasobw, na przykad ikon aplikacji itd. W razie wtpliwoci co do sposobu
wstawiania ich do kodu naley posikowa si gotowym projektem.

Odnoniki
Nastpujce zasoby uznalimy za przydatne w zrozumieniu oraz podczas pracy w rodowisku
OpenGL:
http://developer.android.com/reference/android/opengl/GLSurfaceView.html adres
odnoszcy si do pakietu Androida android.opengl.
www.khronos.org/opengles/documentation/opengles1_0/html/index.html podrcznik
referencyjny rodowiska OpenGL ES utworzony przez grup Khronos.
http://www.glprogramming.com/red/ podrcznik programowania w rodowisku
OpenGL (czerwona ksiga). Chocia materiay tu zawarte s bardzo przydatne, zakres
informacji koczy si na wersji OpenGL ES 1.1. W celu zapoznania si z najnowszymi
informacjami, w tym dotyczcymi jednostek cieniujcych, musimy zaopatrzy si
w sidm edycj ksigi.
http://msdn.microsoft.com/en-us/library/ms970772(printer).aspx bardzo dobry artyku
firmy Microsoft, dotyczcy mapowania tekstur.

716 Android 3. Tworzenie aplikacji

http://ezekiel.vancouver.wsu.edu/~cs442/ bardzo wnikliwy kurs dotyczcy rodowiska


OpenGL autorstwa Waynea O. Cochrana ze Stanowego Uniwersytetu Waszyngtoskiego.
http://java.sun.com/javame/reference/apis/jsr239/ dokumentacja specyfikacji
JSR 239 (Java Binding for the OpenGL ES API).
www.khronos.org/opengles/sdk/docs/man/ fragmenty podrcznika dotyczce
rodowiska OpenGL ES 2.0 grupy Khronos, dobre jako rdo dalszych odnonikw,
a nie samych informacji.
www.opengl.org/documentation/glsl/ przydatny dla osoby, ktra chce zrozumie
kierunek obrany w rodowisku OpenGL ES 2.0, i do uatwienia niezbdnego opanowania
jzyka cieniowania.
OpenGL Shading Language, wydanie trzecie, Randi J. Rost i inni. Osobicie nie czytalimy
tej ksiki, ale zapowiada si obiecujco.
http://developer.android.com/reference/android/opengl/GLES20.html odniesienie
do interfejsu GLES20 z zestawu Android SDK.
http://developer.android.com/reference/android/opengl/GLSurfaceView.html#setEGL
ContextClientVersion(int) odniesienie do klasy GLSurfaceView.
http://www.androidbook.com/akc/display?url=NotesIMPTitlesURL&ownerUserId=sa
tya&folderName=OpenGL&order_by_format=news badania rodowiska OpenGL
przeprowadzone przez jednego z autorw ksiki.
http://www.androidbook.com/item/3190 badania dotyczce tekstur w rodowisku
OpenGL przeprowadzone przez jednego z autorw ksiki.
http://www.androidbook.com/item/3574 instrukcje dotyczce uruchomienia
aplikacji na fizycznym urzdzeniu z poziomu rodowiska Eclipse.
ftp://ftp.helion.pl/przyklady/and3ta.zip z tego adresu moemy pobra projekty
utworzone z myl o niniejszej ksice. Projekty z tego rozdziau zostay
umieszczone w katalogu ProAndroid3_R20_OpenGL.

Podsumowanie
Powicilimy mnstwo miejsca standardowi OpenGL, co moe by przydatne zwaszcza dla
osb majcych z nim do czynienia po raz pierwszy. Chcielibymy, aby byo to znakomite wprowadzenie do rodowiska OpenGL nie tylko dla systemu Android, lecz rwnie dla innych systemw obsugujcych ten standard.
W rozdziale tym przedstawilimy podstawowe informacje na temat biblioteki OpenGL. Pokazalimy interfejs API (dostpny wycznie w systemie Android), umoliwiajcy prac ze standardowymi interfejsami rodowiska OpenGL. Omwilimy pojcia ksztatw i tekstur, a take
zademonstrowalimy, w jaki sposb mona wykorzysta potok rysowania do tworzenia wielu
obiektw. Poznalimy podstawy rodowiska OpenGL ES 2.0, jego jzyk cieniowania, podstawowe rnice pomidzy t a poprzedni wersj bibliotek, a take rnorodne odnoniki pozwalajce na dalsze zgbianie zagadnienia.

R OZDZIA

21
Badanie aktywnych folderw

Wprowadzone w wersji 1.5 rodowiska Android aktywne foldery umoliwiaj


projektantom umieszczanie takich dostawcw treci, jak kontakty, notatki oraz
multimedia, w domylnym ekranie startowym urzdzenia (ktre bdziemy nazywa stron startow urzdzenia). Kiedy dostawca treci, na przykad contacts, zostanie umieszczony na stronie gwnej jako aktywny folder, bdzie on automatycznie odwieany w razie dodawania, usuwania lub modyfikowania kontaktw
w bazie danych. W tym rozdziale wyjanimy sens istnienia aktywnych folderw,
sposoby ich implementacji oraz ich uaktywnienia.

Badanie aktywnych folderw


W Androidzie aktywny folder (ang. live folder) ma si do dostawcy treci tak, jak
czytnik RSS do publikowania strony internetowej. W rozdziale 4. stwierdzilimy,
e dostawcy treci przypominaj strony internetowe dostarczajce informacje
poprzez identyfikatory URI. W miar rozpowszechniania witryn sieciowych, z ktrych w kadej publikowano informacje na swj wasny sposb, pojawia si
potrzeba zbierania treci z wielu rnych stron internetowych, aby da uytkownikowi moliwo ich ledzenia za pomoc jednego czytnika. RSS sta si wsplnym wzorcem wielu rnorodnych zbiorw informacji. Dziki takiemu wzorcowi
istnieje moliwo zaprojektowania czytnika, ktry bdzie odczytywa dane, dopki bd one posiaday jednolit struktur.
Taka sama jest koncepcja aktywnych folderw. Podobnie jak czytnik RSS zapewnia wsplny interfejs dla treci opublikowanych w sieci, tak aktywny folder definiuje wsplny interfejs dla dostawcy treci w Androidzie. Dopki dostawca treci lub klasa osonowa tego dostawcy speniaj wymagania protokou, moemy
w Androidzie utworzy ikon aktywnego folderu, reprezentujc tego dostawc na stronie startowej urzdzenia. Po klikniciu tej ikony system automatycznie
skontaktuje si z dostawc treci. Spodziewamy si zatem, e dostawca treci
przekae kursor. Zgodnie z kontraktem aktywnego folderu kursor ten musi posiada predefiniowany zbir kolumn. Nastpnie jest on wywietlany poprzez
widoki ListView lub GridView.

718 Android 3. Tworzenie aplikacji


Opierajc si na tych koncepcjach, dziaanie aktywnych folderw mona przedstawi w nastpujcy sposb:
1. Najpierw na stronie startowej tworzymy ikon reprezentujc zbir wierszy
pochodzcych od dostawcy treci. Tworzymy takie powizanie poprzez przypisanie
identyfikatora URI do ikony.
2. Kiedy uytkownik kliknie t ikon, system pobiera identyfikator URI i wykorzystuje
go do wywoania dostawcy treci. Dostawca ten poprzez kursor zwraca zbir wierszy.
3. Dopki kursor zawiera kolumny, ktre mog by rozpoznane przez aktywny folder
(na przykad nazw, opis oraz program wywoywany po klikniciu wiersza), system
bdzie je wywietla jako widoki ListView lub GridView.
4. Poniewa widoki ListView oraz GridView maj moliwo aktualizowania swoich
danych podczas wprowadzania zmian w bazie danych, widoki te s nazywane
aktywnymi std wzia si nazwa aktywne foldery.
Pracujc z aktywnymi folderami, naley pamita o dwch podstawowych zasadach. Pierwsza
z nich polega na definiowaniu wsplnych nazw kolumn dla wszystkich kursorw. Dziki niej
wszystkie kursory przeznaczone dla aktywnych folderw w Androidzie s traktowane tak samo.
Wedle drugiej zasady widoki w Androidzie potrafi wyszukiwa aktualizacje danych kursora
i odpowiednio si do nich dostosowywa. Nie jest to regua specyficzna jedynie dla aktywnych
folderw; w rzeczywistoci okazuje si ona standardowa dla wszystkich widokw interfejsu
UI w Androidzie, zwaszcza dla widokw korzystajcych z kursora.
Skoro przedstawilimy ju koncepcj aktywnych folderw, bdziemy systematycznie zagbia
si w ich struktur. Poszczeglne informacje przedstawimy w dwch podrozdziaach. W pierwszym z nich wnikliwie przeanalizujemy, w jaki sposb uytkownik korzysta z aktywnego folderu.
Po tej czci aktywne foldery stan si jeszcze bardziej zrozumiae.
W drugim podrozdziale zaprezentujemy sposoby poprawnego tworzenia tych struktur, aby byy
naprawd aktywne. Aby uaktywni folder, naley przeprowadzi kilka dodatkowych czynnoci,
zatem zajmiemy si tym wcale nie tak oczywistym aspektem aktywnych folderw.

W jaki sposb uytkownik korzysta z aktywnych folderw


Aktywne foldery s dostpne dla uytkownikw poprzez stron startow urzdzenia. Ponisza
sekwencja przedstawia sposb wykorzystywania aktywnych folderw:
1. Otwrz stron startow urzdzenia.
2. Otwrz menu kontekstowe strony startowej. Zostaje ono wywietlone po dugim
klikniciu pustej przestrzeni na ekranie startowym.
3. Znajd w menu kontekstowym opcj Foldery i kliknij j, aby ujrze list dostpnych
aktywnych folderw.
4. Na wywietlonej licie zaznacz nazw aktywnego folderu, ktry chcesz umieci na
stronie startowej. Zostanie utworzona ikona reprezentujca wybrany folder aktywny.
5. Kliknij ikon konfiguracji aktywnego folderu, utworzon w punkcie 4., aby wywietli
wiersze zawierajce informacje (dane reprezentowane przez ten aktywny folder)
w widokach ListView lub GridView.
6. Kliknij jeden z wierszy, aby przywoa aplikacj wywietlajc dane zawarte w tym wierszu.
7. Za pomoc opcji menu wywietlanych przez aplikacj moesz przeglda elementy lub
manipulowa danym elementem. Za ich pomoc moesz rwnie tworzy nowe
elementy dozwolone przez aplikacj.

Rozdzia 21 Badanie aktywnych folderw

719

8. Zwr uwag, e aktywne foldery automatycznie odzwierciedlaj wszelkie zmiany


dokonane na elemencie lub zbiorze elementw.
Omwimy wszystkie powysze etapy, ilustrujc kady z nich zrzutem ekranu. Rozpoczniemy
od punktu 1., czyli typowej strony startowej Androida (rysunek 21.1). Strona startowa moe si
nieznacznie rni w zalenoci od stosowanej wersji Androida oraz urzdzenia.

Rysunek 21.1. Strona startowa Androida

Jeeli na ekranie startowym wykonamy dugie kliknicie, Android wywietli jego menu kontekstowe (rysunek 21.2).

Rysunek 21.2. Menu kontekstowe strony startowej w Androidzie

720 Android 3. Tworzenie aplikacji


Po wybraniu opcji Foldery pojawi si kolejne menu, przedstawiajce dostpne foldery aktywne
(rysunek 21.3). W nastpnym podrozdziale pokaemy, jak utworzy aktywny folder, na razie
jednak zamy, e jest on ju zbudowany i nosi nazw Nowy aktywny folder (rysunek 21.3).

Rysunek 21.3. Przegldanie listy dostpnych aktywnych folderw


Jeeli Czytelnik chce przyjrze si naszej przykadowej aplikacji jeszcze przed jej
utworzeniem, moe pobra tworzcy j projekt i zainstalowa go na emulatorze.
Adres strony, z ktrej mona pobra projekt, znajduje si w podrozdziale Odnoniki.
Potrzebna bdzie rwnie aplikacja obsugujca kontakty, ktra stanowi cz zestawu
SDK i pozwala umieci na emulatorze kilka przykadowych kontaktw. Po pobraniu
i zaimportowaniu projektu do rodowiska Eclipse moemy go uruchomi na emulatorze
w postaci aktywnego folderu. Efekt kocowy bdzie przypomina ekran widoczny
na rysunku 21.3.

Po klikniciu opcji Nowy aktywny folder na stronie startowej Androida zostanie utworzona
ikona reprezentujca aktywny folder. W naszym przykadzie nazw tej ikony bdzie Kontakty
AF skrt od Kontakty Aktywny Folder (rysunek 21.4). W folderze tym bd wywietlane
kontakty z bazy kontaktw. W trakcie implementacji aktywnego folderu pokaemy sposb,
w jaki jest definiowana jego nazwa.
W nastpnym podrozdziale bdzie si mona przekona, e to aktywno zapewnia utworzenie
folderu Kontakty AF. Na razie interesuj nas wraenia uytkownika, zatem kliknijmy ikon Kontakty AF, aby ujrze list kontaktw wywietlon w widoku ListView (rysunek 21.5). Jeszcze raz
przypominamy, e w zalenoci od posiadanej wersji systemu lista ta moe wyglda inaczej.
Wygld listy moe by rny od zaprezentowanego, gdy zaley on od liczby posiadanych
kontaktw. Po klikniciu jednego z kontaktw ujrzymy jego szczegy (rysunek 21.6). Zwrmy
uwag, e szczegy kontaktu s wywietlane poprzez aplikacj obsugujc kontakty, zatem wygld tego aspektu rwnie zaley od wersji systemu Android.

Rozdzia 21 Badanie aktywnych folderw

721

Rysunek 21.4. Ikona aktywnego folderu na ekranie startowym

Rysunek 21.5. Wywietlanie aktywnego folderu kontaktw

Moemy klikn znajdujcy si u dou ekranu przycisk Menu, aby zobaczy moliwoci edycyjne
danego kontaktu (rysunek 21.7). Take ten etap jest obsugiwany przez aplikacj, wic jego
stylistyka jest zalena od rodzaju urzdzenia i wersji Androida.
Po wybraniu opcji edycji kontaktu pojawi si ekran (rwnie o wygldzie zalenym od wersji
systemu) ukazany na rysunku 21.8.
Aby ujrze aktywne zachowanie si tego folderu, mona zaktualizowa imi lub nazwisko
kontaktu. Po powrocie do widoku folderu Kontakty AF ujrzymy, e wprowadzone zmiany zostay uwzgldnione. W tym celu naley wielokrotne klika przycisk cofania, dopki nie wrcimy
do folderu Kontakty AF.

722 Android 3. Tworzenie aplikacji

Rysunek 21.6. Otwieranie aktywnego folderu kontaktw

Rysunek 21.7. Opcje menu pojedynczego kontaktu

Tworzenie aktywnego folderu


Wyjanilimy, czym s aktywne foldery oraz do czego su. W celu wygenerowania aktywnego folderu wymagane s dwa elementy: aktywno i wyspecjalizowany dostawca treci.
Android wykorzystuje etykiet tej aktywnoci do zapenienia widocznej na rysunku 21.3 listy
dostpnych aktywnych folderw. Android wywouje rwnie t aktywno w celu uzyskania
identyfikatora URI umoliwiajcego otrzymanie listy wywietlanych wierszy.

Rozdzia 21 Badanie aktywnych folderw

723

Rysunek 21.8. Edycja szczegowych informacji o kontakcie

Dostarczany przez aktywno identyfikator URI powinien wskazywa wyspecjalizowanego


dostawc treci, ktry bdzie przekazywa krotki. Dostawca przekazuje te krotki poprzez waciwie zdefiniowany kursor. Stwierdzamy, e kursor jest waciwie zdefiniowany, poniewa
oczekujemy, e bdzie zawiera predefiniowany zestaw nazw kolumn.
Zazwyczaj umieszcza si te dwa elementy w aplikacji, a nastpnie wdraa si j na urzdzenie.
Trzeba rwnie zapewni sobie kilka pomocniczych plikw, bez ktrych aktywne foldery nie
bd dziaa. Objanimy i zademonstrujemy wymienione koncepcje na przykadowym projekcie,
skadajcym si z nastpujcych plikw:

AndroidManifest.xml w tym pliku okrelono aktywnoci, wywoywane w celu


utworzenia definicji aktywnego folderu.

AllContactsLiveFolderCreatorActivity.java ta aktywno dostarcza definicj


aktywnego folderu, pozwalajc na wywietlanie wszystkich kontaktw z bazy
danych.

MyContactsProvider.java ten dostawca treci reaguje na identyfikator URI


aktywnego folderu, przekazujcego kursor z kontaktami. Dostawca ten wewntrznie
wykorzystuje dostpnego w Androidzie dostawc treci kontaktw.

MyCursor.java jest to wyspecjalizowany kursor, wykonujcy operacj requery


podczas zmiany danych.

BetterCursorWrapper.java plik ten suy klasie MyCursor do przeprowadzania


operacji requery.

Omwimy kady plik po kolei, aby atwiej byo zrozumie zasad dziaania aktywnych folderw.

AndroidManifest.xml
Mielimy ju wielokrotnie do czynienia z plikiem AndroidManifest.xml. Jest on konieczny do
dziaania wszystkich aplikacji. Fragment pliku dotyczcy aktywnych folderw, ktry zosta oddzielony komentarzem, wskazuje istnienie aktywnoci AllContactsLiveFolderCreatorActivity

724 Android 3. Tworzenie aplikacji


umoliwiajcej utworzenie aktywnego folderu (listing 21.1). Fakt ten zosta wyraony poprzez
deklaracj intencji, ktrej dziaaniem jest android.intent.action.CREATE_LIVE_FOLDER.
Listing 21.1. Plik AndroidManifest.xml definicji aktywnego folderu
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.livefolders"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">

<!-- AKTYWNE FOLDERY -->


<activity
android:name=".AllContactsLiveFolderCreatorActivity"
android:label="Nowy aktywny folder"
android:icon="@drawable/icon">
<intent-filter>
<action android:name="android.intent.action.CREATE_LIVE_FOLDER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<provider android:authorities="com.androidbook.livefolders.contacts"
android:multiprocess="true"
android:name=".MyContactsProvider" />
</application>
<uses-sdk android:minSdkVersion="3" />
<uses-permission android:name="android.permission.READ_CONTACTS"/>
</manifest>

Etykieta tej aktywnoci, Nowy aktywny folder, pojawi si w menu kontekstowym strony startowej (rysunek 21.3). Jak zostao wyjanione w punkcie W jaki sposb uytkownik korzysta z aktywnych folderw, dostp do menu kontekstowego strony startowej uzyskujemy poprzez dugie
kliknicie na obszarze tego ekranu startowego.
Kolejnym godnym uwagi elementem kodu z listingu 21.1 jest deklaracja provider, zakotwiczona
do identyfikatora URI content://com.androidbook.livefolders.contacts i obsugiwana
przez klas dostawcy MyContactsProvider. Dostawca ten dostarcza kursor wypeniajcy kontrolk
ListView, ktra zostaje otwarta po klikniciu odpowiedniej ikony aktywnego folderu (rysunek
21.5). Aktywno AllContactsLiveFolderCreatorActivity takiego folderu musi wiedzie,
czym jest ten identyfikator URI, i przekaza go po wywoaniu przez system. Android przywouje
t aktywno po wybraniu nazwy aktywnego folderu, aby utworzy jego ikon na ekranie
startowym.
Zgodnie z protokoem aktywnych folderw intencja CREATE_LIVE_FOLDER bdzie pozwalaa
wywietla aktywno AllContactsLiveFolderCreatorActivity jako opcj zatytuowan
Nowy aktywny folder w menu kontekstowym strony startowej (rysunek 21.3). Kliknicie tej opcji
spowoduje utworzenie ikony na stronie startowej, co zostao pokazane na rysunku 21.4.

Rozdzia 21 Badanie aktywnych folderw

725

Zadaniem aktywnoci AllContactsLiveFolderCreatorActivity jest zdefiniowanie ikony,


skadajcej si z obrazu i etykiety. W naszym przypadku kod aktywnoci AllContactsLive
FolderCreatorActivity nadaje etykiecie nazw Kontakty AF (listing 21.2). Przyjrzyjmy
si zatem kodowi rdowemu tego kreatora aktywnych folderw.

AllContactsLiveFolderCreatorActivity.java
Klasa AllContactsLiveFolderCreatorActivity ma do spenienia jedn rol: generatora
czy te kreatora aktywnego folderu (listing 21.2). Moemy j sobie wyobrazi jako szablon takiego folderu. Po kadym klikniciu tej aktywnoci (poprzez opcj Foldery w menu kontekstowym ekranu startowego) zostanie wygenerowany aktywny folder.
Listing 21.2. Kod rdowy klasy AllContactsLiveFolderCreatorActivity
public class AllContactsLiveFolderCreatorActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
final String action = intent.getAction();
if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
setResult(RESULT_OK,
createLiveFolder(MyContactsProvider.CONTACTS_URI,
"Kontakty AF",
R.drawable.icon)
);
}
else {
setResult(RESULT_CANCELED);
}
finish();
}
private Intent createLiveFolder(Uri uri, String name, int icon)
{
final Intent intent = new Intent();
intent.setData(uri);
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
Intent.ShortcutIconResource.fromContext(this, icon));
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE,
LiveFolders.DISPLAY_MODE_LIST);
return intent;
}
}

Aby wykona to zadanie, aktywno podaje obiektowi wywoujcemu stronie startowej lub,
w naszym przypadku, strukturze aktywnego folderu nazw aktywnego folderu, obraz ikony
tego folderu, identyfikator URI danych oraz tryb wywietlania (lista lub siatka). Z kolei struktura
zapewnia utworzenie ikony aktywnego folderu na ekranie startowym.

726 Android 3. Tworzenie aplikacji


W dokumentacji zestawu Android SDK dotyczcej klasy android.provider.LiveFolders
znajduje si spis wszystkich kontraktw wymaganych przez aktywny folder.

Metoda createLiveFolder przede wszystkim ustanawia wartoci wobec intencji wywoujcej.


Po przekazaniu intencji procedurze wywoujcej procedura ta otrzyma nastpujce informacje:
nazw aktywnego folderu;
obraz wykorzystywany jako ikona aktywnego folderu;
tryb wywietlania: lista lub siatka;
identyfikator URI danych lub treci, sucy do przywoywania informacji.
Informacje te wystarcz do utworzenia ikony aktywnego folderu, przedstawionej na rysunku 21.4. Kiedy uytkownik j kliknie, zostanie wywoany identyfikator URI, dziki ktremu
zostan odczytane dane. Zadaniem dostawcy treci okrelonego dziki temu identyfikatorowi
jest dostarczenie znormalizowanego kursora. Zademonstrujemy teraz kod tego dostawcy treci
klas MyContactsProvider.

MyContactsProvider.java
Przed klas MyContactsProvider stoj nastpujce zadania:
1. Rozpoznanie przychodzcego identyfikatora URI
content://com.androidbook.livefolders.contacts/contacts.

2. Wewntrzne wywoanie dostawcy treci kontaktw Androida, identyfikowanych


adresem content://contacts/people/ (zwracajmy szczegln uwag na aplikacj
obsugujc kontakty, poniewa adres URL do niej moe ulega zmianie wraz z kad
now wersj systemu).
3. Odczytanie wszystkich krotek kursora oraz odwzorowanie ich na kursorze typu
MatrixCursor wraz z umieszczeniem poprawnych nazw kolumn wymaganych
przez struktur aktywnego folderu.
4. Umieszczenie obiektu MatrixCursor w innym kursorze, aby przeprowadzenie
operacji requery na tym obiekcie powodowao w razie potrzeby wywoanie
dostawcy treci kontaktw.
Kod dostawcy MyContactsProvider zosta umieszczony na listingu 21.3. Istotne elementy
zostay zaznaczone pogrubion czcionk i s oparte na omwionych powyej zaoeniach. Objanienie kodu znajdziemy pod listingiem.
Listing 21.3. Kod rdowy klasy MyContactsProvider
public class MyContactsProvider extends ContentProvider {
public static final String AUTHORITY = "com.androidbook.livefolders.contacts";

//Identyfikator Uri biorcy udzia w procesie tworzenia aktywnego folderu jako dane
//wejciowe
public static final Uri CONTACTS_URI = Uri.parse("content://" +
AUTHORITY + "/contacts" );

//Aby ten identyfikator URI zosta rozpoznany


private static final int TYPE_MY_URI = 0;
private static final UriMatcher URI_MATCHER;
static{

Rozdzia 21 Badanie aktywnych folderw

URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);


URI_MATCHER.addURI(AUTHORITY, "contacts", TYPE_MY_URI);
}
@Override
public boolean onCreate() {
return true;
}
@Override
public int bulkInsert(Uri arg0, ContentValues[] values) {
return 0; //Niczego nie wstawiamy
}

//Zbir kolumn wymaganych przez aktywny folder


//Jest to kontrakt aktywnego folderu
private static final String[] CURSOR_COLUMNS = new String[]
{
BaseColumns._ID,
LiveFolders.NAME,
LiveFolders.DESCRIPTION,
LiveFolders.INTENT,
LiveFolders.ICON_PACKAGE,
LiveFolders.ICON_RESOURCE
};

//W przypadku braku krotek


//wprowadzamy zastpstwo w postaci komunikatu o bdzie
//Zauwamy, e posiada taki sam zbir kolumn jak aktywny folder
private static final String[] CURSOR_ERROR_COLUMNS = new String[]
{
BaseColumns._ID,
LiveFolders.NAME,
LiveFolders.DESCRIPTION
};

//Krotka komunikatu o bdzie


private static final Object[] ERROR_MESSAGE_ROW =
new Object[]
{
-1,
//identyfikator
"Nie znaleziono kontaktw",
"Sprawd baz kontaktw"

//nazwa
//opis

};

//Stosowany kursor bdu


private static MatrixCursor sErrorCursor = new
MatrixCursor(CURSOR_ERROR_COLUMNS);
static
{
sErrorCursor.addRow(ERROR_MESSAGE_ROW);
}

727

728 Android 3. Tworzenie aplikacji


//Kolumny odczytywane z bazy kontaktw
private static final String[] CONTACTS_COLUMN_NAMES = new String[]
{
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.TIMES_CONTACTED,
ContactsContract.Contacts.STARRED
};
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{

//Sprawdza identyfikator Uri i zwraca bd, jeeli nie znajdzie dopasowania


int type = URI_MATCHER.match(uri);
if(type == UriMatcher.NO_MATCH)
{
return sErrorCursor;
}
Log.i("ss", "kwerenda wywoana");
try
{
MatrixCursor mc = loadNewData(this);
mc.setNotificationUri(getContext().getContentResolver(),
Uri.parse("content://contacts/people/"));
MyCursor wmc = new MyCursor(mc,this);
return wmc;
}
catch (Throwable e)
{
return sErrorCursor;
}
}
public static MatrixCursor loadNewData(ContentProvider cp)
{
MatrixCursor mc = new MatrixCursor(CURSOR_COLUMNS);
Cursor allContacts = null;
try
{
allContacts = cp.getContext().getContentResolver().query(
ContactsContract.Contacts.CONTENT_URI,
CONTACTS_COLUMN_NAMES,
null, //filtr krotek
null,
ContactsContract.Contacts.DISPLAY_NAME);

//sortowanie

while(allContacts.moveToNext())
{
String timesContacted = "Nawizane poczenia: "+allContacts.getInt(2);
Object[] rowObject = new Object[]
{
allContacts.getLong(0),
allContacts.getString(1),

//identyfikator
//nazwa

Rozdzia 21 Badanie aktywnych folderw

timesContacted,
Uri.parse("content://contacts/people/"
+allContacts.getLong(0)),
cp.getContext().getPackageName(),
R.drawable.icon

729

//opis
//id. Uri intencji
//pakiet
//ikona

};
mc.addRow(rowObject);
}
return mc;
}
finally
{
allContacts.close();
}
}
@Override
public String getType(Uri uri)
{

//Wskazuje typ MIME danego identyfikatora Uri


//zdefiniowanego dla osonowego dostawcy
//Typ ten wyglda zazwyczaj nastpujco:
// "vnd.android.cursor.dir/vnd.google.note"
return ContactsContract.Contacts.CONTENT_TYPE;
}
public Uri insert(Uri uri, ContentValues initialValues) {
throw new UnsupportedOperationException(
"nic nie zostaje wstawione, poniewa jest to wycznie osona");
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException(
"nic nie zostaje usunite, poniewa jest to wycznie osona");
}
public int update(Uri uri, ContentValues values,
String selection, String[] selectionArgs)
{
throw new UnsupportedOperationException(
"nic nie zostaje zaktualizowane, poniewa jest to wycznie osona");
}
}

Zwrmy uwag, e wymagany przez struktur aktywnego folderu zbir kolumn zostaje zainicjalizowany w kodzie z listingu 21.3, a na listingu 21.4 zostaje wywoany w postaci natychmiastowego odniesienia.
Listing 21.4. Kolumny potrzebne do wypenienia kontraktu aktywnego folderu
private static final String[] CURSOR_COLUMNS = new String[]
{
BaseColumns._ID,

730 Android 3. Tworzenie aplikacji


LiveFolders.NAME,
LiveFolders.DESCRIPTION,
LiveFolders.INTENT,
LiveFolders.ICON_PACKAGE,
LiveFolders.ICON_RESOURCE
};

Poza elementem INTENT przeznaczenie pozostaych obiektw jest oczywiste. Jeeli przyjrzymy
si rysunkowi 21.5, zauwaymy, e obiekt NAME dotyczy nazwy elementu na licie. Atrybut
DESCRIPTION zosta umieszczony na tej samej licie pod obiektem NAME.
Pole INTENT jest w rzeczywistoci polem typu string, wskazujcym identyfikator URI danego
elementu w dostawcy treci. W przypadku kliknicia tego elementu Android zastosuje dziaanie
VIEW poprzez ten identyfikator URI. Dlatego wanie pole to nosi nazw pola INTENT, poniewa
wewntrznie Android uzyska obiekt INTENT z identyfikatora URI.
Dwa ostatnie elementy s zwizane z obiektem ICON, wywietlanym jako cz listy. Przyjrzyjmy si ponownie rysunkowi 21.5, aby zobaczy ikony, oraz listingowi 21.3, aby sprawdzi,
w jaki sposb kolumny te dostarczaj wartoci z bazy kontaktw.
Zwrmy rwnie uwag, e klasa MyContactsContentProvider (osonowy dostawca treci)
wykonuje kod z listingu 21.5 wymuszajc na podstawowym kursorze obsug wszelkich zmian
danych.
Listing 21.5. Rejestrowanie identyfikatora URI za pomoc kursora
MatrixCursor mc = loadNewData(this);
mc.setNotificationUri(getContext().getContentResolver(),
Uri.parse("content://contacts/people/"));

Funkcja loadNewData() uzyskuje od dostawcy treci zbir kontaktw i tworzy obiekt Matrix
Cursor, ktrego kolumny s widoczne na listingu 21.4. Nastpnie obiekt ten otrzymuje
informacj, e ma si zarejestrowa wraz z klas ContentResolver, aby moga ona przekaza do kursora powiadomienie o jakiejkolwiek zmianie danych wskazywanych przez identyfikator URI (content://contacts/people).
Interesujcy jest fakt, e ledzonym identyfikatorem URI nie jest identyfikator naszego dostawcy treci MyContactsProvider, lecz identyfikator dostawcy treci kontaktw dostarczony
przez Androida. Wynika to z faktu, e dostawca MyContactsProvider stanowi jedynie oson
prawdziwego dostawcy treci. Zatem kursor ten musi ledzi waciwego dostawc treci,
a nie jego oson.
Wane jest rwnie, aby osoni obiekt MatrixCursor we wasnym kursorze, co zostao pokazane na listingu 21.6.
Listing 21.6. Osanianie kursora
MatrixCursor mc = loadNewData(this);
mc.setNotificationUri(getContext().getContentResolver(),
Uri.parse("content://contacts/people/"));
MyCursor wmc = new MyCursor(mc,this);

Rozdzia 21 Badanie aktywnych folderw

731

Aby zrozumie sens osaniania kursora, musimy dowiedzie si, w jaki sposb widoki przeprowadzaj aktualizacj zmienionej treci. Taki dostawca treci, jak Contacts, zazwyczaj rejestruje identyfikator URI jako cz implementacji metody query i w ten sposb powiadamia
kursor o potrzebie ledzenia zmian. Do tego suy metoda cursor.setNotificationUri. Kursor moe nastpnie zarejestrowa ten identyfikator URI oraz jego wszystkie podrzdne identyfikatory wraz z dostawc treci. Podczas przeprowadzenia na dostawcy treci operacji wstawienia
lub usunicia danych kod obsugujcy te operacje musi wprowadzi zdarzenie oznaczajce
zmian danych w krotkach definiowanych przez okrelony identyfikator URI.
W ten sposb kursor bdzie aktualizowany za pomoc operacji requery, a widok zostanie stosownie odwieony. Niestety, klasa MatrixCursor nie jest dostosowana do operacji requery.
Obsuguje j kursor SQLiteCursor, jednak nie moemy z niego tutaj skorzysta, poniewa odwzorowujemy kolumny zgodnie z nowym zestawem kolumn.
Aby pomin to ograniczenie, umiecilimy obiekt MatrixCursor w osonie kursora i przesonilimy metod requery w celu pozostawienia tego obiektu i utworzenia nowego, zawierajcego zaktualizowane dane. Chcemy rwnie, eby przy kadej zmianie danych by generowany
nowy obiekt MatrixCursor. Jednak do struktury aktywnego folderu w Androidzie zwracamy
jedynie zewntrzny kursor osaniajcy. Szkielet aktywnego folderu bdzie rozpoznawa tylko jeden kursor, w jego wntrzu jednak bd pojawiay si nowe kursory w miar wprowadzania
zmian w danych.
Do tego su dwie nastpne klasy.

MyCursor.java
Zauwamy, w jaki sposb jest inicjalizowany obiekt MyCursor zawierajcy na pocztku klas
MatrixCursor (listing 21.7). Podczas przeprowadzania operacji requery kursor MyCursor
bdzie zwrotnie wywoywa dostawc w celu przekazania obiektu MatrixCursor. Nowy obiekt
MatrixCursor zastpi stary za pomoc metody set.
Listing 21.7. Kod rdowy klasy MyCursor
public class MyCursor extends BetterCursorWrapper
{
private ContentProvider mcp = null;
public MyCursor(MatrixCursor mc, ContentProvider inCp)
{
super(mc);
mcp = inCp;
}
public boolean requery()
{
MatrixCursor mc = MyContactsProvider.loadNewData(mcp);
this.setInternalCursor(mc);
return super.requery();
}
}

732 Android 3. Tworzenie aplikacji


Moglibymy tego dokona, przesaniajc metod requery klasy MatrixCursor, klasa ta
nie moe jednak w aden sposb wyczyci danych i rozpocz dziaania od pocztku.
Jest to wic rozsdne obejcie (zwrmy uwag, e klasa MyCursor rozszerza klas
BetterCursorWrapper, co zostanie omwione w dalszej czci rozdziau).

Przyjrzymy si teraz klasie BetterCursorWrapper, aby pozna technik osaniania kursora.

BetterCursorWrapper.java
Klasa BetterCursorWrapper (listing 21.8) przypomina klas CursorWrapper struktury bazodanowej w Androidzie. Potrzebne s jednak dwa elementy, ktrych brakuje klasie Cursor
Wrapper. Po pierwsze, nie zawiera ona metody set, sucej do zastpienia wewntrznego
kursora, pochodzcego z metody requery. Po drugie, obiekt CursorWrapper nie jest czci
klasy CrossProcessCursor. Aktywne foldery wymagaj klasy CrossProcessCursor, a nie
zwykego kursora, poniewa przekraczaj one granice procesw.
Listing 21.8. Kod rdowy klasy BetterCursorWrapper
public class BetterCursorWrapper implements CrossProcessCursor
{

//Przechowuje wewntrzny kursor sucy do delegowania metod


protected CrossProcessCursor internalCursor;

//Konstruktor pobiera obiekt CrossProcessCursor w postaci danych wejciowych


public BetterCursorWrapper(CrossProcessCursor inCursor)
{
this.setInternalCursor(inCursor);
}

//Moemy zresetowa w jednej z metod klasy pochodnej


public void setInternalCursor(CrossProcessCursor inCursor)
{
internalCursor = inCursor;
}

//Tu znajduj si wszystkie delegowane metody


public void fillWindow(int arg0, CursorWindow arg1) {
internalCursor.fillWindow(arg0, arg1);
}

// ...inne delegowane metody


}

Na listingu 21.8 nie pokazalimy caej klasy BetterCursorWrapper, mona j jednak atwo
wygenerowa w rodowisku Eclipse. Po wczytaniu powyszego fragmentu umieszczamy kursor w zmiennej internalCursor. Klikamy prawym przyciskiem myszy i wybieramy opcj
Source/Generate Delegated Methods. W ten sposb zostanie zapeniona reszta klasy. Po wygenerowaniu delegowanych klas przez rodowisko Eclipse musimy je oddelegowa do wewntrznej klasy kursora, tak jak to zrobilimy w przypadku metody fillWindow z listingu 21.8 (jeeli
nie chcemy przeprowadza tego procesu, odpowiedni plik znajdziemy w pliku ZIP zawierajcym gotowy projekt).

Rozdzia 21 Badanie aktywnych folderw

733

Posiadamy teraz wszystkie klasy niezbdne do zbudowania, wdroenia i uruchomienia przykadowego projektu demonstrujcego dziaanie aktywnych folderw w rodowisku Eclipse.
Poniewa adna aktywno nie zostaa zarejestrowana w kategorii MAIN, nie ujrzymy interfejsu
uytkownika po wdroeniu projektu, ale w konsoli rodowiska Eclipse pojawi si informacja o jego
instalacji zakoczonej sukcesem.
Podsumujmy ten podrozdzia omwieniem zjawisk zachodzcych podczas uzyskiwania dostpu
do aktywnego folderu.

Testowanie aktywnych folderw


Po przygotowaniu wszystkich plikw projektu aktywnych folderw moemy je skompilowa
i wdroy na emulatorze. Jestemy teraz gotowi do wykorzystania utworzonego przez nas aktywnego folderu.
Przejdmy do ekranu startowego urzdzenia, powinien on przypomina zrzut z rysunku 21.1.
Przeprowadmy czynnoci wypunktowane na pocztku podrozdziau, w punkcie W jaki sposb
uytkownik korzysta z aktywnych folderw. Zlokalizujmy zwaszcza nasz aktywny folder
i utwrzmy jego ikon, tak jak pokazano na rysunku 21.4. Kliknijmy ikon Kontakty AF, a ujrzymy list zapenion kontaktami, podobnie jak na rysunku 21.5.

Instrukcje dotyczce kompilowania kodu


Najlepszym rozwizaniem pozwalajcym na manipulowanie kodem omwionym w tym rozdziale jest pobranie pliku ZIP, utworzonego specjalnie na potrzeby rozdziau. Adres URL do tego
pliku znajdziemy w podrozdziale Odnoniki. W pliku tym znajdziemy wszystkie klasy, jakie
zostay tutaj omwione.
W przeciwiestwie do wielu innych projektw opisywanych w ksice, ten nie posiada aktywnoci, ktra zostanie uruchomiona po wczeniu emulatora; jednak komunikaty w konsoli
rodowiska Eclipse poinformuj nas o zakoczonej sukcesem instalacji pakietw.

Odnoniki
Ponisze adresy mog si okaza bardzo przydatne podczas nauki korzystania z aktywnych
folderw oraz pracy z nimi:
http://developer.android.com/reference/android/provider/LiveFolders.html ten adres
umoliwi zapoznanie si z dokumentacj klasy LiveFolders.
http://developer.android.com/resources/articles/contacts.html w tym artykule
znajdziemy informacje dotyczce korzystania z interfejsu kontaktw. Okae si
on szczeglnie przydatny podczas pracy z aktywnymi folderami wykorzystujcymi
kontakty.
ftp://ftp.helion.pl/przyklady/and3ta.zip pod tym adresem znajduj si projekty
utworzone na potrzeby niniejszej ksiki. Waciwy plik zosta umieszczony w katalogu
o nazwie ProAndroid3_R21_AktywneFoldery.

734 Android 3. Tworzenie aplikacji

Podsumowanie
Dziki aktywnym folderom otrzymujemy innowacyjny, obsugiwany jednym klikniciem mechanizm, wywietlajcy zmienione dane na ekranie startowym. Potencjalnie moemy umieszcza tu dane dowolnego rodzaju pod warunkiem e istnieje moliwo ich zdefiniowania
w postaci listy tworzonej przez wiersze. Wszystkie dane musz posiada waciwoci nazwy
i opisu, pozwalajce na ich zidentyfikowanie. Niemal kady typ danych spenia ten wymg, gdy
mog one zosta w jaki sposb nazwane i opisane. Przydatna okazuje si take obecno aktywnoci wywietlajcej szczegowe informacje na temat kliknitego obiektu w aktywnym folderze. Dane mog by lokalne, na przykad kontakty, a nawet sieciowe, czego przykadem moe
by spis blogw.
Rozdzia ten zawiera opis nowoci w kursorach aktywnych folderw oraz mechanizmw wymaganych do wyeksponowania istniejcych dostawcw treci jako rde aktywnych folderw.
Wyjanilimy przyczyny osaniania kursorw, a take zaprezentowalimy sposb rejestrowania
klasy ContentResolver w celu otrzymywania aktualizacji danych.
Nastpny rozdzia zosta powicony kolejnej innowacji ekranu startowego, znanej pod nazw
widetw ekranu startowego (ang. home screen widgets).

R OZDZIA

22
Widety ekranu startowego

W niniejszym rozdziale szczegowo omwimy zagadnienie widetw ekranu


startowego. Podobnie jak przedstawione w rozdziale 21. aktywne foldery, tak i widety ekranu startowego stanowi kolejny sposb prezentowania czsto aktualizowanych danych na stronie startowej urzdzenia pracujcego pod kontrol
systemu Android. Oglnie rzecz ujmujc, widety ekranu startowego s odczonymi widokami (chocia wypenionymi danymi), wywietlanymi na ekranie
startowym. Dane znajdujce si w tych widokach s aktualizowane w regularnych
odstpach czasu przez procesy zachodzce w tle.
Na przykad widet poczty elektronicznej moe informowa uytkownika o liczbie
nieprzeczytanych wiadomoci, z podkreleniem, e widet moe przedstawia jedynie liczb tych wiadomoci, a nie ich tre. Kliknicie licznika wiadomoci moe
uruchomi aktywno wywietlajc tre wiadomoci. Mog to by nawet zewntrzne rda poczty e-mail, na przykad Yahoo, Gmail lub Hotmail, dopki urzdzenie posiada moliwo poczenia si z serwerem poczty za pomoc protokou HTTP albo innego mechanizmu sieciowego.
W wersji 3.0 Androida widety ekranu startowego uzyskay now funkcjonalno.
Te dodatkowe mechanizmy zostay omwione w rozdziale 31.

Rozdzia podzielimy na trzy czci. W pierwszym podrozdziale omwimy pojcie


widetw ekranu startowego oraz ich architektur. Wyjanimy, w jaki sposb Android wykorzystuje widok RemoteViews do wywietlania widetw oraz jak docza
odbiorcw komunikatw, sucych do aktualizowania tych widokw. Pokaemy
technik tworzenia aktywnoci umoliwiajcych konfigurowanie widetw na
ekranie startowym oraz zaobserwujemy zwizek pomidzy usugami a widetami.
Po przeczytaniu tego podrozdziau Czytelnik bdzie dobrze rozumia architektur
oraz cykl ycia widetw ekranu startowego.
W drugim podrozdziale zademonstrujemy sposb projektowania i rozwijania widetw ekranu startowego jak zwykle posuymy si przykadami kodu z komentarzem. Czytelnicy dowiedz si, jak mona definiowa widety w Androidzie oraz jak
pisa kod tworzcy odbiorcw komunikatw, ktre bd aktualizowa te widety.
Pokaemy metody zarzdzania stanem widetu za pomoc wspdzielonych preferencji oraz przedstawimy kod aktywnoci sucej do konfigurowania widetw.

736 Android 3. Tworzenie aplikacji


W podrozdziale trzecim zajmiemy si kwestiami przydatnoci i ogranicze, damy te oglne
wskazwki uatwiajce prac z widetami. Dodatkowo omwimy zakres i stosowalno widetw. Przedstawimy rwnie porady dotyczce pisania widetw wymagajcych bardzo czstych
aktualizacji.
Rozdzia zakoczymy list zasobw dotyczcych programowania widetw w Androidzie.

Architektura widetw ekranu startowego


Omwienie architektury widetw ekranu startowego rozpocznijmy od ustalenia ich dokadnej
definicji.

Czym s widety ekranu startowego?


Widety ekranu startowego s czsto aktualizowanymi widokami, wywietlanymi na ekranie
startowym. Skoro widet strony startowej jest widokiem, jego wygld i dziaanie s definiowane
w pliku XML ukadu graficznego. Poza tym ukadem graficznym bdziemy musieli jeszcze
zdefiniowa ilo zajmowanej przez widet przestrzeni na ekranie.
W definicji widetu zawarto rwnie kilka klas Java, zapewniajcych inicjalizacj widokw i jego
czste aktualizacje. Klasy te zarzdzaj cyklem ycia widetu na ekranie startowym. Reaguj
one na procesy przecigania widetu na ekran startowy oraz jego usuwania do kosza.
Widok oraz odpowiadajca mu klasa Java s zaprojektowane w taki sposb, e obydwa
obiekty s od siebie oddzielone. Na przykad kada usuga lub aktywno w Androidzie
moe odczyta widok za pomoc identyfikatora jego ukadu graficznego, wypeni go
danymi (podobnie jak w przypadku wypeniania szablonu), a nastpnie wysa go na ekran
startowy. Po przesaniu widetu na ekran startowy zostaje on oddzielony od obsugujcego
go kodu Java.

Najprostsza definicja widetu zawiera nastpujce elementy:


Ukad graficzny widoku wywietlany na ekranie startowym, a take okrelony rozmiar
jego dopasowania na stronie startowej. Pamitajmy, e jest to jedynie widok bez adnych
wstawionych danych. Zadaniem klasy Java bdzie jego aktualizacja.
Zegar okrelajcy czstotliwo aktualizacji.
Klasa Java nazywana dostawc widetu (ang. widget provider), reagujca na aktualizacje
zegara w celu zmiany widoku w okrelony sposb, umoliwiajcy zapenienie go danymi.
Po zdefiniowaniu widetu oraz wprowadzeniu klas Java bdzie on zdatny do uytku. Najpierw
jednak omwimy przykad widetu ekranu startowego, wykorzystywanego w praktyce.

W jaki sposb uytkownik


korzysta z widetw ekranu startowego?
Jedn z funkcjonalnoci widetu ekranu startowego w Androidzie jest moliwo umieszczenia
utworzonego fabrycznie widetu na ekranie startowym. Po umieszczeniu na ekranie startowym
mona go w razie koniecznoci skonfigurowa za pomoc aktywnoci (zdefiniowanej jako cz
pakietu widetu). Bardzo istotne jest zrozumienie tej interakcji przed waciwym zagbieniem
si w szczegy kodu widetu.

Rozdzia 22 Widety ekranu startowego

737

Innymi sowy, przed nauk programowania widetu naley si przekona, w jaki sposb si go
uytkuje.
W naszym przykadzie zajmiemy si widetem noszcym nazw Urodziny, utworzonym specjalnie na potrzeby tego rozdziau. W dalszej czci rozdziau zaprezentujemy jego kod rdowy.
Najpierw posuy nam on do wyjanienia zasady dziaania widetw. Kod rdowy tego widetu zamiecilimy w dalszej czci rozdziau, zatem teraz zalecamy Czytelnikowi skoncentrowanie si na treci rozdziau i ogldaniu zrzutw ekranu, a dopiero pniej na samodzielnym
testowaniu widetu. Jeeli Czytelnik przeanalizuje rysunki i doczone wyjanienia, nie powinien
mie problemw ze zrozumieniem natury i zachowania widetu Urodziny, dziki czemu
opanowanie kodu rdowego stanie si atwiejszym zadaniem.
Rozpocznijmy od zlokalizowania poszukiwanego widetu i utworzenia jego instancji na ekranie
startowym.

Utworzenie instancji widetu na ekranie startowym


Aby uzyska dostp do listy dostpnych widetw, naley poprzez dugie kliknicie na ekranie
startowym wywoa menu kontekstowe strony startowej, przedstawione na rysunku 22.1.

Rysunek 22.1. Menu kontekstowe ekranu startowego

Po wybraniu opcji Widety pojawi si kolejny ekran, zawierajcy list dostpnych widetw, co
zostao przedstawione na rysunku 22.2.
Wikszo widetw stanowi integraln cz Androida. Lista dostpnych widetw moe
wyglda inaczej w zalenoci od wersji uywanego oprogramowania. W celach demonstracyjnych wybralimy widok Birthday Widget. Po jego klikniciu zostanie utworzona odpowiednia
instancja widetu na ekranie startowym, wygldajca jak przykadowy widet Urodziny z rysunku 22.3.
W nagwku widetu Urodziny s wywietlane takie dane, jak imi osoby, liczba dni do jej urodzin, a take data urodzin oraz cze do sklepu z upominkami.

738 Android 3. Tworzenie aplikacji

Rysunek 22.2. Lista widetw ekranu startowego

Rysunek 22.3. Przykadowy widet

Moemy si zastanawia, w jaki sposb zostay skonfigurowane dane jubilata. Co naleaoby


zrobi w przypadku, gdyby potrzebne byy dwa wystpienia widetu, prezentujce informacje
o dwch rnych osobach? Do tego suy aktywno konfiguratora widetw, ktra jest tematem nastpnego podpunktu.
Widok utworzony na ekranie startowym dla tej definicji widetu nosi nazw instancji
widetu. Wynika z tego wniosek, e mona utworzy wicej definicji instancji tego widetu.

Rozdzia 22 Widety ekranu startowego

739

Konfigurator widetw
Definicja widetu moe opcjonalnie zawiera specyfikacj aktywnoci, zwanej aktywnoci konfiguratora widetw. Po wybraniu widetu z listy dostpnych widetw w celu utworzeniu jego
instancji Android wywouje powizan z nim aktywno konfiguracji widetu. Pokaemy, w jaki
sposb samodzielnie napisa tak aktywno. Umoliwia ona konfiguracj wystpienia widetu.
W przypadku naszego widetu urodzinowego aktywno konfiguracji wywietli monit o wprowadzenie imienia jubilata oraz daty jego urodzin, tak jak zostao pokazane na rysunku 22.4.
Zadaniem konfiguratora jest zapisanie tych informacji w staym miejscu, aby po wywoaniu
aktualizacji przez dostawc widetu dostawca ten mg zlokalizowa informacje i zaktualizowa
je o nowe wartoci, wstawiane nastpnie do widetu przez konfigurator.

Rysunek 22.4. Aktywno konfiguratora widetu


Jeeli uytkownik wybierze utworzenie dwch instancji widetu urodzinowego na
stronie startowej, aktywno konfiguratora zostanie wywoana dwukrotnie
(jednorazowo dla kadego wystpienia widetu).

Android wewntrznie ledzi instancje widetw poprzez przypisywanie im identyfikatora. Identyfikator ten jest przekazywany metodom zwrotnym kodu Java oraz klasie konfiguracyjnej w celu
skierowania pocztkowej konfiguracji i aktualizacji do waciwej instancji. Na rysunku 22.3,
w cigu znakw satya:3, cyfra 3 stanowi identyfikator widetu a cilej, identyfikator
instancji widetu. Sam za widet jest identyfikowany za pomoc nazwy (na ktr skada si nazwa klasy oraz pakietu, w ktrym ta klasa si znajduje); w tym rozdziale pojcia identyfikator
widetu oraz identyfikator instancji widetu s uywane zamiennie i odnosz si do identyfikatora wystpienia widetu. Identyfikator instancji widetu zosta pokazany na rysunku 22.3
w celach demonstracyjnych.
Po oglnym omwieniu widetu nadszed czas na szczegowe zapoznanie si z jego cyklem ycia.

740 Android 3. Tworzenie aplikacji

Cykl ycia widetu


Wspomnielimy kilkakrotnie o definicji widetu. Poruszylimy take temat roli klas Java. W tym
punkcie powicimy o wiele wicej uwagi obydwu zagadnieniom oraz przeledzimy cykl ycia
widetu.
Cykl ten skada si z nastpujcych faz:
1. Definiowanie widetu.
2. Tworzenie instancji widetu.
3. Zastosowanie metody onUpdate() (po wyganiciu interwau czasowego).
4. Odpowied na kliknicia (w widoku widetu na ekranie startowym).
5. Usunicie widetu (z ekranu startowego).
6. Odinstalowanie.
Omwimy teraz szczegowo kady z wymienionych etapw.

Faza definiowania widetu


Cykl ycia widetu rozpoczyna si od zdefiniowania widoku widetu. W definicji tej okrelamy
nazw widetu wywietlan na licie dostpnych widetw (rysunek 22.2), wywoywanej z poziomu ekranu startowego. Do utworzenia definicji wymagane s dwa elementy: klasa Java
implementujca dostawc AppWidgetProvider oraz ukad graficzny widetu.
Rozpoczniemy definiowanie widetu od nastpujcego wpisu w pliku manifecie, definiujcego
dostawc AppWidgetProvider (listing 22.1).
Listing 22.1. Definicja widetu w pliku manifecie Androida
<manifest..>
<application>
....
<receiver android:name=".BDayWidgetProvider">
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/bday_appwidget_provider" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
</receiver>
...
<activity>
.....
</activity>
<application>
</manifest>

Definicja ta wskazuje istnienie odbiorcy komunikatw klasy Java noszcego nazw BdayWidget
Provider (jak si przekonamy, wywodzi si on z podstawowej klasy Androida AppWidget
Provider mieszczcej si w pakiecie widget), ktry odbiera komunikaty zawierajce aktualizacje
widetu.

Rozdzia 22 Widety ekranu startowego

741

Android dostarcza informacje o aktualizacji w formie komunikatw rozgoszeniowych,


generowanych na podstawie czstoci interwaw czasowych.

Definicja widetu z listingu 22.1 jest rwnie zwizana z plikiem XML w katalogu /res/xml,
ktry z kolei okrela widok widetu oraz czstotliwo odwieania, co zostao zaprezentowane
na listingu 22.2.
Listing 22.2. Definicja widoku widetu w pliku XML informacji o dostawcy widetu
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="150dp"
android:minHeight="120dp"
android:updatePeriodMillis="43200000"
android:initialLayout="@layout/bday_widget"
android:configure="com.ai.android.BDayWidget.ConfigureBDayWidgetActivity"
>
</appwidget-provider>

Plik ten jest nazywany plikiem informacji o dostawcy widetu. Zostaje on wewntrznie przetumaczony na klas Java AppWidgetProviderInfo. Wartoci szerokoci i wysokoci ukadu graficznego zostaj tu ustalone odpowiednio na 150dp i 120dp. Zostaje tu rwnie okrelony wyraony
w milisekundach czas przedziau czasowego rwny 12 godzinom. Definicja ta wskazuje take
plik ukadu graficznego (listing 22.7), opisujcy widok widetu (rysunek 22.5).
Zauwamy jednak, e ukad graficzny tych widokw widetw moe zawiera jedynie niektre
rodzaje elementw widoku. Dopuszczalne kontrolki w ukadzie graficznym widetu nale
wycznie do klasy widokw znanej jako RemoteViews ta klasa widokw zdalnych akceptuje
jedynie okrelone rodzaje widokw potomnych. Te dopuszczalne podelementy widoku s wypisane na listingu 22.3.
Listing 22.3. Dopuszczalne kontrolki widoku w klasie RemoteViews
FrameLayout
LinearLayout
RelativeLayout
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView

Powysza lista moe w przyszoci rozrasta si z kad kolejn wersj rodowiska SDK. Zasadniczym powodem ograniczenia dopuszczalnych typw elementw w widoku zdalnym jest fakt, e s
one odcite od kontrolujcych je procesw. Te widoki widetw s obsugiwane przez takie aplikacje, jak na przykad Home. Kontrolerami tych widokw s przetwarzane w tle procesy, wywoywane przez zegary. Z tego powodu obiekty te nosz nazw widokw zdalnych. Istnieje odpowiednia klasa Java, nazwana RemoteViews, udzielajca dostpu do tych widokw. Innymi sowy,
programici nie musz uzyskiwa bezporedniego dostpu do tych widokw, aby wywoa wobec
nich metody. Dostp do nich nastpuje wycznie poprzez klas RemoteViews (peni rol bramki).

742 Android 3. Tworzenie aplikacji


Metody klasy RemoteViews zostan omwione w nastpnym podrozdziale, podczas prezentowania przykadowego widetu. Na razie wystarczy pamita, e w pliku ukadu graficznego widetu moemy umieci ograniczony zestaw widokw (listing 22.3).
Definicja widetu (listing 22.2) zawiera rwnie specyfikacj aktywnoci konfiguracyjnej,
ktra musi zosta wywoana podczas tworzenia instancji widetu przez uytkownika. Ta
aktywno na listingu 22.2 nosi nazw ConfigureBDayWidgetActivity. Jest to standardowa
aktywno zawierajca kilka pl formularza. Pola te maj na celu uzyskanie od uytkownika
informacji wymaganych przez instancj widetu.

Faza tworzenia instancji widetu


Po utworzeniu wszystkich elementw XML wymaganych przez definicj widetu oraz udostpnieniu wszystkich klas Java widetw moemy si przyjrze, co si stanie po wybraniu przez
uytkownika nazwy widetu z listy dostpnych widetw (rysunek 22.2). Android wywouje
aktywno konfiguratora (rysunek 22.3), ktra przeprowadza nastpujce czynnoci:
1. Odebranie identyfikatora instancji widetu od intencji wywoujcej, ktra uruchomia
konfigurator.
2. Zebranie informacji potrzebnych instancji widetu za pomoc formularza
wywietlonego uytkownikowi.
3. Zachowanie uzyskanych przez widet informacji. Dostp do nich bdzie potrzebny
podczas wywoania metody update.
4. Przygotowanie do wywietlania widoku widetu po raz pierwszy poprzez odczytanie
ukadu graficznego tego widoku i utworzenie obiektu RemoteViews.
5. Wywoanie metod klasy RemoteViews ustanawiajcych wartoci pojedynczych
obiektw widoku, takich jak tekst, obraz i tak dalej.
6. Wykorzystanie obiektu RemoteViews do zarejestrowania wszelkich zdarze onClick
wobec dowolnego podelementu widetu.
7. Wywoanie klasy AppWidgetManager w celu narysowania obiektu klasy RemoteViews
na stronie startowej z wykorzystaniem identyfikatora instancji tego widetu.
8. Powrt do identyfikatora widetu i zakoczenie dziaania.
Zwrmy uwag, e w tym przypadku pierwsze rysowanie jest wykonane przez konfigurator,
a nie przez metod onUpdate() klasy AppWidgetProvider.
Aktywno konfiguratora jest elementem dodatkowym. Jeeli nie zostanie ona
zdefiniowana, wywoanie przejdzie bezporednio do metody onUpdate() klasy
AppWidgetProvider. Metoda onUpdate() suy do aktualizowania widoku.

Android bdzie powtarza ten proces dla kadej instancji widetu utworzonej przez uytkownika. Warto rwnie zauway, e nie istniej udokumentowane ograniczenia zmuszajce uytkownika do korzystania z tylko jednej instancji widetu.
Poza przywoywaniem aktywnoci konfiguratora Android wywouje zwrotnie rwnie metod onEnabled klasy AppWidgetProvider. Powimy chwil metodom zwrotnym klasy
AppWidgetProvider i przyjrzyjmy si powoce naszego dostawcy BDayWidgetProvider (listing
22.4). Peny kod tego pliku zosta umieszczony na listingu 22.9.

Rozdzia 22 Widety ekranu startowego

743

Listing 22.4. Powoka dostawcy widetu


public class BDayWidgetProvider extends AppWidgetProvider
{
public void onUpdate(Context context,
AppWidgetManager appWidgetManager,
int[] appWidgetIds){}
public void onDeleted(Context context, int[] appWidgetIds){}
public void onEnabled(Context context){}
public void onDisabled(Context context) {}
}

Metoda zwrotna onEnabled() wskazuje, e istnieje co najmniej jedna instancja widetu dziaajca na ekranie startowym. Oznacza to, e uytkownik musia umieci na stronie startowej przynajmniej jeden widet. A zatem w wywoaniu musimy uruchomi otrzymywanie komunikatw dla tego skadnika (z listingu 22.9 dowiemy si, jak tego dokona). W Androidzie
klasy s nazywane czasami skadnikami, szczeglnie gdy tworz wielokrotnie wykorzystywane
jednostki, takie jak aktywno, usuga lub odbiorca transmisji. W naszym przypadku klasa
AppWidgetProvider jest skadnikiem odbiorcy transmisji; moemy j wcza lub wycza
w celu otrzymywania transmitowanych komunikatw.
Metoda zwrotna onDeleted() jest wywoywana podczas przenoszenia przez uytkownika
instancji widetu do kosza. To wanie tu musimy usun wszystkie przechowywane wartoci
dla instancji widetu.
Metoda zwrotna onDisabled() jest wywoywana po usuniciu ostatniej instancji widetu
z ekranu startowego. Nastpuje to w momencie przeniesienia ostatniej instancji do kosza. Powinnimy uywa tej metody do wyrejestrowania procesu otrzymywania transmitowanych
komunikatw przez ten skadnik (zobaczymy to na listingu 22.9).
Metoda zwrotna onUpdate() jest wywoywana za kadym razem, gdy wyganie zegar zaprezentowany na listingu 22.2. Metoda ta jest rwnie wywoywana na samym pocztku, podczas generowania instancji widetu, w przypadku gdy nie zdefiniowalimy aktywnoci konfiguratora. Jeeli aktywno konfiguratora jest dostpna, ta metoda nie jest wywoywana podczas
procesu utworzenia instancji widetu. Nastpnie bdzie ona wywoywana z czstotliwoci
rwn wyganiciom zegara.

Faza metody onUpdate


Nastpnym wanym zdarzeniem po wywietleniu instancji widetu na ekranie startowym jest
wyganicie zegara. Jak ju wspomnielimy, zostaje wtedy wywoana metoda onUpdate().
Do jej wywoania suy odbiorca komunikatw. Oznacza to, e zostanie wczytany waciwy
proces Java, w ktrym jest zdefiniowana metoda onUpdate(), i bdzie on trwa a do chwili
zakoczenia wywoania. Po zwrocie wywoania proces ten bdzie gotowy do zamknicia.
Zalecane jest take wprowadzenie mechanizmu wykorzystujcego na przykad dugoterminowych odbiorcw komunikatw (rozdzia 14.), jeeli przetwarzanie odpowiedzi duszych od
dziesiciu sekund ma zosta zakoczone sukcesem. W przeciwnym razie zostanie wywietlony
komunikat o bdzie ANR (Android nie odpowiada).

744 Android 3. Tworzenie aplikacji


W kadym razie po otrzymaniu danych niezbdnych do zaktualizowania widetu za pomoc
metody onUpdate() moemy przywoa klas AppWidgetManager, aby narysowaa zdalny widok. Gdybymy do przeprowadzenia aktualizacji wykorzystali dugoterminow usug, musielibymy przekaza intencji uruchamiajcej t usug identyfikator widetu w postaci dodatkowych danych.
Chcemy w ten sposb pokaza, e klasa AppWidgetProvider jest bezstanowa i by moe nie
bdzie nawet zdolna do utrzymania zmiennych statycznych pomidzy wywoaniami. Powodem
jest moliwo zamknicia procesu Java zawierajcego t klas odbiorcy komunikatw oraz jego
rekonstrukcji pomidzy dwoma wywoaniami, co powoduje ponown inicjalizacj zmiennych
statycznych.
W wyniku tego musimy w razie potrzeby wprowadzi schemat zapamitywania stanw. Jeeli
aktualizacje nie s zbyt czste powiedzmy, co kilka sekund sensownym rozwizaniem jest
zapisywanie stanu instancji widetu w trwaym magazynie, na przykad pliku, we wspdzielonych preferencjach lub w bazie danych SQLite. W nastpnym przykadzie wykorzystamy do
tego celu wspdzielone preferencje.
Aby zaoszczdzi energi, bardzo zalecane jest ustalenie interwaw aktualizacji
duszych ni godzina, dziki czemu urzdzenie nie bdzie zbyt czsto wychodzio
ze stanu wstrzymania. W dokumentacji Androida widnieje rwnie ostrzeenie,
e w przyszych wersjach oprogramowania moe zosta wprowadzone ustawienie
minimalnego interwau rwnego 30 minutom lub duszego.

W przypadku krtszych przedziaw czasowych, rzdu pojedynczych sekund, musimy samodzielnie wywoa metod onUpdate() za pomoc funkcji dostpnych w klasie AlarmManager.
Jeeli korzystamy z klasy AlarmManager, zamiast wywoywaniem metody onUpdate() mona
si posuy metodami zwrotnymi alarmu. Stosowanie menedera alarmu zostao omwione
w rozdziale 15.
Poniej wymienilimy standardowe czynnoci wymagane podczas pracy z metod onUpdate():
1. Upewnij si, e konfigurator zakoczy prac; jeli nie skoczy, po prostu sprawd
to ponownie po chwili. Nie powinno to stanowi problemu w wersji oprogramowania
co najmniej 2.0, gdzie s oczekiwane dusze interway czasowe. Jeeli bdzie inaczej,
istnieje moliwo, e metoda onUpdate() zostanie wywoana przed zakoczeniem
procesu konfiguracji widetu przez uytkownika w konfiguratorze.
2. Pobierz dane przechowywane dla tej instancji widetu.
3. Pobierz ukad graficzny widoku widetu i utwrz wraz z nim obiekt RemoteViews.
4. Wywoaj metody klasy RemoteViews w celu ustawienia wartoci dla pojedynczych
obiektw widoku, na przykad dla tekstu, obrazu i tak dalej.
5. Zarejestruj wszelkie zdarzenia onClick w dowolnym widoku poprzez wykorzystanie
oczekujcych intencji.
6. Zaprogramuj klas AppWidgetManager, aby narysowaa obiekt RemoteViews,
korzystajc z identyfikatora instancji.
Jak wida, istnieje dua zbieno pomidzy dziaaniem konfiguratora a dziaaniem metody
onUpdate(). Istnieje moliwo wykorzystywania tej zbienoci.

Rozdzia 22 Widety ekranu startowego

745

Metody zwrotnych zdarze generowanych


przez kliknicie widoku widetu
Ustalilimy ju, e metoda onUpdate() na bieco aktualizuje widoki widetu. Widok widetu
oraz jego podelementy mog posiada metody zwrotne rejestrowania klikni przycisku myszy.
Zazwyczaj do zarejestrowania dziaania takiego jak kliknicie suy przetwarzana intencja. Dziaanie to moe nastpnie uruchomi usug lub wykona konkretn czynno, na przykad otwarcie okna przegldarki.
Taka wywoana usuga lub aktywno moe nastpnie nawiza w razie potrzeby komunikacj
z widokiem za pomoc identyfikatora instancji widetu i klasy AppWidgetManager. Wane jest
zatem, aby przetwarzana intencja zawieraa identyfikator instancji widetu.

Usunicie instancji widetu


Kolejnym elementem cyklu ycia instancji widetu jest jej usunicie. Aby tego dokona, uytkownik musi klikn widet ekranu startowego. U spodu ekranu zostanie wywietlony kosz.
Mona do niego przenie instancj widetu w celu jej usunicia z ekranu.
Powoduje to rwnie wywoanie metody onDeleted() wobec dostawcy widetu. Jeeli zachowalimy dla tej instancji jakie informacje o stanie, musimy usun te dane w metodzie onDeleted.
Android wywouje take metod onDisabled() w przypadku usunicia ostatniej instancji danego typu. Ta metoda zwrotna suy do czyszczenia wszystkich atrybutw przechowywanych
dla kadej instancji widetu oraz do wyrejestrowania metod zwrotnych z transmisji metody
onUpdate() (listing 22.9).

Odinstalowanie pakietw widetw


Przedstawilimy peny cykl ycia widetu. Zanim przejdziemy do nastpnej czci, krtko
wspomnimy o potrzebie uporzdkowania widetw w przypadku planowania odinstalowania
oraz zainstalowania nowej wersji pliku .apk, zawierajcego te widety.
Zalecane jest usunicie wszystkich instancji widetw przed prb odinstalowania pakietu. Naley postpowa zgodnie z instrukcjami opisanymi w podpunkcie Usunicie instancji widetu
a do usunicia ostatniej instancji.
Teraz moemy odinstalowa star wersj i zainstalowa now. Jest to szczeglnie istotne
w przypadku osb wykorzystujcych do tworzenia widetw wtyczk Eclipse ADT, poniewa
w trakcie projektowania prbuje ona przeprowadzi ten proces podczas kadego uruchomienia
aplikacji. Zatem pomidzy momentami dziaania aplikacji naley usun instancje widetu.

Przykadowy widet
Do tej pory przedstawilimy podstawy teoretyczne, pokazalimy te sposoby tworzenia widetw. Wykorzystajmy t wiedz do utworzenia przykadowego widetu, ktrego zachowanie
byo wykorzystane jako przykad podczas omawiania architektury widetw. Zaprojektujemy,
wdroymy i przetestujemy ten stary-nowy widet Urodziny.

746 Android 3. Tworzenie aplikacji


Kada instancja widetu bdzie wywietlaa imi jubilata, dat jego urodzin oraz liczb dni, ktre
pozostay do tego wita. Zostanie take utworzony obszar onClick, ktrego kliknicie pozwoli
na wizyt w sklepie w celu zakupu upominku. Z tego wanie wzgldu zostanie uruchomiona
przegldarka wywietlajca stron http://www.google.com.
Kocowy ukad graficzny widetu zosta zilustrowany na rysunku 22.5.

Rysunek 22.5. Wygld i dziaanie widetu urodzinowego

Implementacja tego widetu skada si z wymienionych poniej plikw. W zalenoci od stosowanego rdowego pakietu Java pliki Java bd przechowywane w podkatalogu src wraz ze
struktur katalogw, ktr wykorzystamy w przypadku tych pakietw. W celu zachowania
zwizoci te podkatalogi s reprezentowane przez wyraenie ....

AndroidManifest.xml // jest tu zdefiniowany dostawca AppWidgetProvider


(listing 22.5).

res/xml/bday_appwidget_provider.xml // wymiary i ukad graficzny widetu


(listing 22.6).

res/layout/bday_widget.xml // ukad graficzny widetu (listing 22.7).

res/drawable/box1.xml // zapewnia ramki dla sekcji ukadu graficznego widetu


(listing 22.8).

src//BDayWidgetProvider // implementacja klasy AppWidgetProvider


(listing 22.9).

W implementacji zawarte s rwnie nastpujce pliki zarzdzajce stanem widetu:

src//IWidgetModelSaveContract // model kontraktu zachowywania widetu


(listing 22.10).

src//APrefWidgetModel // abstrakcyjny model widetu opartego na preferencjach


(listing 22.11).

src//BDayWidgetModel // model widetu przechowujcego dane dla jego


widoku (listing 22.12).

src//Utils.java // kilka klas uytkowych (listing 22.13).

Ponadto w implementacji zostay umieszczone nastpujce pliki, odpowiedzialne za aktywno


konfiguracji:

src//ConfigureBDayWidgetActivity.java // aktywno konfiguracji (listing 22.14).

layout/edit_bday_widget.xml // ukad graficzny do wpisywania imienia jubilata


i daty urodzin (listing 22.15).

Omwimy kady z wymienionych plikw oraz wyjanimy wszelkie pominite do tej pory pojcia. Pod koniec lektury tego podrozdziau Czytelnik zapewne bdzie mg utworzy te pliki
i przetestowa widet urodzinowy we wasnym rodowisku.

Rozdzia 22 Widety ekranu startowego

747

Definiowanie dostawcy widetu


Definiowanie widetu rozpoczyna si w pliku manifecie aplikacji Androida. To wanie tutaj
okrelamy dostawc widetu, aktywno konfiguracji widetu oraz wskanik pliku XML
definiujcego ukad graficzny widetu.
Wszystkie te elementy zostay zaznaczone pogrubion czcionk w pliku manifecie, widocznym na listingu 22.5. Zwrmy uwag na definicj dostawcy BDayAppWidgetProvider
penicego funkcj odbiorcy komunikatw, a take na definicj aktywnoci konfiguracji
ConfigureBDayWidgetActivity.
Listing 22.5. Plik manifest przykadowej aplikacji BDayWidget
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ai.android.BDayWidget"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="Urodziny">

<!-**********************************************************************
* Odbiorca komunikatw dostawcy widetu urodzinowego
**********************************************************************
-->
<receiver android:name=".BDayWidgetProvider">
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/bday_appwidget_provider" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
</receiver>

<!-**********************************************************************
* Aktywno konfiguratora widetu urodzinowego
**********************************************************************
-->
<activity android:name=".ConfigureBDayWidgetActivity"
android:label="Konfiguruj widet Urodziny">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

Po przejrzeniu pliku manifestu okazuje si, e odbiorca jest wzem rwnorzdnym


z wzem aktywnoci. Jest on rwnie bezporednim potomkiem wza aplikacji.

748 Android 3. Tworzenie aplikacji


Etykieta aplikacji Urodziny, widoczna w wierszu:
<application android:icon="@drawable/icon" android:label="Urodziny">

stanowi nazw widetu wywietlan na licie dostpnych widetw (rysunek 22.2). Jeeli tworzymy po raz pierwszy definicj widetu, upewnijmy si, e poniszy wiersz zostanie dokadnie
skopiowany:
<meta-data android:name="android.appwidget.provider"

Okrelenie android.appwidget.provider jest charakterystyczne dla Androida i powinno zosta umieszczone w kodzie; to samo dotyczy poniszego fragmentu:
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

Definicja aktywnoci konfiguracji nie rni si od standardowej aktywnoci poza koniecznoci


zadeklarowania moliwoci jej odpowiedzi na dziaania APPWIDGET_CONFIGURE.

Definiowanie rozmiaru widetu


Chocia w pliku manifecie zdefiniowano dostawc widetu, szczegy dotyczce jego ukadu
graficznego umieszczono w osobnym pliku XML. Do dodatkowych informacji zaliczamy rozmiar widetu, nazw jego pliku ukadu graficznego, przedzia czasu aktualizacji oraz nazw
skadnika (lub klasy) aktywnoci konfiguracji.
Taki dodatkowy plik XML jest wskazywany przez wze android:resource, widoczny w uprzednio omwionej definicji dostawcy widetu (listing 22.5). Na listingu 22.6 zostaa przedstawiona
zawarto pliku informacyjnego dostawcy widetu (/res/xml/bday_appwidget_provider.xml).
Listing 22.6. Definicja widoku widetu BDayWidget
<!-- res/xml/bday_appwidget_provider.xml -->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="150dp"
android:minHeight="120dp"
android:updatePeriodMillis="4320000"
android:initialLayout="@layout/bday_widget"
android:configure="com.ai.android.BDayWidget.ConfigureBDayWidgetActivity"
>
</appwidget-provider>

W pliku tym okrela si w pikselach szeroko i wysoko widetu, jednak Android zaokrgli te
wymiary do rozmiaru najbliszej wielokrotnoci komrki. Obszar ekranu startowego jest zorganizowany w macierz komrek: kada komrka stanowi kwadrat o boku 74 dp (piksele niezalene od gstoci). W dokumentacji Androida mona znale zalecenie, aby wymiary tworzonych widetw stanowiy wielokrotno wymiarw tych komrek minus 2 piksele (margines
dla zaokrglenia rogw i tak dalej).
Mona tu take znale warto czstotliwoci wywoywania metody onUpdate(). Zaleca si,
aby ta warto nie przekraczaa kilku razy na dob. Wprowadzenie wartoci 0 oznacza cakowity
brak wywoywania aktualizacji. Mona j wykorzysta, w przypadku gdy chcemy sami
kontrolowa aktualizacje za pomoc klasy AlarmManager.

Rozdzia 22 Widety ekranu startowego

749

Warto atrybutu initialLayout wskazuje rzeczywisty ukad graficzny widetu (listing 22.7).
Natomiast atrybut configure okrela klas aktywnoci konfiguracji. Naley umieci pen nazw tej klasy w tej definicji.
Przyjrzyjmy si teraz waciwemu ukadowi graficznemu widetu.

Pliki zwizane z ukadem graficznym widetu


Z poprzedniego punktu i z listingu 22.6 wiemy, e ukad graficzny widetu jest skonfigurowany
w okrelonym pliku. Plik ten jest standardowym plikiem ukadu graficznego widoku w Androidzie.
Jednak w celu ustandaryzowania procesu tworzenia widetw Android opublikowa zestaw
wytycznych projektowania. Wytyczne te mona znale pod adresem:
http://developer.android.com/guide/practices/ui_guidelines/widget_design.html
Oprcz wytycznych pod tym adresem umieszczono zbir te widokw, poprawiajcych wygld i dziaanie widetw. W naszym przykadzie poszlimy inn ciek i wykorzystalimy
tradycyjny sposb implementowania ukadw graficznych, w ktrych tami s ksztaty.

Plik ukadu graficznego widetu


Na listingu 22.7 zaprezentowalimy tre pliku tworzcego ukad graficzny widetu, ukazany
na rysunku 22.5.
Listing 22.7. Definicja ukadu graficznego widoku widetu BDayWidget
<!-- res/layout/bday_widget.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="150dp"
android:layout_height="120dp"
android:background="@drawable/box1"
>
<TextView
android:id="@+id/bdw_w_name"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:text="Anonim"
android:background="@drawable/box1"
android:gravity="center"
/>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="60dp"
>
<TextView
android:id="@+id/bdw_w_days"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="0"
android:gravity="center"
android:textSize="30sp"

750 Android 3. Tworzenie aplikacji


android:layout_weight="50"
/>
<TextView
android:id="@+id/bdw_w_button_buy"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:textSize="20sp"
android:text="Kup"
android:layout_weight="50"
android:background="#FF6633"
android:gravity="center"
/>
</LinearLayout>
<TextView
android:id="@+id/bdw_w_date"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:text="1/1/2000"
android:background="@drawable/box1"
android:gravity="center"
/>
</LinearLayout>

Aby osign zamierzony efekt, ukad graficzny wykorzystuje zagniedone wzy LinearLayout.
Niektre kontrolki uywaj rwnie pliku definicji ksztatu box1.xml do zdefiniowania granic.

Plik ksztatu ta widetu


Kod tej definicji ksztatu zosta umieszczony na listingu 22.8 (plik ten powinien si znajdowa
w podkatalogu /res/drawable).
Listing 22.8. Definicja ksztatu krawdzi
<!-- res/drawable/box1.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="4dp" android:color="#888888" />
<padding android:left="2dp" android:top="2dp"
android:right="2dp" android:bottom="2dp" />
<corners android:radius="4dp" />
</shape>

Skorzystalimy z takiej metody tworzenia ukadu graficznego, poniewa jest przydatna nie tylko
w przypadku widetw, lecz take do tworzenia innych ukadw graficznych.
Dobrze jest utworzy aktywno i oddzielnie przetestowa ukady graficzne przed wprowadzeniem ich do widetu (przynajmniej my tak zrobilimy). Uzyskanie odpowiedniego wygldu i dziaania widetu kosztowao nas wiele prb. Bezporednie eksperymentowanie na widetach
moe si okaza dosy nuc czynnoci; po kadym uruchomieniu aplikacji naley kolejno
usun, odinstalowa, zainstalowa i ponownie umieci widety na stronie startowej.
Do tej pory omwione pliki stanowi pene definicje XML, niezbdne do dziaania typowego
widetu. Zobaczmy teraz, w jaki sposb bdziemy reagowa na wydarzenia cyklu ycia widetu.
Aby si tego dowiedzie, przebadamy klasy dostawcy widetu.

Rozdzia 22 Widety ekranu startowego

751

Implementacja dostawcy widetu


Przy okazji omawiania architektury widetu zajlimy si zadaniami klasy dostawcy widetu.
Dostawca ten musi posiada zaimplementowane nastpujce metody zwrotne odbiorcy komunikatw:
onUpdate(),
onDeleted(),
onEnabled(),
onDisabled().
Kod Java zaprezentowany na listingu 22.9 przedstawia implementacj kadej z tych metod.
Listing 22.9. Przykadowy dostawca widetu BDayWidgetProvider
///src/<your-package>/BDayWidgetProvider.java
public class BDayWidgetProvider extends AppWidgetProvider
{
private static final String tag = "BDayWidgetProvider";
public void onUpdate(Context context,
AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
final int N = appWidgetIds.length;
for (int i=0; i<N; i++)
{
int appWidgetId = appWidgetIds[i];
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
public void onDeleted(Context context, int[] appWidgetIds)
{
final int N = appWidgetIds.length;
for (int i=0; i<N; i++)
{
BDayWidgetModel bwm =
BDayWidgetModel.retrieveModel(context, appWidgetIds[i]);
bwm.removePrefs(context);
}
}
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
Bundle extras = intent.getExtras();
final int appWidgetId = extras.getInt
(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
this.onDeleted(context, new int[] { appWidgetId });
}
}
else {
super.onReceive(context, intent);

752 Android 3. Tworzenie aplikacji


}
}
public void onEnabled(Context context) {
BDayWidgetModel.clearAllPreferences(context);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(
new ComponentName("com.ai.android.BDayWidget",
".BDayWidgetProvider"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
public void onDisabled(Context context) {
BDayWidgetModel.clearAllPreferences(context);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(
new ComponentName("com.ai.android.BDayWidget",
".BDayWidgetProvider"),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
private void updateAppWidget(Context context,
AppWidgetManager appWidgetManager,
int appWidgetId) {
BDayWidgetModel bwm = BDayWidgetModel.retrieveModel(context, appWidgetId);
if (bwm == null) {
return;
}
ConfigureBDayWidgetActivity
.updateAppWidget(context, appWidgetManager, bwm);
}
}

W podrozdziale Architektura widetw ekranu startowego omwilimy dziaanie kadej z tych


metod. W naszym widecie urodzinowym metody te korzystaj z kolei z metod umieszczonych
w klasie BDayWidgetModel. Wrd tych metod s takie, jak removePrefs(), retrievePrefs()
oraz clearAllPreferences().
Klasa BDayWidgetModel suy do obudowania stanu instancji widetu (klasa ta zostanie omwiona za chwil). Aby zrozumie t klas dostawcy widetw, wystarczy wiedzie, e korzystamy
z klasy modelu do odczytywania danych wymaganych przez instancj widetu. Dane te s przechowywane w preferencjach. Dlatego wanie metody tej klasy nosz nazwy removePrefs(),
retrievePrefs() i clearAllPreferences(). Nazwy stayby si bardziej zrozumiae, gdybymy
zamiast sowa Prefs (preferencje) wprowadzili Data (dane), w wyniku czego metody te nosiyby
nazwy odpowiednio: removeData(), retrieveData(), clearAllData(). Takie przeksztacenie
peni jedynie rol pogldow i nie znajdziemy metod zawierajcych przyrostek Data().
Jak ju stwierdzilimy, metoda aktualizacji jest wywoywana dla wszystkich instancji widetu.
Metoda ta musi zaktualizowa wszystkie wystpienia widetu. Instancje te s przekazywane
w postaci tabeli identyfikatorw. Metoda onUpdate() zlokalizuje dla kadego atrybutu id
odpowiedni model instancji widetu i wywoa t sam metod, ktra jest uywana przez
aktywno konfiguratora (listing 22.14) w celu wywietlenia uzyskanego modelu widetu.

Rozdzia 22 Widety ekranu startowego

753

W metodzie onDeleted() utworzylimy obiekt BDayWidgetModel, a nastpnie zaprogramowalimy jego automatyczne skasowanie z magazynu przechowywanych preferencji.
Poniewa metoda onEnabled() jest wywoywana tylko raz, podczas tworzenia pierwszej instancji, wyczycilimy wszystkie przechowywane dane modeli widetu. Instancja ta zatem rozpoczyna dziaanie jako czysta bez danych. Taka sama czynno jest przeprowadzana w przypadku metody onDisabled(), dziki czemu pami po instancjach widetw zostaje cakowicie
oczyszczona.
W metodzie onEnabled() uruchamiamy dostawc treci, ktry moe teraz odbiera transmitowane komunikaty. Metoda onDisabled() wycza ten skadnik, wic nie bdzie on ju wyszukiwa rozgaszanych komunikatw.
Szczeglnym przypadkiem jest metoda onReceive(). Przed wprowadzeniem
oprogramowania w wersji 1.6 wystpowa bd uniemoliwiajcy wywoanie metody
onDeleted(). Mona byo obej ten problem poprzez jawne dostarczenie metody
onReceive(). Od wersji 1.6 Androida metoda ta staa si zbyteczna; wystarczajca
okazuje si ta sama metoda z bazowej klasy.

Dziki implementacji modeli widetw kod pozostaje czysty. Zajmiemy si teraz kwesti modeli
widetw i sposobem ich implementacji.

Implementacja modeli widetw


Czym jest model widetu? Nie jest to pojcie swoiste dla Androida. Osoby majce styczno
z tradycyjnym programowaniem interfejsw UI z pewnoci znaj pojcie wzorca programowania MVC (ang. Model-View-Controller model-widok-kontroler). Zgodnie z tym wzorcem
model przechowuje dane wymagane przez widok; widok zapewnia prawidowe wywietlanie;
natomiast kontroler poredniczy w komunikacji pomidzy modelem a widokiem.
Chocia Android nie zapewnia obsugi takiego rozwizania, wykorzystalimy wzorzec MVC,
aby uproci programowanie widetw. W tym podejciu kady widok instancji widetu posiada
ekwiwalentn klas Java, zwan modelem widetu. Model ten zawiera wszystkie metody suce
do zapewnienia danych, ktre bd wymagane do dziaania instancji widetu.
Modele te otrzymay take pewne podstawowe klasy, umoliwiajce samoistne ich zapisywanie
i odczytywanie z trwaych magazynw, na przykad wspdzielonych preferencji. Przeledzimy hierarchi klas modeli oraz pokaemy, w jaki sposb mona wykorzysta wspdzielone
preferencje do przechowywania i odczytywania danych. Szczegowe informacje na temat preferencji znajdziemy w rozdziale 9.

Interfejs modelu widetu


Rozpoczniemy od omwienia interfejsu zachowujcego si jak kontrakt wobec modelu widetu,
dziki czemu model ten moe deklarowa zapisywanie pl w trwaej bazie danych. Kontrakt ten
definiuje rwnie sposb konfigurowania pola podczas jego odczytu z bazy danych. W dodatku
interfejs zawiera metod zwrotn init(), ktra jest wywoywana podczas odczytywania modelu
z bazy danych, a przed przesaniem tego modelu do klienta wysyajcego danie.
Listing 22.10 przedstawia kod rdowy interfejsu kontraktu naszego widetu.

754 Android 3. Tworzenie aplikacji


Listing 22.10. Zachowywanie stanu widetu kontrakt
//nazwa pliku: src//IWidgetModelSaveContract.java
public interface IWidgetModelSaveContract
{
public void setValueForPref(String key, String value);
public String getPrefname();

//zwraca kluczowe pary wartoci, ktre chcemy zachowa


public Map<String,String> getPrefsToSave();

//zostaje wywoany po odzyskaniu


public void init();
}

Ten interfejs jest zaprojektowany w taki sposb, e wywodzca si z niego abstrakcyjna klasa
przeprowadza implementacj za pomoc okrelonego, trwaego magazynu. Wczeniej ju wspomnielimy, e bdziemy korzysta z tej funkcji wspdzielonych preferencji Androida jako
z trwaego magazynu. Jak wskazuje sama nazwa interfejsu, jest to kontrakt sucy wycznie
do zapisywania. Takie klienty jak BDayWidgetProvider nadal bd zalene od najbardziej wydzielonej klasy tego interfejsu, posiadajcej swoiste metody.
Realizator tego interfejsu musi dostarczy nazw pliku preferencji w odpowiedzi na metod
getPrefName(). Plik ten zostaje nastpnie wykorzystany do zapisania pary klucz warto, uzyskiwanej poprzez metod getPrefsToSave(). W odwrotnej operacji (metoda setValueFor
Pref()) pochodna klasa ma za zadanie ustanowienie wewntrznej wartoci za pomoc danej
pary klucz warto, uzyskanej z magazynu preferencji.
Na koniec nastpuje wywoanie metody init() w pochodnej klasie w celu zaznaczenia, e wartoci zostay odczytane z trwaego magazynu, oraz umoliwienia przeprowadzenia wszelkich
innych inicjalizacji.
Pamitajmy, e w uytkowej aplikacji wprowadza si nieco inn struktur dziedziczenia:
zamiast dziedziczenia bdziemy prawdopodobnie wykorzystywa mechanizm delegacji
umoliwiajcy wielokrotne wykorzystywanie obiektw. Jednak hierarchia dziedziczenia
bdzie si dobrze sprawowaa w naszym testowym widecie, przedstawionym jako przykad
modeli widetw.

Rozwamy teraz abstrakcyjn implementacj przechowujc pola danych widetu w postaci


wspdzielonych preferencji.

Abstrakcyjna implementacja modelu widetu


Kod odpowiedzialny za interakcj z trwaym magazynem zosta zaimplementowany w klasie
APrefWidgetModel (listing 22.11). Skrt Pref w nazwie klasy wywodzi si od wyrazu Preference
(preferencja), poniewa klasa ta wykorzystuje funkcj Androida SharedPreferences do
przechowywania danych modelu widetu.
Ponadto klasa ta reprezentuje koncepcj prostego widetu. Pole iid stanowi identyfikator
instancji widetu. Klasa ta zawsze wymaga obecnoci konstruktora przyjmujcego argument
w postaci identyfikatora instancji widetu, dziki czemu nastpuje dostosowanie do wymogw
tego identyfikatora.

Rozdzia 22 Widety ekranu startowego

755

Przyjrzyjmy si przedstawionemu na listingu 22.11 kodowi rdowemu tej klasy. Pogrubion


czcionk zaznaczylimy jej najwaniejsze metody.
Listing 22.11. Implementacja procesu zapisywania widetu poprzez wspdzielone preferencje
//nazwa pliku: /src//APrefWidgetModel.java
public abstract class APrefWidgetModel
implements IWidgetModelSaveContract
{
private static String tag = "AWidgetModel";
public int iid;
public APrefWidgetModel(int instanceId) {
iid = instanceId;
}

//abstrakcyjne metody
public abstract String getPrefname();
public abstract void init();
public Map<String,String> getPrefsToSave(){ return null;}
public void savePreferences(Context context){
Map<String,String> keyValuePairs = getPrefsToSave();
if (keyValuePairs == null){
return;
}

//przechodzi do zapisywania wartoci


SharedPreferences.Editor prefs =
context.getSharedPreferences(getPrefname(), 0).edit();
for(String key: keyValuePairs.keySet()){
String value = keyValuePairs.get(key);
savePref(prefs,key,value);
}

//ostatecznie zapisuje wartoci


prefs.commit();
}
private void savePref(SharedPreferences.Editor prefs,
String key, String value) {
String newkey = getStoredKeyForFieldName(key);
prefs.putString(newkey, value);
}
private void removePref(SharedPreferences.Editor prefs, String key) {
String newkey = getStoredKeyForFieldName(key);
prefs.remove(newkey);
}
protected String getStoredKeyForFieldName(String fieldName){
return fieldName + "_" + iid;
}
public static void clearAllPreferences(Context context, String prefname) {
SharedPreferences prefs=context.getSharedPreferences(prefname, 0);
SharedPreferences.Editor prefsEdit = prefs.edit();
prefsEdit.clear();
prefsEdit.commit();
}

756 Android 3. Tworzenie aplikacji


public boolean retrievePrefs(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences(getPrefname(), 0);
Map<String,?> keyValuePairs = prefs.getAll();
boolean prefFound = false;
for (String key: keyValuePairs.keySet()){
if (isItMyPref(key) == true){
String value = (String)keyValuePairs.get(key);
setValueForPref(key,value);
prefFound = true;
}
}
return prefFound;
}
public void removePrefs(Context context) {
Map<String,String> keyValuePairs = getPrefsToSave();
if (keyValuePairs == null){
return;
}

//przechodzi do zapisywania wartoci


SharedPreferences.Editor prefs =
context.getSharedPreferences(getPrefname(), 0).edit();
for(String key: keyValuePairs.keySet()){
removePref(prefs,key);
}

//ostatecznie zapisuje wartoci


prefs.commit();
}
private boolean isItMyPref(String keyname) {
if (keyname.indexOf("_" + iid) > 0){
return true;
}
return false;
}
public void setValueForPref(String key, String value) {
return;
}
}

Zobaczmy, w jaki sposb s implementowane kluczowe metody tej klasy. Rozpoczniemy od


zapisania atrybutw modelu widetu w pliku wspdzielonych preferencji:
public void savePreferences(Context context)
{
Map<String,String> keyValuePairs = getPrefsToSave();
if (keyValuePairs == null){ return; }

//przechodzi do zapisywania wartoci


SharedPreferences.Editor prefs =
context.getSharedPreferences(getPrefname(), 0).edit();
for(String key: keyValuePairs.keySet()){
String value = keyValuePairs.get(key);

Rozdzia 22 Widety ekranu startowego

757

savePref(prefs,key,value);
}

//ostatecznie zapisuje wartoci


prefs.commit();
}

Dziaanie tej metody rozpoczyna si od uzyskania od pochodnych klas odwzorowania par klucz
warto, gdzie kluczami s atrybuty modelu, a wartociami cigi znakw reprezentujce
wartoci tych atrybutw. Metoda ta nastpnie przekazuje obiektowi context plik SharedPreferences
poprzez metod context.getSharedPreferences(). Interfejs API wymaga unikatowej nazwy
dla tego pakietu. Model pochodny jest odpowiedzialny za jej dostarczenie.
Po uzyskaniu wspdzielonych preferencji zgodnie z dokumentacj Androida naley
uzyska ich modyfikowalne wersje. Nastpnie naley je kolejno zaktualizowa. Po przeprowadzeniu procesu aktualizacji uruchamiamy metod commit(), co spowoduje zapisanie preferencji.
Wicej informacji mona uzyska, przegldajc dokumentacj dotyczc interfejsw API klas
SharedPreferences i SharedPreferences.Editor oraz rozdzia 9.; w zamieszczonym na kocu

rozdziau podrozdziale Odnoniki znajduj si adresy URL, dziki ktrym mona uzyska
powysze informacje. Warto rwnie zwrci uwag na fakt, e pliki wspdzielonych preferencji s napisane w jzyku XML i s przechowywane w katalogu danych pakietu.
Poniewa do przechowywania danych uylimy jednego pliku dla wszystkich instancji widetu,
potrzebny jest mechanizm rozrniania nazw pl pomidzy wieloma instancjami widetu. Jeli
na przykad posiadamy dwie instancje widetu nazwane 1 i 2, wymagane bdzie zastosowanie
dwch kluczy przechowujcych atrybut Name, tak e bd istniay wartoci name_1 oraz name_2.
Takie przeksztacenie przeprowadzamy w nastpujcej metodzie:
protected String getStoredKeyForFieldName(String fieldName) {
return fieldName + "_" + iid;
}

Klasa pochodna rwnie wykorzystuje t metod do okrelenia aktualizowanych pl podczas


jej wywoania przez metod setValue().

Implementacja modelu widetu Urodziny


Ostatecznie klasa bdca ostatnim potomkiem w tej hierarchii modeli widetw zapewnia rzeczywiste utrzymywanie wszystkich pl wymaganych przez widok. Klasy bazowe s jej potrzebne
do przechowywania i odczytywania danych. Zaprojektowalimy t klas w taki sposb, e klienty
korzystajce z tych modeli maj bezporednio z ni do czynienia, poniewa jest to klasa najsilniej
z nimi zwizana.
Na przykad podczas pierwszego utworzenia instancji widetu przez aktywno konfiguratora
aktywno ta konkretyzuje jedn z takich klas, zapenia j wartociami i powoduje jej samoistne
zapisanie si.
Z powodu wymogw widoku klasa ta przechowuje trzy pola:
name imi osoby.
bday data urodzin tej osoby.
url adres witryny, w ktrej mona dokona zakupu prezentu urodzinowego.

758 Android 3. Tworzenie aplikacji


W dalszej kolejnoci klasa ta zawiera obliczony atrybut howManyDays, przedstawiajcy liczb dni,
jakie pozostay do dnia urodzin danej osoby.
Zobaczymy take, e klasa ta wypenia kontrakt zapisywania. Potrzebne s do tego nastpujce
metody:
public void setValueForPref(String key, String value);
public String getPrefname();
public Map<String,String> getPrefsToSave();

Na listingu 22.12 umieszczono kod przeprowadzajcy te wszystkie czynnoci.


Listing 22.12. BDayWidgetModel implementacja modelu stanw
//nazwa pliku: /src//BDayWidgetModel.java
public class BDayWidgetModel extends APrefWidgetModel
{
private static String tag="BDayWidgetModel";

// Generuje niepowtarzaln nazw suc do przechowywania daty


private static String BDAY_WIDGET_PROVIDER_NAME=
"com.ai.android.BDayWidget.BDayWidgetProvider";

// Zmienne umoliwiajce rysowanie widoku widetu


private String name = "anon";
private static String F_NAME = "name";
private String bday = "1/1/2001";
private static String F_BDAY = "bday";
private String url="http://www.google.com";

// Instrukcje /get/set konstruktora


public BDayWidgetModel(int instanceId){
super(instanceId);
}
public BDayWidgetModel(int instanceId, String inName, String inBday){
super(instanceId);
name=inName;
bday=inBday;
}
public void init(){}
public void setName(String inname){name=inname;}
public void setBday(String inbday){bday=inbday;}
public String getName(){return name;}
public String getBday(){return bday;}
public long howManyDays(){
try {
return Utils.howfarInDays(Utils.getDate(this.bday));
}
catch(ParseException x){
return 20000;
}
}

Rozdzia 22 Widety ekranu startowego

759

//Implementacja kontraktu zapisywania


public void setValueForPref(String key, String value){
if (key.equals(getStoredKeyForFieldName(BDayWidgetModel.F_NAME))){
this.name = value;
return;
}
if (key.equals(getStoredKeyForFieldName(BDayWidgetModel.F_BDAY))){
this.bday = value;
return;
}
}
public String getPrefname() {
return BDayWidgetModel.BDAY_WIDGET_PROVIDER_NAME;
}

//Zwraca pary wartoci, ktre chcemy zachowa


public Map getPrefsToSave() {
Map map
= new HashMap();
map.put(BDayWidgetModel.F_NAME, this.name);
map.put(BDayWidgetModel.F_BDAY, this.bday);
return map;
}
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("iid:" + iid);
sbuf.append("name:" + name);
sbuf.append("bday:" + bday);
return sbuf.toString();
}
public static void clearAllPreferences(Context ctx){
APrefWidgetModel.clearAllPreferences(ctx,
BDayWidgetModel.BDAY_WIDGET_PROVIDER_NAME);
}
public static BDayWidgetModel retrieveModel(Context ctx, int widgetId){
BDayWidgetModel m = new BDayWidgetModel(widgetId);
boolean found = m.retrievePrefs(ctx);
return found ? m:null;
}
}

Jak wida, w klasie tej znalazy si pewne funkcje zwizane z dat. Zanim przejdziemy do omwienia implementacji aktywnoci konfiguracji, zademonstrujemy kody rdowe tych funkcji.

Kilka funkcji przetwarzajcych daty


Na listingu 22.13 przedstawiono kod klasy sucej do pracy z datami. Pobiera ona cig znakw
daty i sprawdza jego poprawno. Oblicza take rnic pomidzy biecym dniem a wprowadzon dat. Kod jest cakowicie zrozumiay. Umiecilimy go ze wzgldu na zachowanie cigoci opisu.

760 Android 3. Tworzenie aplikacji


Listing 22.13. Funkcje daty
public class Utils
{
private static String tag = "Utils";
public static Date getDate(String dateString)
throws ParseException {
DateFormat a = getDateFormat();
Date date = a.parse(dateString);
return date;
}
public static String test(String sdate){
try {
Date d = getDate(sdate);
DateFormat a = getDateFormat();
String s = a.format(d);
return s;
}
catch(Exception x){
return "problem z dat:" + sdate;
}
}
public static DateFormat getDateFormat(){
SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");

//DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
df.setLenient(false);
return df;
}

//poprawne formaty: 1/1/2009, 11/11/2009,


//niepoprawne formaty: 13/1/2009, 1/32/2009
public static boolean validateDate(String dateString){
try {
SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
df.setLenient(false);
Date date = df.parse(dateString);
return true;
}
catch(ParseException x) {
return false;
}
}
public static long howfarInDays(Date date){
Calendar cal = Calendar.getInstance();
Date today = cal.getTime();
long today_ms = today.getTime();
long target_ms = date.getTime();
return (target_ms - today_ms)/(1000 * 60 * 60 * 24);
}
}

Przyjrzyjmy si teraz omwionej wczeniej implementacji aktywnoci konfiguracji.

Rozdzia 22 Widety ekranu startowego

761

Implementacja aktywnoci konfiguracji widetu


W podrozdziale Architektura widetw ekranu startowego wyjanilimy rol i zadania
aktywnoci konfiguracji. Implementacja tej klasy aktywnoci w przypadku naszego przykadowego widetu nosi nazw ConfigureBDayWidgetActivity. Jej kod rdowy zosta zaprezentowany na listingu 22.14.
Klasa ta pobiera imi jubilata oraz dat jego nastpnych urodzin. Nastpnie tworzy obiekt
BDayWidgetModel, ktry jest przechowywany we wspdzielonych preferencjach. W klasie tej znajduje si rwnie funkcja, ktra przenosi obiekt BDayWidgetModel do waciwego widoku widetu.

Listing 22.14. Implementacja aktywnoci konfiguratora


public class ConfigureBDayWidgetActivity extends Activity
{
private static String tag = "ConfigureBDayWidgetActivity";
private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_bday_widget);
setupButton();
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
}
}
private void setupButton(){
Button b = (Button)this.findViewById(R.id.bdw_button_update_bday_widget);
b.setOnClickListener(
new Button.OnClickListener(){
public void onClick(View v)
{
parentButtonClicked(v);
}
});
}
private void parentButtonClicked(View v){
String name = this.getName();
String date = this.getDate();
if (Utils.validateDate(date) == false){
this.setDate("nieprawidowa data:" + date);
return;
}
if (this.mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID){
return;
}
updateAppWidgetLocal(name,date);

762 Android 3. Tworzenie aplikacji


Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}
private String getName(){
EditText nameEdit = (EditText)this.findViewById(R.id.bdw_bday_name_id);
String name = nameEdit.getText().toString();
return name;
}
private String getDate(){
EditText dateEdit = (EditText)this.findViewById(R.id.bdw_bday_date_id);
String dateString = dateEdit.getText().toString();
return dateString;
}
private void setDate(String errorDate){
EditText dateEdit = (EditText)this.findViewById(R.id.bdw_bday_date_id);
dateEdit.setText("bd");
dateEdit.requestFocus();
}
private void updateAppWidgetLocal(String name, String dob){
BDayWidgetModel m = new BDayWidgetModel(mAppWidgetId,name,dob);
updateAppWidget(this,AppWidgetManager.getInstance(this),m);
m.savePreferences(this);
}
public static void updateAppWidget(Context context,
AppWidgetManager appWidgetManager,
BDayWidgetModel widgetModel)
{
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.bday_widget);
views.setTextViewText(R.id.bdw_w_name
, widgetModel.getName() + ":" + widgetModel.iid);
views.setTextViewText(R.id.bdw_w_date
, widgetModel.getBday());

//aktualizuje nazw
views.setTextViewText(R.id.bdw_w_days,Long.toString(widgetModel.howManyDays()));
Intent defineIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.google.com"));
PendingIntent pendingIntent =
PendingIntent.getActivity(context,
0 /* brak requestCode */,
defineIntent,
0 /* brak flag */);
views.setOnClickPendingIntent(R.id.bdw_w_button_buy, pendingIntent);

// Informuje meneder widetu


appWidgetManager.updateAppWidget(widgetModel.iid, views);
}
}

Rozdzia 22 Widety ekranu startowego

763

Jeeli przyjrzymy si kodowi metody updateAppWidgetLocal(), stwierdzimy, e tworzy ona


i przechowuje model. Do jego wywietlania suy natomiast funkcja updateAppWidget(). Warto
zwrci uwag, w jaki sposb wykorzystuje ona intencj oczekujc do rejestrowania metody
zwrotnej. Intencja ta pobiera gwn intencj, na przykad:
Intent defineIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.google.com"));

i tworzy kolejn intencj w celu uruchomienia aktywnoci. I na odwrt trwajca intencja moe
zosta rwnie wykorzystana do uruchomienia usugi. Odnotujmy fakt, e funkcja ta dziaa
z klasami RemoteViews i AppWidgetManager. Zwrmy uwag na nastpujce zadania tej funkcji:
Uzyskanie widoku RemoteViews z ukadu graficznego.
Ustanowienie wartoci tekstowych w widoku RemoteViews.
Zarejestrowanie trwajcej intencji poprzez widok RemoteViews.
Wywoanie klasy AppWidgetManager w celu wysania widoku RemoteViews do widetu.
Przekazanie wyniku kocowego.
Funkcja statyczna updateAppWidget moe zosta wywoana z dowolnego miejsca,
pod warunkiem e znamy identyfikator widetu. Wynika z tego wniosek, e mona
aktualizowa widet z dowolnego miejsca na urzdzeniu oraz z dowolnego procesu
zarwno widocznego, jak i niewidocznego.

Istotne jest take, aby zakoczy aktywno w nastpujcy sposb:


Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();

Zaobserwujmy, w jaki sposb przekazuje si identyfikator widetu obiektowi wywoujcemu.


W ten sposb klasa AppWidgetManager uzyskuje informacj, e aktywno konfiguratora zostaa
zakoczona wobec instancji widetu.
Podsumujemy omawianie tematu konfiguracji widetu prezentacj ukadu graficznego formularza tej aktywnoci, widoczn na listingu 22.15. Widok ten jest bardzo prosty: skada si z kilku
pl tekstowych i kontrolek edycji, a take z przycisku aktualizowania. Graficznie zostao to
ukazane na rysunku 22.4.
Listing 22.15. Definicja ukadu graficznego dla aktywnoci konfiguratora
<!-- res/layout/edit_bday_widget.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_layout_id"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/bdw_text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"

764 Android 3. Tworzenie aplikacji


android:text="Imi:"
/>
<EditText
android:id="@+id/bdw_bday_name_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Anonim"
/>
<TextView
android:id="@+id/bdw_text2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Data urodzenia (9/1/2001):"
/>
<EditText
android:id="@+id/bdw_bday_date_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="np. 10/1/2009"
/>
<Button
android:id="@+id/bdw_button_update_bday_widget"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="aktualizuj"
/>
</LinearLayout>

Na tym zakoczymy temat implementowania przykadowego widetu. Podczas omawiania tego


wiczenia zaprezentowalimy nastpujce czynnoci:
definiowanie widetu,
odpowied na metody zwrotne widetu,
tworzenie aktywnoci konfiguracji widetu,
zastosowanie klasy RemoteViews,
wprowadzenie struktury zarzdzania stanami,
projektowanie przyjemnego dla oka ukadu graficznego widetu.
Teraz przekaemy Czytelnikowi kilka wskazwek dotyczcych widetw.

Ograniczenia i rozszerzenia widetw


Na pierwszy rzut oka widety w Androidzie zdaj si bardzo prostymi obiektami. Posiadaj one
jednak kilka niestandardowych cech, z ktrymi naley si zaznajomi przed przystpieniem do
ich tworzenia.
Jeeli dany widet nie wymaga zarzdzania stanami oraz ma by wywoywany co najwyej kilka
razy dziennie, nie powinien istnie najmniejszy problem z jego napisaniem.
Kolejnego stopnia wtajemniczenia wymaga pisanie widetu o zarzdzanym stanie, ktry jednak
nie bdzie wywoywany zbyt czsto. Do tej kategorii zalicza si omwiony w tym rozdziale
widet Urodziny. Struktura zarzdzania stanami okazuje si dla takich widetw bardzo ko-

Rozdzia 22 Widety ekranu startowego

765

rzystna. W tym rozdziale pokazalimy podstawowy szkielet zarzdzania stanami. Zakadamy,


e bd dostpne bardziej skomplikowane struktury lub Czytelnik sam napisze bardziej zoony i elastyczny szkielet.
Na kolejnym poziomie trudnoci znajd si widety o czstotliwoci wywoywania rzdu sekund lub milisekund. W takim przypadku musimy samodzielnie zaprogramowa wywoania
aktualizacji za pomoc klasy AlarmManager. Bdziemy rwnie prawdopodobnie potrzebowa
usugi czstego zarzdzania stanami, niezalenej od trwaej struktury. Jeeli na przykad chcemy
napisa widet Stoper, musimy wprowadzi zegar odmierzajcy co najmniej sekundowe przedziay oraz ledzi liczniki, w czym pomoe nam zarzdzanie stanami.
Kolejnym czynnikiem, ktry naley wzi pod uwag, jest brak mechanizmu pozwalajcego
klasie RemoteViews od ktrej jest zalena struktura widoku widetu na bezporedni edycj
widetu (a przynajmniej taki mechanizm nie zosta udokumentowany). Klasa RemoteViews nakada rwnie ograniczenia na rodzaje wykorzystywanych widokw i ukadw graficznych. Nie
posiadamy bezporedniej kontroli nad widokami, jedynie poredni poprzez metody obecne
w klasie RemoteViews.
Opierajc si na aktualnie tworzonych projektach i przeznaczeniu widetw, mona doj do
wniosku, e zaleca si pisanie widetw nalecych do pierwszej i drugiej kategorii. Istnieje spora
doza prawdopodobiestwa, e struktura widetw zostanie rozszerzona w kolejnych wersjach
systemu Android.

Odnoniki
Podczas przygotowywania materiaw do tego rozdziau odkrylimy nastpujce przydatne zasoby (zostay one uporzdkowane pod wzgldem przydatnoci):
http://developer.android.com/guide/topics/appwidgets/index.html pod tym
adresem jest dostpna oficjalna dokumentacja zestawu Android SDK dotyczca
widetw.
http://developer.android.com/reference/android/content/SharedPreferences.html
na tej stronie mona znale informacje na temat interfejsu SharedPreferences,
ktrego znajomo jest wymagana do zarzdzania stanami.
http://developer.android.com/reference/android/content/SharedPreferences.Editor.html
pod tym adresem mona poczyta na temat interfejsu SharedPreferences.Editor,
ktry jest zwizany ze wspdzielonymi preferencjami.
http://developer.android.com/guide/practices/ui_guidelines/widget_design.html
informacje dostpne pod tym adresem przydadz si w procesie projektowania
miych dla oka ukadw graficznych widetw.
http://developer.android.com/reference/android/widget/RemoteViews.html na tej
stronie dostpne s informacje na temat interfejsu RemoteViews, ktry musimy
opanowa, aby rysowa i kontrolowa widoki widetu.
http://developer.android.com/reference/android/appwidget/AppWidgetManager.html
widety s zarzdzane przez klas menedera widetw; interfejs API tej klasy
zosta omwiony na powyszej stronie.
http://www.androidbook.com/item/3300 pod tym adresem jeden ze wspautorw
ksiki zamieci informacje oraz uyteczne fragmenty kodu, ktre mog si okaza
przydatne, jeeli musimy szybko zapoyczy kod stanowicy podstaw widetw.

766 Android 3. Tworzenie aplikacji

http://www.androidbook.com/item/3299 to cze skieruje nas do notatek z bada,


wykorzystanych podczas pisania tego rozdziau.
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu testowy projekt, przygotowany
specjalne z myl o niniejszym rozdziale. Waciwy plik jest umieszczony w katalogu
o nazwie ProAndroid3_R22_Widety.

Podsumowanie
Poznawanie moliwoci widetw ekranu startowego w Androidzie okazao si dla nas przyjemnym zajciem. Widety te stanowi nieskomplikowan koncepcj, ktra moe w znaczcy
sposb wpyn na wraenia uytkownika.
Omwilimy teori dotyczc widetw oraz zademonstrowalimy dziaajcy przykad, uatwiajcy zrozumienie ich koncepcji. Wyjanilimy konieczno stosowania modeli widetw i zarzdzania ich stanem. Mamy te nadziej, e Czytelnikom przyda si zaprezentowany przez nas
kod zarzdzania stanem podczas tworzenia wasnych widetw. Na kocu poruszylimy tematyk problemw projektowych oraz ogranicze widetw. W rozdziale 31. znajdziemy znacznie
dokadniejsz charakterystyk widetw zaprezentowanych w wersji 3.0 Androida.

R OZDZIA

23
Wyszukiwanie w Androidzie

W poprzednich dwch rozdziaach, 21. i 22., zajmowalimy si mechanizmami


zwizanymi ze stron startow systemu Android. W rozdziale 21. wyjanilimy,
w jaki sposb aktywne foldery s umieszczane na stronie startowej oraz jak zapewniaj szybki dostp do zmieniajcych si danych w dostawcach treci. Rozdzia 22. powicilimy analizie widetw ekranu startowego, wywietlajcych
fragmenty informacji na stronie startowej.
Kontynuujemy teraz opis takiej koncepcji informacji w zasigu rki. W niniejszym
rozdziale przedstawimy struktur wyszukiwania w Androidzie. Szkielet wyszukiwania jest w Androidzie niezmiernie rozbudowany. Chocia wydaje si, e
opcja wyszukiwania jest dostpna wycznie na ekranie startowym urzdzenia,
jej zasig mona rozwin na aktywnoci rnorakich aplikacji.
Rozpoczniemy rozdzia od opisu funkcji wyszukiwania w Androidzie. Zaprezentujemy koncepcje przeszukiwania globalnego, propozycji wyszukiwania, przepisywania propozycji oraz przeszukiwania sieci WWW. Pokaemy, w jaki sposb
umieszcza lokalne aplikacje w procesie przeszukiwania globalnego lub je z niego
wyklucza.
Nastpnie przeanalizujemy integracj aktywnoci naszej aplikacji z przyciskiem
wyszukiwania. Bdziemy pracowa z aktywnociami, ktre nie s jawnie przystosowane do wyszukiwania, a take przyjrzymy si aktywnoci wyczajcej moliwo
wyszukiwania. Przeanalizujemy rwnie mechanizm noszcy nazw type-to-search
(napisz, aby szuka), dziki ktremu aktywnoci maj moliwo wywoania
procesu wyszukiwania. Ponadto zademonstrujemy aktywno, ktra w jawny sposb wywouje proces wyszukiwania za pomoc menu.
Podstawowym elementem, odpowiedzialnym za elastyczno wyszukiwania w Androidzie, jest tak zwany dostawca propozycji. Przeanalizujemy uwanie t koncepcj i pokaemy, jak utworzy prostego dostawc propozycji poprzez dziedziczenie po bazowym dostawcy dostpnym w Androidzie.
Czsto jednak trzeba samodzielnie napisa dostawc propozycji od podstaw. Bdzie
to kolejne zagadnienie, ktre bdzie dotyczy zasadniczej czci architektury wyszukiwania w Androidzie.

768 Android 3. Tworzenie aplikacji


Na koniec zajmiemy si dwoma zaawansowanymi zagadnieniami. Pokaemy, w jaki sposb moemy wykorzysta przyciski dziaania dostpne w urzdzeniu do wywoywania niestandardowych dziaa korzystajcych z propozycji wyszukiwania. Zastanowimy si take na sposobem
przekazania danych okrelonej aplikacji do wywoanego procesu wyszukiwania. Rozdzia zamkniemy zestawem odnonikw.

Wyszukiwanie w Androidzie
Wyszukiwanie w Androidzie rozszerza moliwoci znanego nam internetowego paska wyszukiwania Google o zdolno przeszukiwania zarwno lokalnej zawartoci urzdzenia, jak i treci
zewntrznych, udostpnionych w internecie. Mechanizm ten moe by rwnie wykorzystany
do bezporedniego wywoywania aplikacji z poziomu paska wyszukiwania na stronie startowej.
Android umoliwia korzystanie z tych funkcji poprzez wprowadzenie struktury wyszukiwania
pozwalajcej na wspuczestniczenie w niej aplikacji lokalnych.
Protok przeszukiwania w Androidzie jest prosty. Dostpne jest pojedyncze pole wyszukiwania,
w ktrym uytkownicy wpisuj poszukiwany cig znakw. Jest tak zarwno w przypadku stosowania pola wyszukiwania globalnego na stronie startowej, jak i przeszukiwania wasnej
aplikacji wykorzystywane jest to samo pole wyszukiwania.
Tekst wprowadzany przez uytkownika jest pobierany przez system ju w trakcie wpisywania
i przekazywany rnym aplikacjom, ktre zostay zarejestrowane do odpowiadania na proces
wyszukiwania. Zareaguj one w ten sposb, e przeka zestaw odpowiedzi. Android zbiera te
odpowiedzi z rnych aplikacji i wywietla je w postaci listy moliwych propozycji.
Po klikniciu jednej z tych odpowiedzi system wywouje aplikacj przedkadajc wybran propozycj. W tym sensie mamy do czynienia z wyszukiwaniem sfederowanym (pojcie to oznacza
mechanizm umoliwiajcy zintegrowany dostp do rozproszonych zasobw) wewntrz zbioru
wspuczestniczcych aplikacji.
Chocia oglna idea jest cakiem prosta, szczegy protokou wyszukiwania s dosy zoone.
W dalszej czci rozdziau bdziemy je objania na dziaajcych przykadach. W tym podrozdziale przyjrzymy si procesowi wyszukiwania z perspektywy uytkownika.

Badanie procesu przeszukiwania globalnego w Androidzie


Chocia nie wymagamy tego bezwzgldnie, bardzo zalecamy przejrzenie dziau powiconego
wyszukiwaniu w instrukcji obsugi systemu Android w trakcie zapoznawania si z treci
niniejszego rozdziau. W podrozdziale Odnoniki zamiecilimy cze do najnowszej wersji
takiej instrukcji uytkownika.
W trakcie pisania tej ksiki pojawiay si kolejno wersje: 2.0, 2.2, 2.3 oraz 3.0 Androida.
Chocia sam interfejs API nie zosta zmieniony, nieznacznym modyfikacjom ulega sam
interfejs uytkownika. Zrzuty ekranu w tym rozdziale zostay wykonane na emulatorze
pracujcym z wersj 2.2 Androida. Chocia przetestowalimy kody w wersjach 2.3 i 3.0
systemu, nie wykonalimy w ich przypadku adnego rysunku. W odpowiednich miejscach
tego rozdziau wspominamy jednak o rnicach. Niezalenie od posiadanej wersji Androida
Czytelnik nie powinien mie problemu z wyobraeniem sobie sposobu dziaania
rwnowanych wersji interfejsu uytkownika. Zastanwmy si na przykad nad ustawieniami
wyszukiwania. W kadej kolejnej wersji systemu miejsce wywoywania ekranu ustawie
wyszukiwania ulegao zmianie, jednak sam ekran ustawie wyglda tak samo. Sugerujemy
wic, aby w trakcie czytania rozdziau bra pod uwag wspomniane rozbienoci.

Rozdzia 23 Wyszukiwanie w Androidzie

769

Nie da si omin wyszukiwania w Androidzie; pole wyszukiwania jest zazwyczaj umieszczone


na stronie startowej, co zostao ukazane na rysunku 23.1. Pole wyszukiwania nazywane jest take
polem QSB (ang. Quick Search Box pole szybkiego wyszukiwania). W niektrych wersjach
Androida lub w przypadku niektrych producentw urzdze czy operatorw pole to moe nie
by domylnie widoczne na ekranie startowym. Z pewnoci jednak ujrzymy je po wciniciu
przycisku wyszukiwania. W przypadku urzdze nieposiadajcych przyciskw fizycznych (na
przykad tabletw) dostrzeemy jaki inny oczywisty mechanizm wywoywania pola QSB. Wszystko zostao dokadnie opisane w instrukcji uytkowania danej wersji Androida.

Rysunek 23.1. Strona startowa Androida z widocznym polem QSB i przyciskiem wyszukiwania

Poniewa pole QSB zostao wstawione w formie widetu (rozdzia 22. zosta powicony tematyce widetw), moemy przenie je na ekran startowy, jeeli jeszcze go tam nie ma. Rwnie dobrze moemy to pole usun z ekranu startowego wystarczy je przenie do kosza.
Oczywicie zawsze moemy je pniej przywrci z poziomu ekranu widetw.
W celu rozpoczcia wyszukiwania moemy bezporednio pisa w polu QSB. Moemy wtedy
zaobserwowa interesujcy efekt spowodowany tym, e pole QSB jest widetem: bezporednio
po uaktywnieniu pola QSB na ekranie startowym system uruchamia aktywno wyszukiwania
globalnego (rysunek 23.2), w wyniku czego opuszczamy kontekst tego ekranu. Rysunek 23.2 zosta
wykonany w wersji 2.2 Androida. Ekran ten wyglda identycznie w wersji 2.3 systemu.
Jak ju wspomnielimy, moemy take wywoa proces wyszukiwania, klikajc odpowiedni przycisk dziaania. Przyciski dziaania stanowi zestaw przyciskw widocznych na rysunku 23.1 po
prawej stronie. Na interesujcym nas przycisku zosta umieszczony symbol lupy.
Podobnie jak w przypadku przycisku ekranu startowego, moemy klikn przycisk wyszukiwania w dowolnym momencie, bez wzgldu na uruchomion aplikacj. Jednak jeeli aplikacja
zostaa umieszczona w gwnym wtku, umoliwia ona zawenie wyszukiwania, czym zajmiemy si w dalszej czci rozdziau. Takie zawone wyszukiwanie nazywane jest wyszukiwaniem lokalnym. Bardziej oglne, powszechne i niewyspecjalizowane wyszukiwanie nosi miano
wyszukiwania globalnego.

770 Android 3. Tworzenie aplikacji

Rysunek 23.2. Aktywno wyszukiwania globalnego uruchomiona za pomoc widetu ekranu


startowego
Jeli wciniemy przycisk wyszukiwania w obrbie aktywnej aplikacji, to od tej aplikacji
zaley, czy pozwoli ona na korzystanie z lokalnego, czy globalnego wyszukiwania.
W wersjach systemu starszych od 2.0 domylnym zachowaniem byo umoliwienie
wyszukiwania globalnego. W wersjach 2.2 i 2.3 z kolei standardowo wyszukiwanie
globalne zostaje w takim przypadku uniemoliwione. Oznacza to, e jeeli uytkownik
korzysta z danej aktywnoci, musi najpierw wcisn przycisk ekranu startowego, a dopiero
w dalszej kolejnoci przycisk wyszukiwania.

W wersjach systemu starszych od 2.2 pole wyszukiwania globalnego nie rozrniao poszczeglnych dostawcw propozycji wyszukiwania (lub wyszukiwarek). Poczwszy od wersji 2.2,
aplikacja Android Search pozwala wybra konkretny kontekst wyszukiwania (synonim dostawcy
propozycji).
W tym celu naley klikn ikon widoczn po lewej stronie pola QSB. Zostanie otwarta lista
poszczeglnych aplikacji przeszukujcych. Lista taka (dla wersji 2.2 Androida) zostaa zaprezentowana na rysunku 23.3. W przypadku wersji 2.3 systemu widok ten ulega niewielkiej zmianie w prawej grnej czci rozwinitej sekcji kryteriw wyszukiwania zostaa wprowadzona
maa ikona ustawie przeszukiwania.
Jest to domylny zbir aplikacji wyszukujcych (lub kontekstw czy te typw wyszukiwania,
ewentualnie dostawcw propozycji), dostpnych w emulatorze dla wersji 2.2 i 2.3 systemu.
W nowszych wersjach lista ta moe wyglda inaczej. Kontekst wyszukiwania Wszystko zaj
miejsce wyszukiwania globalnego, znanego z wczeniejszych wersji Androida.
Moemy rwnie utworzy wasny kontekst wyszukiwania poprzez napisanie dostawcw propozycji wyszukiwania oraz aktywnoci przeszukiwania lokalnego. Zajmiemy si tym zagadnieniem podczas omawiania rnorodnych przykadw zawartych w tym rozdziale.

Rozdzia 23 Wyszukiwanie w Androidzie

771

Rysunek 23.3. Pole wyszukiwania globalnego z widocznymi kontekstami rnych aplikacji wyszukujcych

Przyjrzyjmy si kontekstowi wyszukiwania symbolizowanemu przez ikon lupy. Przechodzimy


do tego pola (rysunek 23.1), klikajc bezporednio jego obszar albo ikon wyszukiwania. Nie
wpisujmy jeszcze niczego w polu QSB. W tym momencie Android bdzie wywietla ekran
przypominajcy zrzut z rysunku 23.2.
W zalenoci od wczeniej dokonywanych czynnoci obraz widoczny na rysunku 23.2 moe
wyglda nieco inaczej, poniewa Android na podstawie uprzednich dziaa stara si odgadn,
czego szukamy. Taki tryb wyszukiwania, w ktrym w polu QSB nie zosta wprowadzony tekst,
nosi nazw trybu propozycji zerowych. Zalenie od wpisanego tekstu Android wywietli
rn liczb propozycji. Zostan one wywietlone pod polem QSB w formie listy. Elementy
tej listy czsto s nazywane propozycjami wyszukiwania. W miar wpisywania kolejnych liter
Android bdzie dynamicznie aktualizowa wyniki wyszukiwania. Jeeli w polu wyszukiwania nie zostanie wpisany tekst, zostan wywietlone tak zwane propozycje zerowe. Na rysunku 23.2 wida, e aplikacja Spare Parts zostaa uznana przez Android za uywan w przeszoci, zatem nadajc si na propozycj, mimo e aden tekst nie zosta wpisany w polu
wyszukiwania. Chocia nie wprowadzilimy tekstu w polu QSB, zostaje wywietlona klawiatura programowa. Jest ona widoczna na rysunku 23.2.
Po wprowadzeniu znaku a w polu QSB Android wyszukuje propozycje rozpoczynajce si od
litery a lub zawierajce t liter. Stwierdzimy, e Android przeszuka ju lokalnie zainstalowane
aplikacje rozpoczynajce si na liter a oraz du liczb innych propozycji wyszukiwania.
Skorzystamy teraz z przycisku przesunicia kursora w d w celu zaznaczenia pierwszej propozycji. Widok ten zosta ukazany na rysunku 23.4.
Zauwamy, e pierwsza propozycja zostaa zaznaczona, a pierwszoplanowym obiektem ju nie
jest pole QSB, tylko ta zaznaczona propozycja. Kliknijmy ikon widoczn po prawej stronie
pola QSB, aby kontynuowa proces wyszukiwania. Obszar ekranu zosta rwnie powikszony poprzez schowanie klawiatury programowej, gdy nie bdziemy z niej korzysta podczas
przegldania listy propozycji. Dziki temu na ekranie wida rwnoczenie wicej propozycji.

772 Android 3. Tworzenie aplikacji

Rysunek 23.4. Propozycje wyszukiwania

Przyjrzyjmy si jednak ponownie propozycjom. Android pobiera tekst wpisany w polu wyszukiwania i wyszukuje obiekty znane jako dostawcy propozycji. Android wywouje asynchronicznie kadego dostawc propozycji w celu uzyskania pasujcych propozycji, przybierajcych
posta zbioru krotek. Android oczekuje, e te krotki (zwane propozycjami wyszukiwania) bd
pasowa do zestawu predefiniowanych kolumn (kolumny propozycji). W miar przeszukiwania
tych znanych kolumn Android bdzie kompletowa list propozycji. Po zmianie tekstu wpisanego w polu QSB Android przeprowadzi cay proces od pocztku. Taki sposb pracy, polegajcy na wywoywaniu wszystkich dostawcw propozycji w celu uzyskania propozycji wyszukiwania,
jest waciwy w przypadku kontekstu odpowiedzialnego za wyszukiwanie globalne. Jeeli jednak wybierzemy konkretny kontekst wyszukiwania, na przykad taki, jaki jest widoczny na rysunku 23.3, w celu odczytania propozycji wyszukiwania zostanie przywoany jedynie dostawca
propozycji zdefiniowany dla tej aplikacji.
Zbir propozycji wyszukiwania zwany jest take kursorem propozycji. Wynika to z faktu,
e dostawca treci reprezentujcy dostawc propozycji przekazuje obiekt cursor.

Jeeli w tym momencie ponownie klikniemy w polu QSB, system znowu wywietli klawiatur
programow. Kolejn rzecz z rysunku 23.4 wart odnotowania jest zaleno pomidzy zaznaczon propozycj a tekstem wyszukiwania w polu QSB. Tekst wyszukiwania cigle skada si
wycznie z litery a, mimo e zosta zaznaczony konkretny element, w naszym wypadku aplikacja Aparat. Jednak nie zawsze tak jest, co wida na rysunku 23.5, ktry przedstawia zaznaczenie
propozycji wskazujcej adres sklepu Amazon.
Zauwamy, e wstawiona przez nas litera a zostaa zastpiona przez peny adres URL serwisu
Amazon. Moemy teraz klikn strzak (ktr bdziemy nazywa strzak nawigacji), aby
otworzy stron Amazon, lub zwyczajnie klikn zaznaczon propozycj. W obydwu przypadkach skutek bdzie identyczny.

Rozdzia 23 Wyszukiwanie w Androidzie

773

Rysunek 23.5. Przepisanie propozycji


Taki proces modyfikowania tekstu wyszukiwania na podstawie zaznaczonej propozycji
nosi nazw przepisywania propozycji.

Nieco pniej zajmiemy si dokadniej procesem przepisywania propozycji, w skrcie jednak


Android wykorzystuje jedn z kolumn kursora propozycji do wyszukiwania tego tekstu. Jeeli
taka kolumna istnieje, zostanie przepisany tekst wyszukiwania, w przeciwnym razie pozostanie
on niezmieniony.
Jeeli propozycja nie zostanie przepisana, istniej dwie moliwoci. Jeeli klikniemy ikon wyszukiwania w polu QSB, niezalenie od tego, co zostao zaznaczone, Android przystpi do przeszukiwania w bazie Google. Jeli za bezporednio klikniemy element propozycji, w aplikacji,
ktra wygenerowaa dan propozycj, zostanie wywoana aktywno wyszukiwania. Aktywno
ta jest odpowiedzialna za wywietlenie wynikw wyszukiwania.
Na rysunku 23.6 wida przykad bezporedniego wywoywania propozycji. W naszym wypadku takim przykadem jest aplikacja APIDemos. Po klikniciu propozycji aplikacja ta zostanie bezporednio wywoana. Przebieg tego procesu jest dosy skomplikowany i zajmiemy si
jego omwieniem w dalszej czci rozdziau (w punkcie Implementacja niestandardowego
dostawcy propozycji).
Na rysunku 23.7 zaprezentowano, co si stanie po klikniciu strzaki nawigacji w przypadku
wprowadzenia litery a w polu QSB.
Po przedstawieniu techniki wyszukiwania za pomoc pola QSB przejdziemy do omwienia,
w jaki sposb moemy wcza i wycza okrelone aplikacje ze wspudziau w przeszukiwaniu
globalnym.

774 Android 3. Tworzenie aplikacji

Rysunek 23.6. Wywoywanie aplikacji za pomoc wyszukiwania

Rysunek 23.7. Przeszukiwanie internetu

Wczanie dostawcw propozycji


do procesu wyszukiwania globalnego
Jak ju stwierdzilimy, aplikacje posuguj si dostawcami propozycji w celu przekazania
odpowiedzi na proces wyszukiwania. Chocia aplikacja moe posiada infrastruktur niezbdn
do odpowiedzi na wyszukiwanie, nie oznacza to wcale, e jej propozycje bd automatycznie
wywietlane w polu QSB. Uytkownik musi zezwoli dostawcy propozycji na udzia w tym procesie. Przedstawimy teraz kolejne etapy wczania lub wyczania posiadanych dostawcw
propozycji. Proces uzyskiwania dostpu do omawianych poniej ustawie rni si nieznacznie
pomidzy wersjami 2.2 i 2.3 Androida. Najpierw zajmiemy si wersj 2.2.

Rozdzia 23 Wyszukiwanie w Androidzie

775

Praca z ustawieniami wyszukiwania w wersji 2.2 Androida


Rozpocznijmy od ekranu ustawie Androida (rysunek 23.8).

Rysunek 23.8. Lokalizowanie aplikacji odpowiedzialnej za ustawienia

Dostp do tego widoku uzyskujemy poprzez kliknicie ikony reprezentujcej list aplikacji,
umieszczonej w dolnej czci ekranu urzdzenia (ekran startowy zosta pokazany na rysunku 23.1).
Naley wyszuka tam aplikacj Ustawienia, ktrej ikon pokazalimy na rysunku 23.8, i uruchomi
j. Zostanie wywietlona strona z ustawieniami Androida, przypominajca ekran z rysunku 23.9.

Rysunek 23.9. Wyszukiwanie ustawie aplikacji Ustawienia wyszukiwania

776 Android 3. Tworzenie aplikacji


Spord rnorodnych ustawie Androida wybieramy opcj Szukaj. Zostanie uruchomiona
aplikacja Ustawienia wyszukiwania, zilustrowana na rysunku 23.10.

Rysunek 23.10. Aplikacja Ustawienia wyszukiwania

Znajdujemy w tej aktywnoci zakadk Okno szybkiego wyszukiwania i wybieramy opcj Wyszukiwane elementy (Wybierz elementy do wyszukania w telefonie). Ukae si zaprezentowana
na rysunku 23.11 lista dostpnych dostawcw propozycji (nazywanych czasem aplikacjami wyszukujcymi). Przypominamy, e zalenie od wersji systemu lista ta moe wyglda inaczej.

Rysunek 23.11. Wczone i wyczone aplikacje wyszukujce

Rozdzia 23 Wyszukiwanie w Androidzie

777

Dostawcy propozycji (lub stanowice ich czci aplikacje), wczone do wyszukiwania globalnego, s pokazane na rysunku 23.11 jako zaznaczone. Nowy dostawca propozycji nie jest domylnie zaznaczony. Aby zosta doczony do procesu wyszukiwania, trzeba klikn jego nazw.
Po takim wczeniu dany dostawca bdzie umieszcza propozycje w oknie globalnego wyszukiwania. Taki wczony dostawca propozycji zostanie rwnie wywietlony pord aplikacji wyszukujcych, widocznych na rysunku 23.3.

Praca z ustawieniami wyszukiwania w wersji 2.3 Androida


Rnica w dostpie do ustawie dostawcw propozycji pomidzy wersjami 2.2 i 2.3 systemu
(oraz, miejmy nadziej, przyszymi wersjami) polega na sposobie otwierania ekranu ustawie
wyszukiwania, widocznego na rysunkach 23.10 i 23.11.
W wersji 2.3 Androida moemy bezporednio otworzy widok pokazany na rysunku 23.11 z poziomu rozwinitego ekranu kryteriw wyszukiwania (rysunek 23.2). Znajdziemy tu niewielk
ikon ustawie. Po jej klikniciu system natychmiast uruchomi ekran widoczny na rysunku
23.11, gdzie ujrzymy wszystkie niestandardowe aktywnoci wyszukiwania.
Aby przej do oglnego ekranu ustawie wyszukiwania (rysunek 23.10), musimy otworzy ktry
z ekranw widocznych na rysunkach 23.2, 23.3 lub 23.4. W istocie nastpuje kliknicie pola
QSB. Jeeli pole QSB jest aktywne, po klikniciu przycisku Menu ujrzymy obiekt menu nazwany
Ustawienia wyszukiwania. Jego kliknicie umoliwi dostp do oglnych ustawie wyszukiwania,
znanych z rysunku 23.10. Gdy ju otworzymy ten ekran, instrukcje korzystania z ustawie s takie same jak w przypadku wersji 2.2 Androida.
Do tej pory zapoznalimy si z oglnym mechanizmem dziaania wyszukiwania w Androidzie.
Teraz przyjrzymy si dokadniej omawianym pojciom oraz zademonstrujemy na przykadach zasad ich dziaania. Rozpoczniemy od oddziaywania prostych aktywnoci na proces wyszukiwania.

Interakcja aktywnoci z przyciskiem wyszukiwania


Co si stanie po wciniciu przycisku wyszukiwania, w przypadku gdy na pierwszym planie
znajduje si aktywno? Odpowied zaley od rodzaju tej aktywnoci. Przeledzimy zachowanie
nastpujcych typw aktywnoci:
zwyka aktywno niezwizana z wyszukiwaniem,
aktywno jawnie wyczajca wyszukiwanie,
aktywno jawnie wywoujca wyszukiwanie globalne,
aktywno okrelajca wyszukiwanie lokalne.
Przeanalizujemy te opcje na przykadowych projektach skadajcych si z nastpujcych plikw (po
omwieniu kadego z nich zaprezentujemy zrzuty ekranu, ukazujce koncepcje tej aplikacji):
RegularActivity.java (listing 23.1),
NoSearchActivity.java (listing 23.6),
SearchInvokerActivity.java (listing 23.8),
LocalSearchEnabledActivity.java (listing 23.13),
SearchActivity.java (listing 23.11).

778 Android 3. Tworzenie aplikacji


Poza ostatni aktywnoci (SearchActivity.java) kada z pozostaych reprezentuje po jednym
rodzaju z wymienionych wczeniej aktywnoci. Plik SearchActivity.java jest wymagany przez
aktywno LocalSearchEnabledActivity. Kada aktywno, w tym SearchActivity, zawiera prosty ukad graficzny z widokiem tekstowym. Obsugiwane s nastpujce pliki ukadu
graficznego:
res/layout/main.xml (dla aktywnoci RegularActivity; listing 23.3),
res/layout/no_search_activity.xml (listing 23.7),
res/layout/search_invoker_activity.xml (listing 23.9),
res/layout/local_search_enabled_activity.xml (listing 23.14),
res/layout/search_activity.xml (listing 23.11).
Dwa nastpne pliki definiuj te aktywnoci w Androidzie oraz przeszukuj metadane w przypadku aktywnoci obsugujcej wyszukiwanie lokalne:
AndroidManifest.xml (listing 23.2),
xml/searchable.xml (listing 23.12).
W kolejnym pliku zostay umieszczone dane tekstowe kadego ukadu graficznego w postaci
cigu znakw:
res/values/strings.xml (listing 23.4).
Wymienione poniej dwa pliki dostarczaj menu niezbdne do wywoania tych aktywnoci
w razie potrzeby oraz do wyszukiwania globalnego:
res/menu/main_menu.xml (listing 23.5),
res/menu/search_invoker_menu.xml (listing 23.10).
Zbadamy teraz interakcj pomidzy tymi aktywnociami a przyciskiem wyszukiwania poprzez metodyczny przegld kodu rdowego tych plikw pod ktem rodzajw aktywnoci.
Jeli Czytelnik zechce skompilowa i przetestowa omawiane pliki, zalecamy pobranie
projektw, ktre mona zaimportowa do rodowiska Eclipse. Adres URL do tych
projektw znajduje si w podrozdziale Odnoniki.

Rozpocznijmy od przeanalizowania zachowania przycisku wyszukiwania w obecnoci standardowej aktywnoci Androida.

Zachowanie przycisku wyszukiwania


wobec standardowej aktywnoci
Na listingu 23.1 przedstawilimy kod rdowy Java przykadowej aktywnoci RegularActivity.
Umoliwi on nam sprawdzenie, co si dzieje, w przypadku gdy na pierwszym planie systemu
znajduje si niezwizana z wyszukiwaniem aktywno.
Listing 23.1. Kod rdowy standardowej aktywnoci
//nazwa pliku: RegularActivity.java
public class RegularActivity extends Activity
{
private final String tag = "RegularActivity";

Rozdzia 23 Wyszukiwanie w Androidzie

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{

//wywouje nadrzdn klas w celu doczenia menu systemowych


super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();

//metoda getMenuInflater() pochodzi z bazowej aktywnoci


inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
appendMenuItemText(item);
if (item.getItemId() == R.id.menu_clear) {
this.emptyText();
return true;
}
if (item.getItemId() == R.id.mid_no_search) {
this.invokeNoSearchActivity();
return true;
}
if (item.getItemId() == R.id.mid_local_search) {
this.invokeLocalSearchActivity();
return true;
}
if (item.getItemId() == R.id.mid_invoke_search) {
this.invokeSearchInvokerActivity();
return true;
}
return true;
}
private TextView getTextView()
{
return (TextView)this.findViewById(R.id.text1);
}
private void appendMenuItemText(MenuItem menuItem)
{
String title = menuItem.getTitle().toString();
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + title);
}
private void emptyText()
{
TextView tv = getTextView();

779

780 Android 3. Tworzenie aplikacji


tv.setText("");
}
private void invokeNoSearchActivity()
{

//odkomentujmy ponisze wiersze w momencie


//dodania tej aktywnoci do projektu
//Intent intent =
// new Intent(this,NoSearchActivity.class);
//startActivity(intent);
}
private void invokeSearchInvokerActivity()
{

//odkomentujmy ponisze wiersze w momencie


//dodania tej aktywnoci do projektu
//Intent intent =
// new Intent(this,SearchInvokerActivity.class);
//startActivity(intent);
}
private void invokeLocalSearchActivity()
{

//odkomentujmy ponisze wiersze w momencie


//dodania tej aktywnoci do projektu
//Intent intent =
// new Intent(this,LocalSearchEnabledActivity.class);
//startActivity(intent);
}
}

Zadaniem tego kodu jest odgrywanie roli prostej aktywnoci, niezwizanej z wyszukiwaniem.
Jednak ta przykadowa aktywno steruje rwnie wywoywaniem pozostaych testowanych
przez nas aktywnoci. Dlatego wida w kodzie dodatkowe elementy menu reprezentujce te
aktywnoci. Kada funkcja rozpoczynajca si od instrukcji typu invoke zawiera kod pozwalajcy na uruchomienie pozostaych rodzajw aktywnoci.
Wszystkie pliki niezbdne do kompilacji zostay umieszczone jeden po drugim, jednak ju teraz
Czytelnik moe oznaczy jako komentarze funkcje typu invoke lub doczy do projektu kod
tych klas. Aby uatwi zadanie, opatrzylimy ju znakami komentarzy odpowiednie wiersze.
Spjrzmy na plik manifest, aby dowiedzie si, w jaki sposb omawiana aktywno jest definiowana (listing 23.2). Widoczne s rwnie definicje pozostaych aktywnoci, zostan one jednak omwione w dalszej czci rozdziau. Take tym razem oznaczylimy jako komentarze te
dodatkowe aktywnoci a do czasu, gdy bd potrzebne.
Listing 23.2. Interakcja aktywnoci z przyciskiem wyszukiwania plik manifest
//nazwa pliku: manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.search.nosearch">
<application android:icon="@drawable/icon"

Rozdzia 23 Wyszukiwanie w Androidzie

781

android:label="Interakcja aktywnoci testowej z polem QSB">


<activity android:name=".RegularActivity"
android:label="Interakcja aktywno/QSB: Standardowa aktywno">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- W trakcie tworzenia poszczeglnych aktywnoci odkomentujmy je.


Podpowiemy, gdy nadejdzie waciwa chwila.
<activity android:name=".NoSearchActivity"
android:label=" Interakcja aktywno/QSB:Wyczone wyszukiwanie">
</activity>
<activity android:name=".SearchInvokerActivity"
android:label=" Interakcja aktywno/QSB:Wywoanie wyszukiwania">
</activity>
<activity android:name=".LocalSearchEnabledActivity"
android:label=" Interakcja aktywno/QSB:Wyszukiwanie lokalne">
<meta-data android:name="android.app.default_searchable"
android:value=".SearchActivity" />
</activity>
<activity android:name=".SearchActivity"
android:label=" Interakcja aktywno/QSB:Wyniki wyszukiwania">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>

-->
</application>
<uses-sdk android:minSdkVersion="4" />
</manifest>

Zauwamy, e klasa RegularActivity jest zdefiniowana jako gwna aktywno tego projektu
i nie posiada innych parametrw zwizanych z wyszukiwaniem.
Plik ukadu graficznego tej aktywnoci zosta ukazany na listingu 23.3.
Listing 23.3. Plik ukadu graficznego standardowej aktywnoci
//nazwa pliku: layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"

782 Android 3. Tworzenie aplikacji


android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/regular_activity_prompt"
/>
</LinearLayout>

Zaprezentujemy teraz wykorzystywane w tym projekcie zasoby znakowe. Na listingu 23.4 zostay
umieszczone cigi znakw wykorzystywane w innych aktywnociach. Jednak te dodatkowe zasoby nie powinny mie wpywu na proces kompilowania biecej aktywnoci, nawet jeli nie zostay wprowadzone pozostae klasy.
W ten sposb na listingu 23.4 zostaa zaprezentowana zawarto pliku strings.xml przechowujcego tekst wywietlany przez nasz aktywno. Pogrubion czcionk i jako komentarze
zaznaczylimy sekcje odpowiedzialne za poszczeglne aktywnoci.
Listing 23.4. Interakcja aktywno przycisk wyszukiwania plik strings.xml
//nazwa pliku: /res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

<!
**************************************************
* regular_activity_prompt
**************************************************
-->
<string name="regular_activity_prompt">
Jest to przykadowa aplikacja, w ktrej testowana jest interakcja pomidzy polem QSB
z przyciskiem wyszukiwania a aktywnoci. Zostay w niej zawarte cztery aktywnoci,
wcznie z niniejsz. Obecnie jest uruchomiona standardowa aktywno. Dostp do
pozostaych trzech aktywnoci uzyskujemy poprzez menu.
\n\n
Jest to standardowa aktywno, nieposiadajca funkcji zwizanych z wyszukiwaniem.
Jeeli klikniemy teraz przycisk wyszukiwania, zostanie wywoane przeszukiwanie
globalne.
\n
\nPozostae aktywnoci to:
\n\n1) Aktywno braku wyszukiwania: aktywno, w ktrej wyszukiwanie zostao
wyczone.
\n2) Wywoanie wyszukiwania: programowe wywoanie wyszukiwania globalnego.
\n3) Aktywno wyszukiwania lokalnego: przywouje wyszukiwania lokalne.
\n
\nTutaj bd si pojawiay informacje o bdach.
</string>

<!-**************************************************
* no_search_activity_prompt
**************************************************
-->
<string name="no_search_activity_prompt">
W tej aktywnoci metoda onSearchRequested
zwraca warto false. Przycisk wyszukiwania
bdzie teraz ignorowany.

Rozdzia 23 Wyszukiwanie w Androidzie

783

\n
\nMona klikn przycisk powrotu, aby uzyska dostp
do poprzedniej aktywnoci i wybra w menu
inn aktywno.
</string>

<!-**************************************************
* search_activity_prompt
**************************************************
-->
<string name="search_activity_prompt">
Jest to tak zwana aktywno wyszukiwania lub aktywno wynikw wyszukiwania.
Ta aktywno jest wywoywana podczas wcinicia przycisku wyszukiwania w trakcie
uywania tej aktywnoci przez inn aktywno w roli aktywnoci wynikw wyszukiwania.
\n\n
Zazwyczaj moemy uzyska z intencji cig znakw kwerendy,
aby dowiedzie si, czym jest ta kwerenda.
</string>

<!-**************************************************
* search_invoker_activity_prompt
**************************************************
-->
<string name="search_invoker_activity_prompt">
W tej aktywnoci element menu wyszukiwania jest stosowany
do wywoania domylnego wyszukiwania. W tym przypadku
nie zostao dla tej aktywnoci okrelone wyszukiwanie lokalne, wic
zostaje wywoane wyszukiwanie globalne. Kliknicie
przycisku menu spowoduje wywietlenie menu wyszukiwania. Po jego
klikniciu zostanie uruchomione wyszukiwanie globalne.
</string>

<!-**************************************************
* local_search_enabled_activity_prompt
**************************************************
-->
<string name="local_search_enabled_activity_prompt">
Jest to bardzo prosta aktywno, dla ktrej w pliku manifecie
zostaa wskazana powizana aktywno wyszukiwania.
W ten sposb po wciniciu przycisku wyszukiwania zostaje wywietlone
wyszukiwanie lokalne.
\n\n
Jego lokalno jest widoczna w etykiecie pola QSB oraz w jego podpowiedzi.
Obydwa te elementy pochodz z metadanych wyszukiwania.
\n\n
Po klikniciu ikony kwerendy zostaniemy przeniesieni
do aktywnoci wyszukiwania lokalnego.
</string>

<!-**************************************************
* Inne wartoci
**************************************************
-->

784 Android 3. Tworzenie aplikacji


<string name="search_label">Demonstracja wyszukiwania lokalnego</string>
<string name="search_hint">Podpowied do demonstracji wyszukiwania
lokalnego</string>
</resources>

Podobnie jak w przypadku pliku manifestu, plik strings.xml jest wykorzystywany przez wszystkie
aktywnoci tego projektu. Moemy zauway, e obecna w tym pliku staa typu string regular_activity wskazuje tekst widoczny na ekranie aktywnoci standardowej.
Aby wspomc proces kompilacji standardowej aktywnoci, na listingu 23.5 zaprezentujemy
plik z zasobami menu. Chocia zawarte s w nim elementy zwizane z pozostaymi aktywnociami, nie bd one wpywa na proces kompilowania i umoliwi nam korzystanie ze standardowej aktywnoci, dostpnej na listingu 23.1.
Listing 23.5. Plik menu standardowej aktywnoci
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- nazwa pliku: /res/menu/main_menu.xml -->


<!--Ta grupa korzysta z domylnej kategorii. -->
<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/mid_no_search"
android:title="Aktywno braku wyszukiwania" />
<item android:id="@+id/mid_local_search"
android:title="Aktywno wyszukiwania lokalnego" />
<item android:id="@+id/mid_invoke_search"
android:title="Aktywno wywoania wyszukiwania" />
<item android:id="@+id/menu_clear"
android:title="wyczy" />
</group>
</menu>

Po utworzeniu tych plikw moemy skompilowa i przetestowa t aktywno (lub poczeka


do momentu zakoczenia omawiania wszystkich rodzajw aktywnoci tego projektu). Jeeli
chcemy dokona kompilacji teraz, na listingach 23.1 oraz 23.2 (w pliku manifecie) musimy
oznaczy komentarzem wszystkie pozostae aktywnoci. Moemy ewentualnie skorzysta z listingw zdefiniowanych na pocztku podrozdziau, aby skompilowa cay projekt, a dopiero
pniej go testowa.
Po skompilowaniu aplikacji i uruchomieniu gwnej, standardowej aktywnoci ukad graficzny
powinien by podobny do przedstawionego na rysunku 23.12.
Listing 23.5 prezentuje plik XML menu stosowanego w tej standardowej aktywnoci. Wygld
tego menu zosta pokazany na rysunku 23.13.
Po uruchomieniu tej aktywnoci (powinna wyglda jak na rysunku 23.12) kliknijmy przycisk
wyszukiwania (jest on widoczny na rysunku 23.1). Zgodnie z dokumentacj powinno zosta
wywoane okno dialogowe globalnego wyszukiwania.
W wersjach systemu starszych od 2.0 wcinicie przycisku wyszukiwania w odpowiedzi uruchamiao proces wyszukiwania globalnego. W wersjach 2.2 i 2.3 tak si nie dzieje.

Rozdzia 23 Wyszukiwanie w Androidzie

785

Rysunek 23.12. Interakcja standardowa aktywno wyszukiwanie

Rysunek 23.13. Uzyskiwanie dostpu do pozostaych aktywnoci testowych.

Jeeli chcemy wymusi na standardowej aktywnoci korzystanie z wyszukiwania globalnego, musi


ona przesania metod onSearchRequested() i wykonywa nastpujce operacje:
@Override
public boolean onSearchRequested()
{
Log.d(tag,"Wywolano zadanie metody onSearch");

786 Android 3. Tworzenie aplikacji


this.startSearch("test",true,null,true);
return true;
}

Po umieszczeniu powyszego kodu w pliku RegularActivity.java moemy wcisn przycisk wyszukiwania, co spowoduje wywoanie pola globalnego przeszukiwania. Metoda startSearch()
wraz z argumentami zostanie omwiona w dalszej czci rozdziau. Pole globalnego wyszukiwania bdzie wygldao tak jak przedstawione na rysunku 23.2.

Zachowanie aktywnoci wyczajcej wyszukiwanie


Aktywno posiada moliwo cakowitego wyczenia wyszukiwania (zarwno globalnego, jak
i lokalnego) poprzez przekazanie wartoci false z metody zwrotnej onSearchRequested()
danej klasy aktywnoci. Na listingu 23.6 zosta przedstawiony kod rdowy takiej aktywnoci,
nazwanej NoSearchActivity.
Listing 23.6. Aktywno wyczajca wyszukiwanie
//nazwa pliku: NoSearchActivity.java
public class NoSearchActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.no_search_activity);
return;
}
@Override
public boolean onSearchRequested()
{
return false;
}
}

Listing 23.7 prezentuje plik ukadu graficznego tej aktywnoci.


Listing 23.7. Plik XML NoSearchActivity
//nazwa pliku: layout/no_search_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/no_search_activity_prompt"
/>
</LinearLayout>

Rozdzia 23 Wyszukiwanie w Androidzie

787

Po utworzeniu tych dwch plikw (listingi 23.6 oraz 23.7) musimy usun znaki komentarzy
dla kilku sekcji w dwch nastpujcych plikach:
RegularActivity.java (listing 23.1),
AndroidManifest.xml (listing 23.2).
W pliku RegularActivity.java (listing 23.1) usuwamy znaki komentarza z kodu znajdujcego si
w segmencie funkcji invokeNoSearchActivity().
W pliku AndroidManifest.xml (listing 23.2) usuwamy znaki komentarza z definicji aktywnoci
NoSearchActivity. Zwrmy uwag, e mamy do czynienia z plikiem XML. Sposb zaznacza-

nia jako komentarz i usuwania tego oznaczenia w przypadku pliku XML rni si od analogicznej czynnoci przeprowadzanej w pliku Java.
Moemy teraz wywoa aktywno NoSearchActivity, klikajc element menu Aktywno braku
wyszukiwania, widoczny na rysunku 23.13.
Po jego klikniciu zostanie wywietlony ekran pokazany na rysunku 23.14. Teraz wcinicie przycisku wyszukiwania nie spowoduje adnej reakcji; jego wcinicie nie zostanie odnotowane.
W przypadku obecnoci aktywnoci wyczajcej wyszukiwanie kliknicie przycisku
wyszukiwania spowoduje zablokowanie wywoywania zarwno lokalnego, jak i globalnego
wyszukiwania.

Rysunek 23.14. Aktywno wyczonego wyszukiwania

Jawne wywoywanie wyszukiwania za pomoc menu


Oprcz odpowiedzi na kliknicie przycisku wyszukiwania, aktywno moe rwnie w jawny
sposb wywoywa wyszukiwanie za pomoc elementu menu wyszukiwania. Listing 23.8 ukazuje kod rdowy przykadowej aktywnoci (SearchInvokerActivity) posiadajcej tak
moliwo.

788 Android 3. Tworzenie aplikacji


Listing 23.8. Aktywno SearchInvokerActivity
//nazwa pliku: SearchInvokerActivity.java
public class SearchInvokerActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_invoker_activity);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.search_invoker_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
appendMenuItemText(item);
if (item.getItemId() == R.id.mid_si_clear)
{
this.emptyText();
return true;
}
if (item.getItemId() == R.id.mid_si_search)
{
this.invokeSearch();
return true;
}
return true;
}
private TextView getTextView()
{
return (TextView)this.findViewById(R.id.text1);
}
private void appendMenuItemText(MenuItem menuItem)
{
String title = menuItem.getTitle().toString();
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + title);
}
private void emptyText()
{
TextView tv = getTextView();
tv.setText("");
}
private void invokeSearch()
{
this.onSearchRequested();

Rozdzia 23 Wyszukiwanie w Androidzie

789

}
@Override
public boolean onSearchRequested()
{
this.startSearch("test",true,null,true);
return true;
}
}

Najwaniejsze fragmenty kodu zostay zaznaczone pogrubion czcionk. Warto przeanalizowa


sposb, w jaki identyfikator menu (R.id.mid_si_search) wywouje funkcj invokeSearch, ktra
z kolei wywouje metod onSearchRequested(). Metoda ta przywouje proces wyszukiwania.
Metoda bazowa startSearch() posiada nastpujce argumenty:
initialQuery wyszukiwany tekst.
selectInitialQuery warto logiczna okrelajca, czy wyszukiwany tekst ma
zosta zaznaczony, czy nie. W tym przypadku wprowadzamy warto true, dziki
czemu tekst zostanie zaznaczony, co z kolei pozwala na jego wykasowanie i wstawienie
nowego tekstu w razie potrzeby.
appSearchData obiekt typu Bundle przekazywany aktywnoci przeszukujcej.
W tym przypadku nie okrelamy konkretnej aktywnoci wyszukujcej, wprowadzamy
wic tu warto null.
globalSearch w przypadku wartoci true zostanie wywoane pole wyszukiwania
globalnego. W przeciwnym wypadku zostanie w razie moliwoci wywoany proces
wyszukiwania lokalnego; jeeli nie bdzie on dostpny, zostanie wywoane pole
wyszukiwania globalnego.
W przeciwiestwie do tego, co pokazalimy na listingu 23.8, dokumentacja zestawu SDK zaleca
wywoywanie bazowej metody onSearchRequested(). Jednak domylnie metoda ta w ostatnim
argumencie metody startSearch() posiada warto false. Zgodnie z dokumentacj oznacza
to, e w przypadku braku wyszukiwania lokalnego zostanie wywoane pole wyszukiwania globalnego. W tej wersji systemu jednak (dotyczy to zarwno wersji 2.2, jak i 2.3 Androida) wyszukiwanie globalne nie zostaje przywoane. By moe mamy tu do czynienia z bdem lub zamierzonym dziaaniem, ktrego opis nie zosta zaktualizowany w dokumentacji.
W naszym przykadzie wymusilimy wywoanie wyszukiwania globalnego poprzez wstawienie
wartoci true do ostatniego argumentu metody startSearch().
Na listingu 23.9 przedstawiono ukad graficzny tej aktywnoci.
Listing 23.9. Plik XML ukadu graficznego aktywnoci SearchInvokerActivity
//nazwa pliku: layout/search_invoker_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"

790 Android 3. Tworzenie aplikacji


android:layout_height="wrap_content"
android:text="@string/search_invoker_activity_prompt"
/>
</LinearLayout>

Z kolei listing 23.10 zawiera kod XML menu tej aktywnoci.


Listing 23.10. Plik XML menu aktywnoci SearchInvokerActivity
//nazwa pliku: menu/search_invoker_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa korzysta z domylnej kategorii. -->


<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/mid_si_search"
android:title="Szukaj" />
<item android:id="@+id/mid_si_clear"
android:title="wyczy" />
</group>
</menu>

Po utworzeniu tych trzech plikw (listingi 23.8, 23.9 oraz 23.10) musimy usun znaki komentarza z kilku sekcji w dwch nastpujcych plikach:
RegularActivity.java (listing 23.1),
AndroidManifest.xml (listing 23.2).
W pliku RegularActivity.java (listing 23.1) usuwamy znaki komentarza z kodu znajdujcego si
w segmencie funkcji invokeSearchInvokerActivity().
W pliku AndroidManifest.xml (listing 23.2) usuwamy znaki komentarza z definicji aktywnoci
SearchInvokerActivity.

Rysunek 23.15 ukazuje wygld tej aktywnoci po wywoaniu jej z menu gwnego aktywnoci
RegularActivity (na rysunku 23.13 widzimy element menu Aktywno wywoywania wyszukiwania).
W przypadku tej aktywnoci kliknicie przycisku Szukaj spowoduje wywoanie znanego nam
pola wyszukiwania globalnego, widocznego na rysunku 23.2. Wcinicie przycisku wyszukiwania
rwnie spowoduje wywietlenie globalnego pola QSB, poniewa przesonilimy metod onSearch
Requested() bazowej aktywnoci.

Wyszukiwanie lokalne i pokrewne aktywnoci


Okrelmy teraz warunki, w jakich wcinicie przycisku wyszukiwania nie wywoa wyszukiwania
globalnego, lecz lokalne. Najpierw jednak musimy nieco lepiej wyjani pojcie wyszukiwania
lokalnego.
Mechanizm wyszukiwania lokalnego skada si z trzech elementw. Pierwszy z nich stanowi
pole wyszukiwania bardzo przypominajce pole QSB (jeeli nie identyczne). Bez wzgldu na to,
czy pole QSB jest lokalne, czy globalne, jest ono kontrolk tekstow pozwalajc na wprowadzenie danych i uruchomienie przeszukiwania za pomoc ikony wyszukiwania. Lokalne pole

Rozdzia 23 Wyszukiwanie w Androidzie

791

Rysunek 23.15. Aktywno wywoywania wyszukiwania

QSB jest wywoywane, w przypadku gdy aktywno deklaruje w pliku manifecie potrzeb wyszukiwania lokalnego. Lokalne pole QSB mona odrni od pola QSB globalnego po nagwku
(rysunek 23.18) i podpowiedzi (tekcie znajdujcym si wewntrz pola wyszukiwania) tego
widoku. Jak si bdzie mona przekona, te dwie wartoci pochodz z pliku XML metadanych
wyszukiwania.
Drugim elementem wyszukiwania lokalnego jest aktywno odbierajca z lokalnego pola QSB
wpisany cig znakw oraz wywietlajca zbir wynikw lub danych wyjciowych, zwizanych
z tym cigiem znakw. Aktywno ta czsto jest nazywana aktywnoci wyszukiwania lub aktywnoci wynikw wyszukiwania.
Trzecim, opcjonalnym skadnikiem wyszukiwania lokalnego jest aktywno, ktra moe wywoywa dopiero co wspomnian aktywno wynikw wyszukiwania (drugiego skadnika). Aktywno ta jest czsto nazywana wywoaniem wyszukiwania lub aktywnoci wywoujc wyszukiwanie. Jest ona opcjonalna, poniewa istnieje moliwo bezporedniego wywoania
aktywnoci wyszukiwania lokalnego (drugiego elementu) z poziomu wyszukiwania globalnego
za pomoc propozycji.
Na rysunku 23.16 zostao przedstawione oddziaywanie tych skadnikw midzy sob wewntrz
kontekstu.
Najwaniejsze interakcje zaprezentowane na rysunku 23.16 zostay oznaczone numerami. Poniej zostay dokadniej omwione zjawiska ukazane na tym rysunku.
Aktywno SearchActivity musi posiada zdefiniowan w pliku manifecie
moliwo odbierania da wyszukiwania. Wykorzystuje ona rwnie obowizkowy
plik XML do odpowiedniego wywietlenia lokalnego pola QSB (na przykad jego
tytuu, podpowiedzi itd.) oraz powiadomienia o obecnoci powizanego z nim
dostawcy propozycji (listing 23.12). Na rysunku 23.16 etap ten zosta zaznaczony
liniami nazwanymi Definicja, przebiegajcymi pomidzy aktywnoci SearchActivity
a dwoma plikami XML (plikiem manifestem oraz plikiem metadanych wyszukiwania).

792 Android 3. Tworzenie aplikacji

Rysunek 23.16. Interakcja aktywnoci wyszukiwania lokalnego

Po zdefiniowaniu aktywnoci SearchActivity w pliku manifecie (listing 23.2)


aktywno wywoania wyszukiwania wskazuje na ich powizanie ze sob poprzez
definicj metadanych android.app.default_searchable.
Po utworzeniu obydwch definicji wcinicie przycisku wyszukiwania spowoduje
wywoanie lokalnego pola QSB, w przypadku gdy aktywno wywoania wyszukiwania
znajduje si na pierwszym planie. Na rysunku 23.16 jest to oznaczone cyframi 1 i 2.
Moemy stwierdzi, e pole QSB jest lokalne po jego tytule oraz podpowiedzi. Te dwie
wartoci s konfigurowane w obowizkowym pliku definicji XML metadanych
wyszukiwania. Po wywoaniu pola QSB za pomoc przycisku wyszukiwania moemy
umieszcza w nim zapytania dotyczce wyszukiwania. Podobnie jak w przypadku
globalnego pola QSB, rwnie pole lokalne posiada moliwo wywietlania propozycji.
Zostao to zaznaczone na rysunku 23.16 cyfr 3.
Po wprowadzeniu zapytania i klikniciu ikony wyszukiwania dane z pola QSB zostan
przeniesione do aktywnoci SearchActivity, ktra przetwarza kwerendy, na przykad
na ich podstawie wywietla zestaw wynikw. Jest to etap oznaczony na rysunku
23.16 cyfr 4.

Kad z tych interakcji przeanalizujemy pod ktem odpowiedniego kodu rdowego. Rozpoczniemy od listingu 23.11, zawierajcego kod rdowy aktywnoci SearchActivity (ktrej zadaniem jest odbieranie zapyta i wywietlanie wynikw wyszukiwania).
Listing 23.11. Aktywno SearchActivity oraz jej ukad graficzny
//nazwa pliku: SearchActivity.java
public class SearchActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_activity);
return;
}
}

Rozdzia 23 Wyszukiwanie w Androidzie

793

//A take odpowiadajcy jej plik res/layout/search_activity.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/search_activity_prompt"
/>
</LinearLayout>

Wprowadzilimy moliwie najprostsz aktywno wyszukiwania. Pniej wyjanimy, w jaki


sposb zapytania s odbierane przez t aktywno. Na razie zademonstrujemy sposb jej wywoywania przez pole QSB. Jest ona zdefiniowana w pliku manifecie jako aktywno wyszukiwania odpowiedzialna za wyniki w nastpujcy sposb (listing 23.2):
<activity android:name=".SearchActivity"
android:label="Interakcja aktywno/QSB:Wyniki wyszukiwania">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>

Naley okreli dwa elementy dla aktywnoci wyszukiwania. Musi ona zadeklarowa
moliwo odpowiedzi na dziaania SEARCH, a take okrela plik XML, w ktrym
zawarty jest opis metadanych wymaganych do interakcji z t aktywnoci.

Na listingu 23.12 prezentujemy zawarto pliku XML metadanych wyszukiwania dla aktywnoci
SearchActivity.
Listing 23.12. Plik searchable.xml metadane wyszukiwania
<!-- /res/xml/searchable.xml -->
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
android:hint="@string/search_hint"
android:searchMode="showSearchLabelAsBadge"
/>

Omwienie rnych opcji dostpnych w tym pliku XML znajduje si pod adresem
http://developer.android.com/reference/android/app/SearchManager.html.

794 Android 3. Tworzenie aplikacji


W dalszej czci rozdziau zajmiemy si omwieniem wikszoci tych atrybutw. Na razie wystarczy zapamita, e atrybut android:label suy do przypisania etykiety polu wyszukiwania.
Dziki atrybutowi android:hint moemy umieszcza tekst wewntrz pola wyszukiwania, co
jest widoczne na rysunku 23.18.
Zobaczmy teraz, w jaki sposb dowolna aktywno moe wyznaczy t aktywno Search
Activity jako swojego adresata wyszukiwania. Nazwiemy t aktywno LocalSearchEnabled
Activity. Na listingu 23.13 zosta umieszczony jej kod rdowy.
Listing 23.13. Aktywno LocalSearchEnabledActivity
//nazwa pliku: LocalSearchEnabledActivity.java
public class LocalSearchEnabledActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.local_search_enabled_activity);
return;
}
}

Listing 23.14 przedstawia plik XML ukadu graficznego tej aktywnoci.


Listing 23.14. Plik ukadu graficznego aktywnoci LocalSearchEnabledActivity
<?xml version="1.0" encoding="utf-8"?>

<!-- nazwa pliku: layout/local_search_enabled_activity.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/local_search_enabled_activity_prompt"
/>
</LinearLayout>

Zwrmy rwnie uwag, e klasa LocalSearchEnabledActivity (listing 23.14) definiuje


klas SearchActivity (listing 23.11) jako swoj docelow aktywno wyszukiwania. Relacj
t odnajdziemy w definicji aktywnoci LocalSearchEnabledActivity (listing 23.2). Poniej
przypominamy tre tej definicji:
<activity android:name=".LocalSearchEnabledActivity"
android:label="Interakcja aktywno/QSB::Wyszukiwanie lokalne">
<meta-data android:name="android.app.default_searchable"
android:value=".SearchActivity" />
</activity>

Rozdzia 23 Wyszukiwanie w Androidzie

795

A teraz moemy skompletowa wszystkie nowe pliki w celu przetestowania dwch nowych
aktywnoci: LocalSearchEnabledActivity oraz SearchActivity. Nazwy plikw oraz numery
odpowiadajcych im listingw znajdziemy poniej:
SearchActivity.java (listing 23.11),
layout/search_activity.xml (cz listingu 23.11),
res/xml/searchable.xml (listing 23.12),
LocalSearchEnabledActivity.java (listing 23.13),
local_search_enabled_activity (listing 23.14).
Po utworzeniu tych plikw musimy usun znaki komentarza z kilku sekcji w dwch nastpujcych plikach:
RegularActivity.java (listing 23.1),
AndroidManifest.xml (listing 23.2).
W pliku RegularActivity.java (listing 23.1) usuwamy znaki komentarza z kodu znajdujcego si
w segmencie funkcji invokeLocalSearchActivity().
W pliku AndroidManifest.xml (listing 23.2) usuwamy znaki komentarza z definicji aktywnoci
LocalSearchEnabledActivity oraz SearchActivity.

Po udanym usuniciu znakw komentarzy z kodu w obydwu plikach mona ponownie skompilowa projekt.
Po uzyskaniu tych aktywnoci wraz z ich ukadami graficznymi moemy wywoa aktywno
z poziomu gwnej aktywnoci RegularActivity, klikajc
element menu Aktywno wyszukiwania lokalnego (na rysunku 23.13 s widoczne elementy
menu). Po wywoaniu aktywno bdzie wygldaa tak jak na rysunku 23.17.
LocalSearchEnabledActivity

Rysunek 23.17. Aktywno uruchamiajca wyszukiwanie lokalne

Jeeli aktywno ta znajduje si na pierwszym planie, wcinicie na urzdzeniu przycisku


wyszukiwania wywoa pole lokalnego wyszukiwania (lokalne pole QSB), uwidocznione na rysunku 23.18.

796 Android 3. Tworzenie aplikacji

Rysunek 23.18. Pole wyszukiwania lokalnego

Spjrzmy na etykiet oraz podpowied tego pola wyszukiwania. Rni si one od analogicznych elementw pola globalnego wyszukiwania (rysunek 23.2). Pochodz one z metadanych
wyszukiwania zdefiniowanych dla aktywnoci SearchActivity (plik searchable.xml z listingu
23.12). Jeeli wprowadzimy teraz jaki tekst w polu QSB i klikniemy ikon wyszukiwania, zostanie wywoana aktywno SearchActivity (listing 23.11). Ekran tej aktywnoci zosta pokazany na rysunku 23.19.

Rysunek 23.19. Wyniki wyszukiwania w odpowiedzi na dane wpisane w polu wyszukiwania lokalnego

Chocia ta aktywno nie wykorzystuje adnej kwerendy wyszukiwania tekstu do uzyskiwania


zestawu wynikw, suy ona do pokazania sposobu definiowania aktywnoci oraz jej wywoywania. W dalszej czci rozdziau zademonstrujemy mechanizm wykorzystywania przez aktywno SearchResults kwerend wyszukiwania oraz rnych dziaa wymaganych do przetwarzania tych zapyta.

Rozdzia 23 Wyszukiwanie w Androidzie

797

Uruchomienie funkcji type-to-search


Dotychczas zaprezentowalimy kilka sposobw wywoywania wyszukiwania lokalnego i globalnego. Pokazalimy, w jaki sposb mona przeprowadza wyszukiwanie za pomoc pola QSB
na stronie startowej urzdzenia. Wyjanilimy, jak mona wywoa wyszukiwanie globalne z poziomu dowolnej aktywnoci, pod warunkiem e moliwo wyszukiwania nie zostaa w tej
aktywnoci wyczona. Omwilimy rwnie okrelanie wyszukiwania lokalnego za pomoc
aktywnoci. Zakoczymy ten temat, pokazujc jeszcze jeden sposb wywoywania wyszukiwania,
zwany type-to-search (wpisz, aby szuka).
Jeli przeanalizujemy takie aktywnoci, jak RegularActivity ukazana na rysunku 23.12, to
stwierdzimy, e mona wywoa wyszukiwanie, wpisujc losowo wybran liter (na przykad t).
Jest to tryb noszcy nazw type-to-search, gdy nacinicie dowolnego przycisku niewykorzystywanego przez aktywno wywoa proces wyszukiwania.
Zaoenia mechanizmu type-to-search s cakiem proste. W przypadku dowolnej aktywnoci
Androida moemy wprowadzi ustawienie, ktre spowoduje, e wcinicie jakiegokolwiek
przycisku oprcz przyciskw jawnie obsugiwanych przez aktywno wywoa wyszukiwanie. Jeeli na przykad aktywno obsuguje wycznie przyciski x i y, do wywoania wyszukiwania mog posuy wszystkie pozostae, chociaby z lub a. Jest to przydatny tryb w przypadku aktywnoci ju wywietlajcej wyniki wyszukiwania. Aktywno taka moe interpretowa
wcinicie przycisku jako sygna do rozpoczcia nowego wyszukiwania.
Poniej umiecilimy dwa przykadowe wiersze kodu suce do uruchomienia takiego zachowania, wstawiane do metody onCreate() (pierwszy wiersz jest odpowiedzialny za wywoywanie wyszukiwania globalnego, a drugi za wywoywanie wyszukiwania lokalnego):
this.setDefaultKeyMode(Activity.DEFAULT_KEYS_SEARCH_GLOBAL);

lub
this.setDefaultKeyMode(Activity.DEFAULT_KEYS_SEARCH_LOCAL);

Wydaje si, e wywoania globalnego wyszukiwania za pomoc mechanizmu type-to-search


nie pod ciek z wykorzystaniem metody onSearchRequested(). Wcinicie przycisku
wywouje wyszukiwanie globalne w bezporedni sposb. W wyniku tego moe si wydawa,
e utworzona w naszym przykadzie klasa RegularActivity bdzie wywoywa wyszukiwanie
globalne w momencie aktywowania funkcji type-to-search (przypomnijmy sobie, e w trakcie
testowania standardowej aktywnoci, ktra nie wczaa ani nie wyczaa w jawny sposb wyszukiwania, prba przywoania pola wyszukiwania globalnego za pomoc przycisku koczya
si niepowodzeniem). Moemy przetestowa zachowanie funkcji type-to-search, umieszczajc
nastpujcy wiersz kodu na kocu metody onCreate() w klasie RegularActivity (listing 23.1):
this.setDefaultKeyMode(Activity.DEFAULT_KEYS_SEARCH_GLOBAL);

Teraz wprowadzenie jakiej litery, np. t, na ekranie widocznym na rysunku 23.12 spowoduje
wywoanie pola przeszukiwania globalnego.
Na tym zakoczymy omawianie interakcji wyszukiwania z aktywnociami w Androidzie oraz
mechanizmw korzystania z funkcji wyszukiwania. Dowiemy si teraz, w jaki sposb moemy
nie tylko korzysta z procesu wyszukiwania, lecz rwnie wpywa na to, jak przebiega. Zaimplementujemy w tym celu prostego dostawc propozycji wobec globalnego oraz lokalnego pola
wyszukiwania.

798 Android 3. Tworzenie aplikacji

Implementacja prostego dostawcy propozycji


Mamy do czynienia z obszernym rozdziaem, jeli wic Czytelnik dotychczas pracowa z nim
bez przerwy, warto chwil odpocz czeka nas teraz kolejna dua porcja materiau, wymagajca cakowitego skupienia.
Zdylimy wspomnie, w jaki sposb wykorzystuje si dostawcw propozycji do umoliwienia
aplikacjom wspudziau w procesie wyszukiwania globalnego. Przyszed czas na zaprojektowanie i napisanie prostego dostawcy propozycji. Wystarczy nam w tym celu kilka wierszy kodu
pochodzcych z przygotowanego wczeniej dostawcy SearchRecentSuggestionsProvider,
umieszczonego w zestawie Android SDK.
Rozpoczniemy od wyjanienia zasady dziaania aplikacji zawierajcej prostego dostawc
propozycji. Zamiecimy list plikw stosowanych w procesie implementacji. Wspomniana
lista powinna da Czytelnikowi oglne pojcie na temat aplikacji oraz implementowanych
w niej mechanizmw.
Podczas pisania dostawcy propozycji wykorzystywane s trzy gwne skadniki. Pierwszym
z nich jest sam dostawca propozycji, ktry przekazuje propozycje procesowi wyszukiwania.
Drugim skadnikiem jest aktywno wyszukiwania, pobierajca zapytanie lub propozycj i przeksztacajca je do wynikw wyszukiwania. Trzeci element nosi nazw metadanych wyszukiwania i jest zdefiniowany wewntrz kontekstu aktywnoci wyszukiwania. Omwimy zadania
kadego z wymienionych elementw i zademonstrujemy sposb ich implementacji w kodzie
rdowym.
Najpierw jednak utwrzmy plan naszej aplikacji prostego dostawcy propozycji.

Planowanie prostego dostawcy propozycji


Dziaanie wynikowego dostawcy propozycji jest z gry okrelone, poniewa planujemy jego
dziedziczenie od dostawcy SearchRecentSuggestionsProvider.
Dostawca SearchRecentSuggestionsProvider umoliwia nam zapisywanie kwerend w czasie
prezentowania aktywnoci wyszukiwania. Po ich zapisaniu przez aktywno wyszukiwania zostan one przekazane polu QSB poprzez dostawc propozycji w czasie wpisywania liter lub tekstu
w polu QSB.
W pochodnym dostawcy treci po prostu inicjalizujemy bazowego dostawc poprzez wskazanie
fragmentw wyszukiwanego tekstu, ktre bd powtarzane. W tym przypadku mamy niewiele
wicej pracy. Wstawimy take minimalistyczn wersj aktywnoci wyszukiwania, stanowicej
zwyky widok tekstu, dziki czemu dowiemy si o wywoaniu tej aktywnoci. Wewntrz tej aktywnoci zaprezentujemy metody suce do odczytywania i zapisywania kwerend, dziki czemu
bd one dostpne dla dostawcy wyszukiwania.
Naszym zadaniem po utworzeniu aplikacji bdzie przejrzenie wczeniejszych kwerend, umieszczonych w globalnym oraz lokalnym polu QSB w postaci propozycji.
Zapoznamy si teraz z list plikw potrzebnych do zaimplementowania naszego projektu. Moemy rwnie pobra poszczeglne pliki spod adresu zamieszczonego na kocu rozdziau.

Rozdzia 23 Wyszukiwanie w Androidzie

799

Pliki implementacji prostego dostawcy propozycji


Do podstawowych plikw biorcych udzia w implementacji aplikacji dostawcy propozycji nale
SearchActivity.java, SimpleSuggestionProvider.java oraz searchable.xml (metadane wyszukiwania). Jednak aby nasza przykadowa aplikacja dziaaa poprawnie, potrzebne bd take inne
pliki. Wymienimy je wszystkie wraz z krtkim opisem kadego z nich. W trakcie omawiania
budowy aplikacji bdziemy prezentowa kod rdowy poszczeglnych plikw.
Na pierwszy ogie pjd pliki Java:

SimpleSuggestionProvider.java implementuje omawianego w tym podrozdziale


dostawc propozycji w procesie dziedziczenia po bazowym dostawcy propozycji,
dostpnym w zestawie SDK (listing 23.15).

SearchActivity.java plik wymagany do dziaania dostawcy propozycji,


otrzymujcy poszukiwany tekst oraz przekazujcy wyniki wyszukiwania. Klasa ta
rwnie zapewnia zapisywanie kwerend dla dostawcy propozycji (listing 23.17).

SimpleMainActivity.java aktywno wywoujca pole lokalnego wyszukiwania


oraz zawierajca lokalne propozycje (listing 23.19).

Poniej znajduj si odpowiednie pliki ukadu graficznego:

main.xml plik ukadu graficznego aktywnoci SimpleMainActivity


(cz listingu 23.19).

/res/layout/layout_search_activity.xml ukad graficzny aktywnoci


SearchActivity (cz listingu 23.17).

/res/values/strings.xml pliki ukadu graficznego korzystaj z obecnych tu definicji


cigw znakw (cz listingu 23.19).

Tutaj znajduje si plik metadanych wyszukiwania.

/xml/searchable.xml poprzez ten plik aktywno wyszukiwania jest poczona


z dostawc propozycji (listing 23.18).

Oczywicie nie moe zabrakn pliku manifestu:

AndroidManifest.xml w tym pliku s zdefiniowane wszystkie skadniki aplikacji


(listing 23.16).

Jeeli Czytelnik planuje kompilowanie tego projektu bezporednio poprzez kopiowanie i wklejanie kodu rdowego z tej ksiki, radzimy dokonywa tego zgodnie z numeracj listingw.
Alternatywnym rozwizaniem jest pobranie projektw zwizanych z tym rozdziaem, umieszczonych pod adresem URL, ktry znajduje si na kocu rozdziau.
Rozpocznijmy analiz tych plikw od implementacji klasy SimpleSuggestionProvider.

Implementacja klasy SimpleSuggestionProvider


W tym projekcie prostego dostawcy propozycji klasa SimpleSuggestionProvider peni rol
dostawcy propozycji poprzez dziedziczenie po klasie SearchRecentSuggestionsProvider. Przyjrzyjmy si najpierw zadaniom tego prostego dostawcy propozycji.

800 Android 3. Tworzenie aplikacji

Zadania prostego dostawcy propozycji


Poniewa nasz prosty dostawca propozycji wywodzi si z klasy SearchRecentSuggestions
Provider, wikszo czynnoci jest przeprowadzana przez bazowego dostawc. Aby przekazywa podpowiedzi do bazowego dostawcy, klasa prostego dostawcy propozycji musi zainicjalizowa bazow klas wraz z unikatowym uprawnieniem. Wynika to z faktu, e proces wyszukiwania
w Androidzie wywouje dostawc propozycji na podstawie niepowtarzalnego identyfikatora URI
dostawcy treci. Z kolei dostawcy treci w Androidzie s przywoywani za pomoc nazw ich
domen, stanowicych cigi znakw zwane uprawnieniami (w rozdziale 4. znajdziemy szczegowe informacje dotyczce cigw znakw uprawnie).
Po zaimplementowaniu dostawcy propozycji za pomoc takiego prostego wywoania bazowej
klasy trzeba go skonfigurowa w pliku manifecie jako standardowego dostawc treci zawierajcego uprawnienie. Nastpnie trzeba go powiza (niebezporednio, za pomoc pliku
searchable.xml) z aktywnoci wyszukiwania. Definicja aktywnoci wyszukiwania odnosi si
do pliku searchable.xml, ktry z kolei wskazuje dostawc propozycji.
Przeanalizujmy kod rdowy tego dostawcy i sprawdmy, jak si ma ten kod do czci wymienionych zada tej klasy.

Peny kod rdowy klasy SimpleSuggestionProvider


Poniewa dziedziczymy po klasie SearchRecentSuggestionsProvider, kod rdowy prostego
dostawcy treci nie bdzie skomplikowany taki jak przedstawiony na listingu 23.15.
Listing 23.15. Plik SimpleSuggestionProvider.java
//SimpleSuggestionProvider.java
public class SimpleSuggestionProvider
extends SearchRecentSuggestionsProvider {
final static String AUTHORITY =
"com.androidbook.search.simplesp.SimpleSuggestionProvider";
final static int MODE =
DATABASE_MODE_QUERIES | DATABASE_MODE_2LINES;
public SimpleSuggestionProvider() {
super();
setupSuggestions(AUTHORITY, MODE);
}
}

Warto zwrci uwag na kilka istotnych elementw na listingu 23.15.


1. Inicjalizacja nadrzdnej klasy.
2. Konfiguracja bazowego dostawcy za pomoc uprawnienia i trybu (wskazujcego,
ktre fragmenty poszukiwanego tekstu maj zosta zapamitane).
Cig znakw uprawnienia musi by niepowtarzalny oraz odpowiada definicji jego dostawcy
treci umieszczonej w pliku manifecie (kod pliku manifestu znajdziemy na listingu 23.16).
Przyjrzyjmy si trybowi bazodanowemu, czyli drugiemu argumentowi metody
Suggestions().

setup

Rozdzia 23 Wyszukiwanie w Androidzie

801

Tryby bazodanowe klasy SearchRecentSuggestionsProvider


Podstaw dziaania dostpnej w Androidzie klasy SearchRecentSuggestionsProvider jest
przechowywanie i odtwarzanie kwerend z bazy danych w celu pniejszego ich wywietlania
jako propozycji wyszukiwania. Taka propozycja zawiera dwa cigi znakw (rysunek 23.2). Jedynie
pierwszy cig znakw jest obowizkowy. Podczas uywania klasy SearchRecentSuggestions
Provider do odtwarzania tych danych musimy okreli, czy chcemy przechowywa jeden
cig, czy dwa cigi znakw.
Do tego celu su dwa tryby (bity trybu pracy) obsugiwane przez tego bazowego dostawc propozycji. W obydwu przypadkach jest stosowany przedrostek:
DATABASE_MODE_

Oto te tryby:
DATABASE_MODE_QUERIES

(warto binarna 1),


DATABASE_MODE_2LINES (warto binarna 2).

Za pomoc pierwszego trybu okrelamy potrzeb przechowywania i wywietlania tylko jednego


cigu znakw. Dziki drugiemu trybowi dostawca propozycji moe przechowywa dwa cigi
znakw. Pierwszym cigiem znakw jest kwerenda, natomiast drugim opis danej propozycji.
Aktywno SearchActivity zachowuje te cigi znakw, jeli zostanie wywoana w celu udzielenia odpowiedzi na kwerend. W celu zachowania tych elementw klasa SearchActivity
skorzysta z nastpujcej metody (zostanie ona szczegowo omwiona podczas analizy aktywnoci wyszukiwania):
public class SearchRecentSuggestions
{
...
public void saveRecentQuery (String queryString, String line2);
...
}

Klasa SearchRecentSuggestions stanowi cz pakietu SDK. Omwimy j dokadniej


podczas analizy aktywnoci wyszukiwania, umieszczonej na listingu 23.17.

Warto queryString stanowi cig znakw wpisany przez uytkownika. Zostanie ona wywietlona jako propozycja, a jeeli uytkownik j kliknie, cig ten zostanie przesany do aktywnoci wyszukiwania (jako nowe zapytanie wyszukiwania).
Poniej przedstawiamy opis dokumentacji Androida na temat argumentu line2:

Jeeli biecy dostawca propozycji zosta skonfigurowany za pomoc trybu DATABASE_


MODE_2LINES, mona w tym argumencie przesa drug lini tekstu. Tekst ten zostanie
wywietlony mniejsz czcionk pod gwn propozycj. W trakcie wpisywania tekstu
na licie bd si ukazywa pasujce wyraenia, obecne w obydwu wierszach propozycji. Jeeli nie zostanie skonfigurowany tryb dwch linii lub jeli dana propozycja
nie zawiera dodatkowego tekstu, mona wstawi tu warto null.
W naszym przykadzie chcemy zachowa zarwno kwerend, jak i pomocniczy tekst, wywietlany wraz z kwerend w propozycji, a przynajmniej sprbujemy zaprezentowa go w postaci
dostawcy SSSP (ang. Search Simple Suggestion Provider prosty dostawca propozycji wyszu-

802 Android 3. Tworzenie aplikacji


kiwania) pod propozycj. Kiedy wic propozycje generowane przez dostawc zostan wywietlone w polu globalnego wyszukiwania, bdziemy wiedzie, ktra aplikacja jest odpowiedzialna
za przeszukiwanie tekstu.
Aby zapewni, e propozycja wraz z pomocniczym tekstem bd zapisywane, musimy, zgodnie
z listingiem 23.15, ustawi dwa bity trybu pracy. Jeeli zdefiniujemy wycznie jeden bit odpowiedzialny za zachowywanie dwch wierszy, bdzie si pojawia wyjtek nieprawidowego argumentu. Wrd bitw trybu pracy musi si znajdowa przynajmniej bit DATABASE_
MODE_QUERIES. W istocie naley zaimplementowa bitow operacj OR. Zatem tryby te s
komplementarne i nie wykluczaj si wzajemnie.
Informacje na temat domylnego dostawcy treci mona znale pod adresem:
http://developer.android.com/reference/android/provider/SearchRecentSuggestions.html.

Po utworzeniu kodu rdowego naszego prostego dostawcy treci sprawdmy, jak moemy go
zarejestrowa w pliku manifecie.

Deklarowanie dostawcy propozycji w pliku manifecie


Poniewa klasa SimpleSuggestionProvider jest zasadniczo dostawc treci, musi zosta
zarejestrowana w pliku manifecie. Zawarto tego pliku zostaa pokazana na listingu 23.16.
Najwaniejsze fragmenty kodu zostay zaznaczone pogrubion czcionk.
Listing 23.16. Plik manifest zawierajcy definicj dostawcy SimpleSuggestionProvider
//nazwa pliku: AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.search.simplesp"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon"
android:label="Prosty dostawca propozycji wyszukiwania (SSSP)">
<activity android:name=".SimpleMainActivity"
android:label="SSSP:Prosta aktywno gwna">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-****************************************************************
* Kod zwizany z wyszukiwaniem: aktywno wyszukiwania
****************************************************************
-->
<activity android:name=".SearchActivity"
android:label="SSSP: Aktywno wyszukiwania"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />

Rozdzia 23 Wyszukiwanie w Androidzie

803

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


</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<meta-data android:name="android.app.default_searchable"
android:value=".SearchActivity" />
<provider android:name=".SimpleSuggestionProvider"
android:authorities
="com.androidbook.search.simplesp.SimpleSuggestionProvider" />
</application>
<uses-sdk android:minSdkVersion="4" />
</manifest>

Zwrmy uwag na uprawnienie prostego dostawcy propozycji w pliku kodu rdowego (listing
23.15) oraz w pliku manifecie (listing 23.16). W obydwu przypadkach jego warto jest nastpujca:
com.androidbook.search.simplesp.SimpleSuggestionProvider

Pozostaymi sekcjami pliku manifestu zajmiemy si po omwieniu innych aspektw prostego


dostawcy propozycji. Jak wida po pliku manifecie, kluczow rol odgrywa aktywno wyszukiwania. Przyjrzyjmy si wic jej teraz uwaniej. Drug aktywnoci, SimpleMainActivity,
zajmiemy si pod koniec podrozdziau, poniewa jest ona wycznie aktywnoci sterujc,
suc tylko do uruchomienia aplikacji.

Aktywno wyszukiwania dostpna


w prostym dostawcy propozycji
Aktywno wyszukiwania jest wywoywana przez system (pole QSB) za pomoc cigu znakw
stanowicego kwerend. Aktywno ta musi z kolei odczytywa t kwerend z intencji i wykonywa odpowiednie czynnoci, a take, potencjalnie, wywietla wyniki.
Poniewa mamy do czynienia z aktywnoci, istnieje moliwo jej wywoania za pomoc innych
intencji lub dziaa. Z tego powodu dobrym zwyczajem jest sprawdzanie intencji wywoujcej
t aktywno. W naszym przypadku dziaaniem wywoujcym aktywno jest ACTION_SEARCH.
W pewnych okolicznociach aktywno wyszukiwania moe zosta samoistnie wywoana.
W przypadku duego prawdopodobiestwa wystpienia takiej sytuacji naley zdefiniowa tryb
singleTop uruchamiania tej aktywnoci. Aktywno powinna rwnie posiada zdolno
uruchamiania metody onNewIntent(). Ta kwestia zostanie poruszona w podpunkcie Metody
onCreate() i onNewIntent().
Kada czynno wykonywana na cigu znakw kwerendy zostanie zapisana w dzienniku. Po zapisaniu kwerendy w dzienniku musimy j zapisa w dostawcy SearchRecentSuggestions
Provider, aby w przypadku kolejnych wyszukiwa bya wywietlana jako jedna z propozycji.
Spjrzmy teraz na kod rdowy klasy aktywnoci wyszukiwania.

804 Android 3. Tworzenie aplikacji

Peny kod rdowy aktywnoci wyszukiwania


Na listingu 23.17 zosta przedstawiony kod rdowy klasy SearchActivity.
Listing 23.17. Aktywno wyszukiwania dla dostawcy SimpleSuggestionProvider
//nazwa pliku: SearchActivity.java
public class SearchActivity extends Activity
{
private final static String tag ="SearchActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(tag,"Jestem tworzona");

//w przeciwnym wypadku wykonuje t czynno


setContentView(R.layout.layout_test_search_activity);

//this.setDefaultKeyMode(Activity.DEFAULT_KEYS_SEARCH_GLOBAL);
this.setDefaultKeyMode(Activity.DEFAULT_KEYS_SEARCH_LOCAL);

// umieszcza tutaj i przetwarza kwerend wyszukiwania


final Intent queryIntent = getIntent();
final String queryAction = queryIntent.getAction();
if (Intent.ACTION_SEARCH.equals(queryAction))
{
Log.d(tag,"nowa intencja dla wyszukiwania");
this.doSearchQuery(queryIntent);
}
else {
Log.d(tag,"nowa intencja NIE dla wyszukiwania");
}
return;
}
@Override
public void onNewIntent(final Intent newIntent)
{
super.onNewIntent(newIntent);
Log.d(tag,"nowa intencja mnie wywoujca");

// umieszcza tutaj i przetwarza kwerend wyszukiwania


final Intent queryIntent = getIntent();
final String queryAction = queryIntent.getAction();
if (Intent.ACTION_SEARCH.equals(queryAction))
{
this.doSearchQuery(queryIntent);
Log.d(tag,"nowa intencja dla wyszukiwania");
}
else {
Log.d(tag,"nowa intencja NIE dla wyszukiwania");
}
}
private void doSearchQuery(final Intent queryIntent)
{
final String queryString =

Rozdzia 23 Wyszukiwanie w Androidzie

805

queryIntent.getStringExtra(SearchManager.QUERY);

// zapisuje cig znakw kwerendy w biecym dostawcy propozycji kwerend.


SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
SimpleSuggestionProvider.AUTHORITY,
SimpleSuggestionProvider.MODE);
suggestions.saveRecentQuery(queryString, "SSSP");
}
}

//Poniej zosta umieszczony plik ukadu graficznego odpowiadajcy omawianej


//aktywnoci. Wytnijmy ten kod i wstawmy go do osobnego pliku
//ukadu graficznego. W komentarzu zostaa wstawiona lokalizacja pliku.
<?xml version="1.0" encoding="utf-8"?>

<!-- /res/layout/layout_search_activity.xml -->


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Widok aktywnoci testujcej wyszukiwanie"
/>
</LinearLayout>

Po zapoznaniu si z zawartoci listingu 23.17 przeledmy, w jaki sposb aktywno wyszukiwania sprawdza dziaanie i odczytuje cig znakw kwerendy.

Sprawdzanie dziaania i odczytywanie kwerendy


Kod aktywnoci wyszukiwania sprawdza wywoujce j dziaanie poprzez przeanalizowanie
intencji wywoujcej i porwnanie jej ze sta intent.ACTION_SEARCH. Jeeli mamy do czynienia
z takim samym dziaaniem, zostaje wywoana funkcja doSearchQuery().
Za pomoc tej funkcji aktywno wyszukiwania odczytuje cig znakw kwerendy, uywajc
dodatkowej intencji. Suy do tego celu kod:
final String queryString =
queryIntent.getStringExtra(SearchManager.QUERY);

Zauwamy, e ta dodatkowa intencja zostaa zdefiniowana jako SearchManager.QUERY. W tym


rozdziale znajduje si wiele takich elementw dodatkowych, zdefiniowanych w odniesieniu do
interfejsu API SearchManager (adres do dodatkowych materiaw dotyczcych tego zakresu
zosta umieszczony w podrozdziale Odnoniki).

Metody onCreate() i onNewIntent()


Uruchomienie aktywnoci wyszukiwania nastpuje po wpisaniu przez uytkownika tekstu
w polu wyszukiwania i klikniciu propozycji lub strzaki nawigacji. W wyniku tego system tworzy

806 Android 3. Tworzenie aplikacji


aktywno wyszukiwania i wywouje jej metod onCreate(). Intencja przekazana tej metodzie
ustanowi dziaanie ACTION_SEARCH.
Czasem zdarza si, e aktywno nie zostaje utworzona, lecz s przekazywane nowe kryteria wyszukiwania poprzez metod onNewIntent(). Jak to si dzieje? Metoda zwrotna onNewIntent()
jest cile zwizana z trybem uruchamiania aktywnoci. Przygldajc si listingowi 23.16, moemy
stwierdzi, e aktywno wyszukiwania otrzymuje warto singleTop w pliku manifecie.
Aktywno skonfigurowana w trybie singleTop informuje system, aby nie tworzy nowej
aktywnoci, jeeli znajdzie si ona na wierzchu stosu. W takim przypadku zamiast metody
onCreate() zostaje wywoana metoda onNewIntent(). Wanie dlatego w kodzie rdowym
aktywnoci (listing 23.17) intencja jest sprawdzana w dwch miejscach.

Testowanie metody onNewIntent()


Po zaimplementowaniu metody onNewIntent() stwierdzimy, e jest ona wywoywana w niestandardowy sposb. Rodzi si nastpujce pytanie: kiedy aktywno wyszukiwania znajduje si
na wierzchu stosu? To si zazwyczaj nie zdarza.
Wyjanijmy dlaczego. Zamy, e aktywno A wywouje proces wyszukiwania, wskutek czego
pojawia si aktywno wyszukiwania B. Aktywno B powoduje wywietlenie wynikw, a uytkownik wraca do poprzedniego ekranu za pomoc przycisku powrotu. W tym czasie aktywno
B, stanowica nasz aktywno wyszukiwania, ustpuje miejsca na szczycie stosu aktywnoci A.
Uytkownik moe ewentualnie wcisn przycisk powrotu do ekranu startowego i skorzysta
z wyszukiwania globalnego, co spowoduje umieszczenie aktywnoci ekranu startowego na
wierzchu stosu.
Aktywno wyszukiwania mona umieci na wierzchu stosu w nastpujcy sposb: powiedzmy, e w efekcie wyszukiwania wynikiem aktywnoci A jest aktywno B. Jeeli aktywno B
definiuje tryb type-to-search, to po przejciu do tej aktywnoci zostanie ona ponownie wywoana,
z nowymi kryteriami. Listing 23.17 ukazuje sposb konfiguracji trybu type-to-search. Oto
odpowiedni fragment kodu:
this.setDefaultKeyMode(Activity.DEFAULT_KEYS_SEARCH_LOCAL);

Zapisywanie kwerendy za pomoc dostawcy


SearchRecentSuggestionsProvider
Stwierdzilimy, e istnieje konieczno zachowywania napotkanych kwerend przez aktywno
wyszukiwania w celu wywietlania ich w formie propozycji za pomoc dostawcy. Poniej przedstawiamy segment kodu odpowiedzialny za zapisywanie kwerend:
final String queryString =
queryIntent.getStringExtra(SearchManager.QUERY);

// Zapisuje cig znakw kwerendy w biecym dostawcy propozycji kwerend.


SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
SimpleSuggestionProvider.AUTHORITY,
SimpleSuggestionProvider.MODE);
suggestions.saveRecentQuery(queryString, "SSSP");

Powyszy kod udowadnia, e Android przekazuje informacj kwerendy w postaci parametru


EXTRA (SearchManager.Query) poprzez intencj.

Rozdzia 23 Wyszukiwanie w Androidzie

807

Po wprowadzeniu kwerendy mona wykorzysta klas zestawu SDK SearchRecentSuggestions


do zachowania kwerendy oraz podpowiedzi ("SSSP") poprzez utworzenie i zachowanie nowej
propozycji. Poniewa skorzystalimy z trybu zachowywania dwch wierszy oraz trybu kwerendy,
warto drugiego argumentu metody saveRecentQuery wynosi SSSP (prosty dostawca propozycji
wyszukiwania). Tekst zawarty w tym argumencie bdzie si pojawia pod propozycjami generowanymi przez dostawc.
Zajmiemy si teraz definicj metadanych wyszukiwania, czc aktywno wyszukiwania
z dostawc propozycji wyszukiwania.

Metadane wyszukiwania
Definiowanie wyszukiwania w Androidzie rozpoczyna si od aktywnoci wyszukiwania. Definiujemy j najpierw w pliku manifecie. Czci jej definicji jest okrelenie miejsca, w ktrym
znajdzie si plik XML metadanych wyszukiwania. Przyjrzyjmy si listingowi 23.16, w ktrym
zdefiniowalimy aktywno wyszukiwania wraz ze ciek do pliku metadanych (searchable.xml).
Listing 23.18 przedstawia plik metadanych wyszukiwania wykorzystywany przez nasz aplikacj.
Listing 23.18. Metadane wyszukiwania dla dostawcy SimpleSuggestionProvider
<!-- nazwa pliku: /res/xml/searchable.xml -->
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
android:hint="@string/search_hint"
android:searchMode="showSearchLabelAsBadge"
android:queryAfterZeroResults="true"
android:includeInGlobalSearch="true"
android:searchSuggestAuthority=
"com.androidbook.search.simplesp.SimpleSuggestionProvider"
android:searchSuggestSelection=" ? "
/>

Przeanalizujmy kluczowe atrybuty widoczne na listingu 23.18.


Atrybut includeInGlobalSearch wskazuje, aby uywa tego dostawcy jako jednego ze rde
globalnego pola QSB.
Atrybut searchSuggestAuthority okrela zdefiniowane w pliku manifecie uprawnienie tego
dostawcy propozycji (listing 23.16).
Atrybut queryAfterZeroResults suy do okrelania, czy pole QSB ma wysya wicej liter do
dostawcy propozycji, w przypadku gdy biecy zbir znakw nie pozwoli na otrzymanie adnych wynikw. Poniewa znajdujemy si na etapie testowania i nie chcemy pomija adnej
funkcji, przydzielamy temu atrybutowi warto true, dziki czemu dostawca bdzie reagowa
przy kadej nadarzajcej si okazji.
Kolejny atrybut, searchSuggestSelection, przyjmuje zawsze warto ?, w przypadku gdy wywodzi si z poprzedniego dostawcy propozycji wyszukiwania. Taki cig znakw jest przekazywany dostawcy propozycji jako warto selection (klauzula where) metody query dostawcy
treci. Zazwyczaj w ten sposb jest reprezentowana klauzula where przechodzca do instrukcji
wyboru dowolnego dostawcy treci.

808 Android 3. Tworzenie aplikacji


Charakterystyczny dla dostawcw propozycji jest fakt, e w przypadku posiadania wartoci dla
argumentu searchSuggestSelection (jako protokou) Android przekazuje warto kwerendy
wyszukiwania (wprowadzan w polu QSB) jako pierwszy wpis w tablicy argumentw wyboru
bdcej czci metody danego dostawcy treci.
Kod przeprowadzajcy te czynnoci (mechanizmy definiujce wewntrzny sposb wykorzystywania cigw znakw przez dostawc) jest ukryty w poprzednim dostawcy propozycji
wyszukiwania, wic nie mamy moliwoci przedstawienia sposobu uywania tych argumentw w metodzie query dostawcy treci.
Szczegowo zajmiemy si tym nieco dalej, gdy dokadnie przedstawimy znaczenie wartoci ?.
W rzeczywistoci jest do mao prawdopodobne, e ta warto jest w ogle uywana do
zawania wynikw, poniewa adne pole nie jest kwalifikowane w nastpujcy sposb: jaki
identyfikator = ?. Natomiast istnieje moliwo, e jej wyrana obecno zachca system do przekazywania zawartoci pola QSB w postaci pierwszego argumentu do dostawcy. Do
tego umieszczony w zestawie SDK dostawca propozycji wyszukiwania polega wycznie na tym
protokole podczas odczytywania wartoci pola QSB w postaci dopasowanej tablicy, generowanej przez list argumentw wyboru z metody query().
Zajmijmy si teraz aktywnoci wywoania wyszukiwania, ktra posuy nam jako gwny
punkt wyjcia dla tej aplikacji. Aplikacja ta umoliwi nam przetestowanie wyszukiwania lokalnego.

Aktywno wywoania wyszukiwania


Aktywno ta pozwala nam na wywoanie wyszukiwania lokalnego w czasie, gdy jest obsugiwana na pierwszym planie. Listing 23.19 przedstawia kod rdowy takiej aktywnoci wywoania wyszukiwania, jej ukad graficzny oraz plik strings.xml, bdce czci projektu.
Listing 23.19. Dostawca SimpleSuggestionProvider gwna aktywno
public class SimpleMainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}

//nazwa pliku: /res/layout/main.xml


//Skopiujmy poniszy kod XML do pliku main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"

Rozdzia 23 Wyszukiwanie w Androidzie

809

android:layout_height="wrap_content"
android:text="@string/main_activity_text"
/>
</LinearLayout>

//nazwa pliku: /res/values/strings.xml


//Skopiujmy poniszy kod XML do pliku strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="main_activity_text">
Jest to prosta aktywno. Kliknij przycisk wyszukiwania,
aby przywoa proces przeszukiwania lokalnego.
\n\n
Dostawca propozycji bdzie rwnie uczestniczy w
procesie wyszukiwania globalnego. Kiedy uruchomimy
t aplikacj z poziomu wyszukiwania globalnego, nie
ujrzymy niniejszego widoku, lecz bezporednio
widok klasy SearchActivity.
</string>
<string name="search_activity_text">
Jeeli widzimy t aktywno, zostalimy tutaj skierowani
przez proces wyszukiwania globalnego lub lokalnego.
\n\n
Aktywno ta uruchamia rwnie funkcj type-to-search. Prezentuje ona
take koncepcje trybu singletop/new intencji.
</string>
<string name="app_name">Prosty dostawca propozycji</string>
<string name="search_label">Demonstracja wyszukiwania lokalnego</string>
<string name="search_hint">Podpowied wyszukiwania lokalnego</string>
</resources>

Jeli spojrzymy na definicj tej aktywnoci w pliku manifecie (listing 23.16), stwierdzimy, e
klasa SearchActivity nie zostaa jawnie zdefiniowana dla tej aktywnoci jako domylne wyszukiwanie lokalne. Wynika to z faktu, e t specyfikacj zastosowalimy na poziomie aplikacji,
a nie aktywnoci, wprowadzajc w pliku manifecie nastpujcy fragment kodu:
<meta-data android:name="android.app.default_searchable"
android:value=".SearchActivity" />

Zwrmy uwag, e powyszy fragment zosta umieszczony w pliku manifecie poza aktywnociami (listing 23.16). Dziki tej specyfikacji Android wie, e dla wszystkich aktywnoci
zawartych w tej aplikacji, w tym dla samej aktywnoci SearchActivity, domyln aktywnoci
wyszukiwania jest SearchActivity. Mona wykorzysta t wiedz do wywoania metody
onNewIntent(), klikajc przycisk wyszukiwania podczas sprawdzania wynikw w klasie Search
Activity. W przypadku zdefiniowania domylnego wyszukiwania jedynie na poziomie
prostej aktywnoci wywoania wyszukiwania, a nie na poziomie caej aplikacji, wspomniana
technika nie ma prawa bytu.

810 Android 3. Tworzenie aplikacji

Uytkowanie prostego dostawcy propozycji


Podczas przygotowywania tego programu musimy si upewni, e uprawnienie naszego dostawcy
propozycji jest takie samo w nastpujcych plikach:
AndroidManifest.xml,
searchable.xml,
SimpleSuggestionProvider.java.
Po uruchomieniu aplikacji ujrzymy jej ekran startowy, wygldajcy tak jak na rysunku 23.20
(jest to nasza gwna aktywno wywoania wyszukiwania).

Rysunek 23.20. Prosty dostawca propozycji gwna aktywno


(ustawiona dla wyszukiwania lokalnego)

Jeeli klikniemy przycisk wyszukiwania w trakcie dziaania aktywnoci na pierwszym planie,


zostanie wywoany proces wyszukiwania lokalnego, widoczny na rysunku 23.21.
Na rysunku 23.21 nie wida adnych propozycji, poniewa jak na razie niczego nie szukalimy.
Natomiast na podstawie etykiety i podpowiedzi, zdefiniowanych w pliku XML metadanych
wyszukiwania, dowiadujemy si, e mamy do czynienia z wyszukiwaniem lokalnym.
Przejdmy do nastpnego etapu i poszukajmy cigu znakw test1. Zostaniemy wywietlony
ekran Aktywno wyszukiwania, przedstawiony na rysunku 23.22.
Jak wynika z widocznego na listingu 23.17 kodu rdowego klasy SearchActivity, jej dziaanie
na ekranie nie jest widoczne, jednak niedostrzegalnie zachowuje ona kwerendy w bazie danych.
Jeli teraz wrcimy do gwnego ekranu (wciskajc przycisk cofania) i ponownie wywoamy
wyszukiwanie, ujrzymy ekran, widoczny na rysunku 23.23, na ktrym propozycje wyszukiwania zostay zapisane wczeniejszym wpisem kwerendy. Na tym rysunku widzimy rwnie podpowied SSSP znajdujc si pod propozycj. Moe si to wydawa w tym miejscu nieistotne,

Rozdzia 23 Wyszukiwanie w Androidzie

811

Rysunek 23.21. Prosty dostawca propozycji pole wyszukiwania lokalnego

Rysunek 23.22. Prosty dostawca propozycji aktywno wywietlajca wyniki wyszukiwania lokalnego

poniewa mamy do czynienia z wyszukiwaniem lokalnym i wyranie wida, e propozycja wywodzi si z naszej aplikacji. Jednak podpowied ta pozwoli nam odrni element test1 od innych
elementw o podobnej nazwie w procesie wyszukiwania globalnego.
Nadarza si teraz dobra sposobno wywoania metody onNewIntent(). Na ekranie aktywnoci
wyszukiwania (rysunek 23.22) moemy wpisa na przykad liter t, w wyniku czego zostanie
wywoane wyszukiwanie za pomoc trybu type-to-search, a wywoanie metody onNewIntent()
zostanie zapisane w pliku dziennika.

812 Android 3. Tworzenie aplikacji

Rysunek 23.23. Prosty dostawca propozycji odczytana propozycja lokalna

Zobaczmy, co naley zrobi, aby nasze propozycje zostay wywietlone w polu wyszukiwania
globalnego. Poniewa zamiecilimy obiekt includeInGlobalSearch w pliku searchable.xml,
propozycje te powinny by widoczne rwnie w przypadku wyszukiwania globalnego. Musimy
jednak przedtem zezwoli tej aplikacji na obsug globalnych propozycji wyszukiwania, co zostao zaprezentowane na rysunku 23.24.

Rysunek 23.24. Wczanie prostego dostawcy propozycji wyszukiwania

Na pocztku rozdziau podalimy instrukcj wywietlenia tego ekranu. Utworzony przez nas
prosty, niestandardowy dostawca propozycji znajduje si teraz na licie aplikacji pozwalajcych
na wyszukiwanie, pod nazw SSSP: Aktywno wyszukiwania. Cig znakw definiujcy t nazw
wywodzi si od nazwy aktywnoci SearchActivity (listing 23.16).

Rozdzia 23 Wyszukiwanie w Androidzie

813

Po zaznaczeniu utworzonego dostawcy umoliwimy wspprac wyszukiwania globalnego,


pokazanego na rysunku 23.25, z tym dostawc propozycji.

Rysunek 23.25. Propozycje globalne pochodzce od prostego dostawcy

Jeeli w polu wyszukiwania globalnego wprowadzimy na przykad liter t, zobaczymy propozycje pochodzce od omawianego w tym podrozdziale dostawcy. Gdy w trybie wyszukiwania globalnego poszukujemy okrelonego elementu, zauwaymy aktywno wyszukiwania lokalnego,
zilustrowan na rysunku 23.22.
Na tym zakoczymy analiz prostych dostawcw propozycji. Dowiedzielimy si, w jaki sposb
mona wykorzysta wbudowan klas SearchRecentSuggestionsProvider do zapamitywania zapyta swoistych dla danej aplikacji. Korzystajc z tej techniki, moemy nawet wprowadza
propozycje lokalne do kontekstu globalnego.
Jednak dziki temu prostemu wiczeniu nie pokazalimy sposobu pisania dostawcw propozycji od podstaw. Co waniejsze, nie przedstawilimy jeszcze jakichkolwiek informacji o sposobie przekazywania zestawu propozycji przez dostawc. Nie powiedzielimy, jakie kolumny
s dostpne w takim zestawie. Aby zrozumie te oraz inne kwestie, trzeba zaimplementowa od
podstaw wasnego dostawc propozycji.

Implementacja niestandardowego
dostawcy propozycji
Technologia wyszukiwania w Androidzie jest zbyt elastyczna, aby nie mona byo jej dostosowa
do wasnych potrzeb. Poniewa w poprzednim podrozdziale pokazalimy, jak korzysta z wbudowanego dostawcy propozycji, wiele funkcji w klasie SearchRecentSuggestionsProvider
pozostao ukrytych i nieomwionych. Przeanalizujemy te pominite szczegy, implementujc
niestandardowego dostawc treci, nazwanego SuggestUrlProvider.

814 Android 3. Tworzenie aplikacji


Rozpoczniemy od wyjanienia mechanizmu dziaania tego dostawcy. Nastpnie podamy list
plikw potrzebnych do jego implementacji. Lista ta powinna da Czytelnikowi oglne pojcie
na temat procesu budowania wasnego dostawcy propozycji.
Na koniec pokaemy zastosowanie utworzonej aplikacji. Zaczynajmy.

Implementacja niestandardowego dostawcy propozycji


Nasz niestandardowy dostawca propozycji bdzie nosi nazw SuggestUrlProvider. Gwnym
zadaniem tego dostawcy jest monitorowanie tekstu wpisywanego w polu QSB. Jeeli kwerend
wyszukiwania stanowi tekst wygldajcy jak na przykad great.m (sufiks .m jest skrtem od wyrazu znaczenie, z ang. meaning), dostawca zinterpretuje pierwsz cz zapytania jako sowo
i zaproponuje wywoanie internetowego adresu URL, umoliwiajcego wyszukanie znaczenia
tego sowa.
Dla kadego wyrazu s proponowane dwa adresy URL. Pierwszy adres pozwala uytkownikowi
na wyszukanie sowa za pomoc witryny http://www.thefreedictionary.com, drugi uruchamia
stron http://www.google.com. Wybranie jednej z propozycji powoduje uruchomienie przegldarki z otwart witryn. Jeeli uytkownik kliknie ikon wyszukiwania w polu QSB, aktywno
wyszukiwania utworzy wpis tej kwerendy w dzienniku, znajdujcym si w prostym ukadzie
graficznym tej aktywnoci. Stanie si to bardziej zrozumiae po zaprezentowaniu odpowiednich
zrzutw ekranu.
Przyjrzyjmy si licie plikw tworzcych nasz projekt. Za pomoc adresu URL zamieszczonego
na kocu rozdziau moemy rwnie pobra plik ZIP zawierajcy gotowy projekt.

Pliki wymagane do implementacji projektu


SuggestUrlProvider
Dwoma gwnymi plikami s SearchActivity.java i SuggestUrlProvider.java, jednak do poprawnego dziaania projektu wymagana bdzie implementacja kilku dodatkowych plikw. Poniej
przedstawiamy ich list wraz z krtkim opisem. W dalszej czci rozdziau zamiecilimy kod
rdowy kadego z nich.

SuggestUrlProvider.java w tym pliku zostaje zaimplementowany protok


niestandardowego dostawcy propozycji. W naszym przypadku dostawca ten tumaczy
cigi znakw kwerend na sowa i zwraca par propozycji za pomoc kursora propozycji
(listing 23.20).

SearchActivity.java aktywno ta jest odpowiedzialna za odbieranie kwerend


lub propozycji dostarczanych przez dostawc propozycji. Definicja aktywnoci
SearchActivity ma rwnie za zadanie powizanie dostawcy propozycji z t aktywnoci
(listing 23.23).

layout/layout_search_activity.xml ten plik ukadu graficznego jest uywany


opcjonalnie przez klas SearchActivity. W naszym przykadzie suy on do umieszczenia
wysanej kwerendy we wpisie dziennika (listing 23.24).

values/strings.xml zawiera definicje cigw znakw ukadu graficznego,


tytu i podpowied wyszukiwania lokalnego itp. (listing 23.25).

Rozdzia 23 Wyszukiwanie w Androidzie

815

xml/searchable.xml plik XML metadanych wyszukiwania czcy klas


SearchActivity, dostawc propozycji i pole QSB (listing 23.21).

AndroidManifest.xml plik manifest aplikacji, w ktrym s definiowane aktywno


wyszukiwania i dostawca propozycji. Tutaj rwnie okrelamy, e klasa SearchActivity
bdzie wywoywana wobec naszej aplikacji jako wyszukiwanie lokalne (listing 23.27).

Zaczniemy najpierw od analizy dostawcy SuggestUrlProvider.

Implementacja klasy SuggestUrlProvider


W przypadku naszego projektu niestandardowego dostawcy propozycji klasa SuggestUrl
Provider zapewnia implementacj protokou dostawcy propozycji. Badanie implementacji
tej klasy rozpoczniemy od analizy jej zada.

Zadania dostawcy propozycji


W swej istocie dostawca propozycji jest dostawc treci. Podobnie jak dostawca treci jest on
wywoywany przez proces wyszukiwania w Androidzie za pomoc identyfikatora URI okrelajcego dostawc oraz dodatkowego argumentu reprezentujcego kwerend.
W Androidzie do wywoywania dostawcy propozycji s wykorzystywane dwa rodzaje identyfikatorw URI. Pierwszy z nich nosi nazw identyfikatora URI wyszukiwania. Suy on do zbierania zestawu propozycji. Odpowiedzi musi by co najmniej jeden wiersz, zawierajcy zbir
znanych kolumn.
Drugi identyfikator URI nosi nazw identyfikatora URI propozycji. Pozwala on na aktualizowanie propozycji przechowywanej w pamici podrcznej. Odpowiedzi musi by pojedynczy wiersz skadajcy si z grupy znanych kolumn.
Dostawca propozycji musi rwnie okreli w metadanych wyszukiwania (plik searchable.xml)
sposb otrzymywania fragmentu kwerendy wyszukiwania, take w trakcie wpisywania danych.
Mona tego dokona za pomoc argumentu select metody query lub za pomoc ostatniego
segmentu samego identyfikatora URI (rwnie przekazywanego w postaci jednego z argumentw do metody kwerendy danego dostawcy).
Dostpna jest dua liczba kolumn dla dostawcy treci, z ktrych kada uruchamia okrelone
dziaanie wyszukiwania. Dostawca musi najpierw okreli zbir przekazywanych kolumn sterujcych. Poniej wymieniamy niektre spord kolumn tego typu:
Kolumny wczajce i wyczajce zapis w pamici podrcznej propozycji zwracanych
procesowi wyszukiwania w Androidzie.
Kolumny kontrolujce proces przepisywania tekstu propozycji do pola kwerendy.
Kolumny suce do bezporedniego wywoania dziaania, w przeciwiestwie do
pokazywania zestawu wynikw po klikniciu propozycji przez uytkownika.

Kod rdowy dostawcy SuggestUrlProvider


Listing 23.20 przedstawia kod rdowy klasy SuggestUrlProvider. W dalszej czci rozdziau,
podczas omawiania kadego z zada dostawcy, szczegowo przeanalizujemy poszczeglne
fragmenty kodu.

816 Android 3. Tworzenie aplikacji


Listing 23.20. Kod rdowy niestandardowego dostawcy propozycji
public class SuggestUrlProvider extends ContentProvider
{
private static final String tag = "SuggestUrlProvider";
public static String AUTHORITY =
"com.androidbook.search.custom.suggesturlprovider";
private static final int SEARCH_SUGGEST = 0;
private static final int SHORTCUT_REFRESH = 1;
private static final UriMatcher sURIMatcher = buildUriMatcher();
private static final String[] COLUMNS = {
"_id", // musi zawrze t kolumn
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID
};
private static UriMatcher buildUriMatcher()
{

UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);


matcher.addURI(AUTHORITY,
SearchManager.SUGGEST_URI_PATH_QUERY,
SEARCH_SUGGEST);
matcher.addURI(AUTHORITY,
SearchManager.SUGGEST_URI_PATH_QUERY +
"/*",
SEARCH_SUGGEST);
matcher.addURI(AUTHORITY,
SearchManager.SUGGEST_URI_PATH_SHORTCUT,
SHORTCUT_REFRESH);
matcher.addURI(AUTHORITY,
SearchManager.SUGGEST_URI_PATH_SHORTCUT +
"/*",
SHORTCUT_REFRESH);
return matcher;
@Override
public boolean onCreate() {

//nie robimy tu nic szczeglnego


Log.d(tag,"wywolana metoda onCreate");
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
Log.d(tag,"kwerenda wywolana za pomoca identyfikatora uri:" + uri);
Log.d(tag,"wybor:" + selection);
String query = selectionArgs[0];
Log.d(tag,"kwerenda:" + query);
switch (sURIMatcher.match(uri)) {

Rozdzia 23 Wyszukiwanie w Androidzie

817

case SEARCH_SUGGEST:
Log.d(tag,"wywolana propozycja wyszukiwania");
return getSuggestions(query);
case SHORTCUT_REFRESH:
Log.d(tag,"wywolane odswiezenie skrotu");
return null;
default:
throw new IllegalArgumentException("Nieznany adres URL " + uri);
}
}
private Cursor getSuggestions(String query)
{
if (query == null) return null;
String word = getWord(query);
if (word == null)
return null;
Log.d(tag,"kwerenda przekracza dlugosc 3 liter");
MatrixCursor cursor = new MatrixCursor(COLUMNS);
cursor.addRow(createRow1(word));
cursor.addRow(createRow2(word));
return cursor;
}
private Object[] createRow1(String query)
{
return columnValuesOfQuery(query,
"android.intent.action.VIEW",
"http://www.thefreedictionary.com/" + query,
"Wyszukaj na stronie freedictionary.com wyraz",
query);
}
private Object[] createRow2(String query)
{
return columnValuesOfQuery(query,
"android.intent.action.VIEW",
"http://www.google.com/search?hl=en&source=hp&q=define%3A/"
+ query,
"wyszukaj na stronie google.com wyraz",
query);
}
private Object[] columnValuesOfQuery(String query,
String intentAction,
String url,
String text1,
String text2)
{
return new String[] {
query, // _id
text1,
text2,

// text1
// text2

url,

// intent_data (umieszczony po klikniciu elementu)


intentAction, //dziaanie

818 Android 3. Tworzenie aplikacji


SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT
};
}
private Cursor refreshShortcut(String shortcutId, String[] projection) {
return null;
}
public String getType(Uri uri) {
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
case SHORTCUT_REFRESH:
return SearchManager.SHORTCUT_MIME_TYPE;
default:
throw new IllegalArgumentException("Nieznany adres URL " + uri);
}
}
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException();
}
private String getWord(String query)
{
int dotIndex = query.indexOf('.');
if (dotIndex < 0)
return null;
return query.substring(0,dotIndex);
}
}

Identyfikatory URI dostawcy propozycji


Po zaprezentowaniu penego kodu rdowego niestandardowego dostawcy propozycji warto
si przyjrze, w jaki sposb fragmenty tego kodu speniaj zadania zwizane z identyfikatorami URI.
Najpierw zobaczmy, jaki jest format identyfikatorw URI wykorzystywanych do wywoywania
dostawcy propozycji. Jeeli nasz dostawca propozycji posiada uprawnienie:
com.androidbook.search.custom.suggesturlprovider

to Android bdzie wysya dwa moliwe identyfikatory URI. Pierwszy typ identyfikator URI
wyszukiwania wyglda nastpujco:
content://com.androidbook.search.suggesturlprovider/search_suggest_query

Rozdzia 23 Wyszukiwanie w Androidzie

819

albo
content://com.androidbook.search.suggesturlprovider/search_suggest_query/<kwerenda>

Identyfikator tego typu jest nadawany na pocztku procesu wpisywania tekstu w polu QSB.
W jednej z odmian tego identyfikatora kwerenda jest przekazywana na jego kocu jako dodatkowy element (segment cieki). Moliwo doczania kwerendy jako segmentu cieki jest
definiowana w pliku metadanych wyszukiwania searchable.xml. Powrcimy do tego tematu
podczas szczegowego omwienia metadanych wyszukiwania.
Drugi rodzaj identyfikatora URI jest przeznaczony dla dostawcy propozycji i jest zwizany ze
skrtami wyszukiwania. Skrty wyszukiwania w Androidzie s propozycjami (rysunek 23.3),
ktre system przechowuje w pamici podrcznej, zamiast wywoywa dostawc propozycji w celu
uzyskania nowej treci. Tematyk skrtw wyszukiwania poruszymy podczas analizowania
kolumn propozycji. Na razie wystarczy wiedzie, e drugi rodzaj identyfikatora propozycji przybiera nastpujce ksztaty:
content://com.androidbook.search.suggesturlprovider/search_suggest_shortcut

lub
content://com.androidbook.search.suggesturlprovider/search_suggest_shortcut/
<identyfikator-skrtu>

Identyfikator tego typu jest nadawany przez system podczas prby okrelenia wanoci skrtw
przechowywanych w pamici podrcznej. Taki identyfikator URI nosi nazw identyfikatora URI
skrtu. Jeeli dostawca przekae pojedynczy wiersz, biecy skrt zostanie zastpiony nowym.
Jeeli zostanie przesana warto null, bieca propozycja przestanie by uznawana za wan.
Klasa SearchManager definiuje w Androidzie dwie stae, pozwalajce na odrnianie tych
segmentw identyfikatorw URI (search_suggest_search i search_suggest_shortcut).
S to odpowiednio:
SearchManager.SUGGEST_URI_PATH_QUERY
SearchManager.SUGGEST_URI_PATH_SHORTCUT

Zadaniem dostawcy propozycji jest rozpoznawanie tych identyfikatorw, przychodzcych


w metodzie query(). Na listingu 23.20 zosta zaprezentowany sposb przeprowadzania tej
czynnoci za pomoc klasy UriMatcher (zastosowanie klasy UriMatcher zostao szczegowo
omwione w rozdziale 5.).

Implementacja metody getType() i okrelenie typw MIME


Poniewa dostawca propozycji jest oglnie dostawc treci, ma zaimplementowa kontrakt definiujcy implementacj metody getType().
W naszym przypadku implementacja metody getType() zostaa ukazana na listingu 23.20.
Poniej przypominamy odpowiedni fragment kodu:
public String getType(Uri uri) {
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
case SHORTCUT_REFRESH:
return SearchManager.SHORTCUT_MIME_TYPE;
default:
throw

820 Android 3. Tworzenie aplikacji


new IllegalArgumentException("Nieznany adres URL" + uri);
}
}

W strukturze wyszukiwania poprzez klas SearchManager wprowadzono par staych, wspomagajcych przetwarzanie takich typw MIME. Tymi typami MIME s:
SearchManager.SUGGEST_MIME_TYPE
SearchManager.SHORTCUT_MIME_TYPE

S one tumaczone na wyraenia:


vnd.android.cursor.dir/vnd.android.search.suggest
vnd.android.cursor.item/vnd.android.search.suggest

Przekazywanie kwerendy do dostawcy propozycji


argument Selection
Ostatnim etapem zastosowania ktrego z identyfikatorw URI do wywoania dostawcy jest
wywoanie metody query() dostawcy propozycji w celu uzyskania kursora propozycji. Jeeli
przyjrzymy si implementacji metody query() z listingu 23.20, to zauwaymy, e do sformuowania i przekazania kursora uywamy argumentw selection oraz selectionArgs. Dla
przypomnienia poniej wstawilimy omawiany fragment kodu:
public Cursor query(Uri uri, String[] projection,
String selection,
String[] selectionArgs, String sortOrder)
{
Log.d(tag,"kwerenda wywolana za pomoca identyfikatora uri:" + uri);
Log.d(tag,"wybor:" + selection);
String query = selectionArgs[0];
Log.d(tag,"kwerenda:" + query);
switch (sURIMatcher.match(uri)) {

case SEARCH_SUGGEST:
Log.d(tag,"wywolana propozycja wyszukiwania");
return getSuggestions(query);

case SHORTCUT_REFRESH:
Log.d(tag,"wywolane odswiezenie skrotu");
return null;
default:
throw
new IllegalArgumentException("Nieznany adres URL " + uri);
}

Aby zrozumie, jakie dane s przekazywane za pomoc tych dwch argumentw (selection
oraz selectionArgs), musimy spojrze na plik metadanych wyszukiwania searchable.xml.
Na listingu 23.21 zosta przedstawiony kod tego pliku.
Listing 23.21. Metadane wyszukiwania dla niestandardowego dostawcy propozycji
//xml/searchable.xml
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
android:hint="@string/search_hint"

Rozdzia 23 Wyszukiwanie w Androidzie

821

android:searchMode="showSearchLabelAsBadge"
android:searchSettingsDescription="proponuje adresy url"
android:includeInGlobalSearch="true"
android:queryAfterZeroResults="true"
android:searchSuggestAuthority="com.androidbook.search.custom.suggesturlprovider"
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSuggestSelection=" ? "
/>

Zwrmy uwag na warto cigu znakw searchSuggestAuthority. Powinna ona


odpowiada waciwej definicji adresu URL dostawcy treci, umieszczonej w pliku
manifecie.

Zwrmy uwag na atrybut searchSuggestSelection. Jest on bezporednio zwizany z argumentem selection metody query() znajdujcej si w naszym dostawcy treci. Jak pamitamy z rozdziau 4., argument ten suy przewanie do przekazywania klauzuli where
wraz z wymienialnymi symbolami ?.
Nastpnie tablica wymienialnych wartoci jest przekazywana do argumentu tablicy selection
Args. Tak si rzeczywicie dzieje. W przypadku okrelenia atrybutu searchSuggestSelection
istnieje zaoenie, e nie chcemy otrzymywa tekstu wyszukiwania poprzez identyfikator URI,
lecz za pomoc argumentu selection metody query(). W takim wypadku proces wyszukiwania w Androidzie bdzie wysya symbol ? (zwrmy uwag na spacj poprzedzajc znak ?)
jako warto argumentu selection i przekazywa tekst kwerendy w postaci pierwszego elementu tablicy argumentw selection.
Jeli nie zdefiniujemy argumentu searchSuggestSelection, tekst wyszukiwania zostanie
przekazany w formie ostatniego segmentu identyfikatora URI. Moemy wybra dowolny sposb.
W naszym przykadzie wykorzystalimy argument selection, a nie identyfikator URI.

Badanie metadanych wyszukiwania


pod ktem niestandardowego dostawcy propozycji
Skoro ju poruszylimy temat metadanych wyszukiwania, to sprawdmy, jakie s dostpne pozostae atrybuty. Omwimy atrybuty najczciej stosowane wobec dostawcy propozycji oraz najbliej z nim zwizane. Pen list atrybutw interfejsu API SearchManager moemy przejrze pod
adresem: http://developer.android.com/guide/topics/search/searchable-config.html.
Atrybut searchSuggestIntentAction (listing 23.21) uywany jest do przekazywania lub
definiowania dziaania danej intencji podczas wywoania aktywnoci SearchActivity za pomoc tej intencji. Dziki temu klasa SearchActivity nie jest ograniczona wycznie do domylnego wyszukiwania. Poniej prezentujemy przykad wykorzystania dziaania intencji wewntrz
metody onCreate(), bdcej czci odpowiadajc aktywnoci wyszukiwania:
// Ciao metody onCreate
// Wprowadzamy tutaj i przetwarzamy kwerend wyszukiwania
final Intent queryIntent = getIntent();

//dziaanie kwerendy
final String queryAction = queryIntent.getAction();

822 Android 3. Tworzenie aplikacji


if (Intent.ACTION_SEARCH.equals(queryAction))
{
this.doSearchQuery(queryIntent);
}
else if (Intent.ACTION_VIEW.equals(queryAction))
{
this.doView(queryIntent);
}
else {
Log.d(tag,"NIE tworzy kwerendy z poziomu wyszukiwania");
}

Zobaczymy ten kod umieszczony w kontekcie (listing 23.23), gdzie klasa SearchActivity
oczekuje dziaania VIEW lub SEARCH poprzez sprawdzanie wartoci dziaania danej intencji.
Kolejny atrybut, ktrego nie wykorzystalimy, lecz ktry jest dostpny dla dostawcw propozycji, nosi nazw searchSuggestPath. Po okreleniu tej wartoci o typie string zostanie ona
przyczona do identyfikatora URI (wywoujcego dostawc propozycji) tu po segmencie
SUGGEST_URI_PATH_QUERY. Umoliwia to pojedynczemu, niestandardowemu dostawcy propozycji
przetworzenie dwch rnych aktywnoci wyszukiwania. Kada aktywno SearchActivity
bdzie posiadaa inny sufiks identyfikatora URI. Dostawca propozycji moe korzysta z tego sufiksu cieki, aby przekazywa rne zestawy wynikw do docelowej aktywnoci wyszukiwania.
Podobnie jak w przypadku dziaania Intent, rwnie i teraz moemy okreli dane intencji
za pomoc atrybutu searchSuggestIntentData. Jest to identyfikator URI danych, ktry
podczas wywoania moe by przekazany jako cz intencji, wraz z dziaaniem do aktywnoci wyszukiwania.
Atrybut searchSuggestThreshold definiuje liczb znakw, jak naley wpisa w polu QSB,
aby wywoa dostawc propozycji. Domyln wartoci progow jest 0.
Kolejny atrybut, queryAfterZeroResults (przyjmujcy wartoci true lub false), wskazuje,
czy dla nastpnego zestawu znakw ma zosta wywoany dostawca, w przypadku gdy dla biecego
zestawu znakw otrzymano zerowy zestaw wynikw. W przypadku naszego adresu URL istotne
jest, aby wczy t flag, dziki czemu cay tekst kwerendy staje si za kadym razem widoczny.
Po zapoznaniu si z identyfikatorami URI, argumentami selection i metadanymi wyszukiwania zajmijmy si najistotniejszym aspektem dostawcy propozycji kursorem propozycji.

Kolumny kursora propozycji


Kursor propozycji jest przede wszystkim kursorem. Nie rni si niczym od kursorw bazodanowych, obszernie omwionych w rozdziale 4. Kursor propozycji peni rol kontraktu pomidzy procesem wyszukiwania w Androidzie a dostawc propozycji. Oznacza to, e nazwy i typy
kolumn przekazywane przez kursor s niezmienne i znane obydwu stronom.
W celu uzyskania elastycznoci procesu wyszukiwania Android oferuje olbrzymi liczb kolumn, w wikszoci opcjonalnych. Dostawca propozycji nie musi przekazywa wszystkich kolumn; moe zignorowa kolumny, ktre nie s z nim cile powizane. W tym punkcie przyjrzymy si przeznaczeniu czci kolumn (opis pozostaych kolumn znajdziemy we wspomnianym
ju kilkakrotnie omwieniu interfejsu API SearchManager).
Najpierw zajmiemy si kolumnami, ktre dostawca propozycji moe przekazywa, omwimy
ich przeznaczenie oraz wpyw na wyszukiwanie.

Rozdzia 23 Wyszukiwanie w Androidzie

823

Tak jak wszystkie kursory, rwnie kursor propozycji musi zawiera kolumn _id. Jest to kolumna
obowizkowa. Nazwy pozostaych kolumn rozpoczynaj si od przedrostka SUGGEST_COLUMN_.
Stae te s zdefiniowane jako cz odniesienia do interfejsu API SearchManager. Poniej zostan omwione najczciej uywane kolumny. Pen ich list mona znale w umieszczonych
na kocu rozdziau zasobach dotyczcych tego interfejsu API.
text_1 jest to pierwszy wiersz tekstu propozycji (rysunek 23.3).
text_2 jest to drugi wiersz tekstu propozycji (rysunek 23.3).
icon_1 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
zazwyczaj identyfikator zasobu.
intent_action jest to argument przekazywany aktywnoci SearchActivity
podczas jej wywoywania w postaci dziaania intencji. W przypadku obecnoci tej
kolumny w metadanych wyszukiwania bdzie ona przesaniaa odpowiednie dziaanie
intencji (listing 23.21).
intent_data s to informacje przekazywane aktywnoci SearchActivity podczas
jej wywoywania w postaci danych intencji. W przypadku obecnoci tej kolumny
w metadanych wyszukiwania bdzie ona przesaniaa odpowiednie dziaanie intencji
(listing 23.21). Jest to identyfikator URI danych.
intent_data_id zostaje ona dodana do identyfikatora URI danych. Jest szczeglnie
przydatna, gdy chcemy jednorazowo wspomnie o gwnej partii danych w metadanych,
a nastpnie zmienia t parti dla kadej propozycji. Dziki tej kolumnie mona
nieco skuteczniej przeprowadzi tak czynno.
query cig znakw kwerendy, wysyany do aktywnoci wyszukiwania.
shortcut_id jak zostao wczeniej wspomniane, wyszukiwanie w Androidzie
przechowuje w pamici podrcznej propozycje dostarczane przez dostawc. Takie
przechowywane propozycje nosz nazw skrtw. Jeeli ta kolumna jest nieobecna,
Android bdzie przechowywa propozycj i nigdy nie zada jej aktualizacji. Jeeli
zostanie umieszczona warto SUGGEST_NEVER_MAKE_SHORTCUT, Android przestanie
przechowywa propozycje w pamici podrcznej. Jeeli wstawimy tu dowoln inn
warto, identyfikator ten zostanie przekazany jako ostatni segment identyfikatora
URI skrtu (wicej informacji znajduje si w podpunkcie Identyfikatory URI
dostawcy propozycji).
spinner_while_refreshing ta warto logiczna pozwala okreli, czy podczas
procesu aktualizowania skrtw ma by uywana kontrolka Spinner.
Istnieje rwnie zmienny zestaw dodatkowych kolumn, umoliwiajcych reagowanie na wcinicie przyciskw dziaania. Zajmiemy si t kwesti podczas omawiania przyciskw dziaania.
Zobaczmy, w jaki sposb nasz niestandardowy dostawca propozycji przekazuje te kolumny.

Zapenianie i przekazywanie listy kolumn


Niestandardowy dostawca propozycji nie musi przekazywa wszystkich kolumn wymienionych
we wczeniejszym podpunkcie. Nasz dostawca bdzie przekazywa jedynie podzbir kolumn
okrelony na podstawie zada omwionych w podrozdziale Implementacja niestandardowego
dostawcy propozycji.

824 Android 3. Tworzenie aplikacji


Po analizie listingu 23.20 moemy stwierdzi, e wyjciowa lista kolumn jest nastpujca (zostaa ona wstawiona do listingu 23.22):
Listing 23.22. Definiowanie kolumn kursora propozycji
private static final String[] COLUMNS = {
"_id", // musi zawiera t kolumn
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID
};

Kolumny te zostay dobrane w taki sposb, aby speniay ponisze wymagania.


Kiedy uytkownik wpisuje w polu QSB sowo z podpowiedzi, tak jak great.m, nasz dostawca
propozycji nie odpowie, dopki w tekcie wyszukiwania znajduje si kropka. Po rozpoznaniu
wyrazu dostawca propozycji wyizoluje go z caego wyraenia (w naszym przypadku jest to wyraz
great), a nastpnie przekae dwie propozycje.
Pierwsza propozycja suy do wywoania witryny thefreewebdictionary.com wraz z danym sowem,
natomiast druga propozycja spowoduje przeszukanie bazy danych Google za pomoc wyraenia
define:great.
W tym celu dostawca wczytuje kolumn intent_action jako klas intent.action.view oraz
dane intencji zawierajce peny identyfikator URI. Android powinien uruchomi przegldark
po rozpoznaniu identyfikatora URI danych, rozpoczynajcego si od segmentu http://.
Wypenimy kolumn text1 wartociami search some-website with:, a kolumn text2
waciwym sowem (przypominamy, e w naszym przypadku jest to wyraz great). Aby uproci
zadanie, identyfikatorowi skrtu przypiszemy warto SUGGEST_NEVER_MAKE_SHORTCUT.
W ten sposb wyczymy przechowywanie propozycji w pamici podrcznej i uniemoliwimy
usunicie skrtu identyfikatora URI propozycji.
Na tym zakoczymy analiz kodu rdowego klasy niestandardowego dostawcy propozycji.
Czytelnicy uzyskali wiedz na temat identyfikatorw URI, kursorw propozycji oraz metadanych wyszukiwania powizanych z okrelonym dostawc. Wiedz ju take, w jaki sposb mona
zapenia kolumny propozycji.
Zastanwmy si teraz nad sposobem zaimplementowania aktywnoci wyszukiwania do naszego niestandardowego dostawcy propozycji.

Implementacja aktywnoci wyszukiwania


dla niestandardowego dostawcy propozycji
Podczas omawiania tematu implementacji prostego dostawcy propozycji zajlimy si pobienie tematem zada aktywnoci wyszukiwania. Przeanalizujmy teraz pominite aspekty
tego zagadnienia.
Proces przeszukiwania w Androidzie wywouje aktywno wyszukiwania w celu przetworzenia
dziaa wyszukiwania, uruchomionych na jeden z dwch sposobw. Jednym ze sposobw

Rozdzia 23 Wyszukiwanie w Androidzie

825

uruchomienia dziaania wyszukiwania jest kliknicie ikony wyszukiwania, bdcej czci pola
QSB; drugi natomiast polega na bezporednim klikniciu propozycji.
Aktywno wyszukiwania musi ustali powd, dla ktrego zostaa wywoana. Informacja ta jest
umieszczona w intencji dziaania. Dlatego intencja ta musi zosta przeanalizowana przez aktywno wyszukiwania. W wielu przypadkach dziaaniem takim jest ACTION_SEARCH. Jednak dostawca propozycji posiada moliwo przesonicia tego dziaania poprzez jawne okrelenie
innego dziaania, korzystajc z metadanych wyszukiwania lub kolumny kursora propozycji.
W naszym przykadzie stosujemy dziaanie VIEW.
W trakcie omawiania prostego dostawcy propozycji wspomnielimy, e istnieje rwnie moliwo skonfigurowania trybu uruchamiania singleTop wobec aktywnoci wyszukiwania. W takim przypadku aktywno musi dodatkowo odpowiedzie na metod onNewIntent(), a take
na metod onCreate(). Omwimy przypadki odpowiadania na obydwie metody i pokaemy
podobiestwa wystpujce pomidzy nimi.
Zastosujemy zarwno metod onNewIntent(), jak i onCreate() w celu przeanalizowania
dziaa ACTION_SEARCH i ACTION_VIEW. W trakcie korzystania z dziaania wyszukiwania spowodujemy po prostu przekazanie tekstu kwerendy uytkownikowi. W przypadku dziaania
widoku przeniesiemy kontrolk do wyszukiwarki i zakoczymy biec aktywno, dziki
czemu uytkownik bdzie mia wraenie wywoania przegldarki bezporednio po klikniciu
propozycji.
Klasa SearchActivity nie musi by aktywnoci uruchamian z poziomu gwnego
menu aplikacji. Upewnijmy si, e nie skonfigurujemy niewiadomie filtrw intencji
w taki sposb jak w przypadku innych aktywnoci, wywoywanych z poziomu gwnego
menu aplikacji.

Skoro ju wiemy, czego oczekiwa, spjrzmy na kod rdowy pliku SearchActivity.java.

Klasa SearchActivity niestandardowego dostawcy propozycji


Po zapoznaniu si z zadaniami aktywnoci wyszukiwania, szczeglnie za z tymi przedstawianymi w naszym przykadzie, mona zapozna si z jej kodem rdowym (listing 23.23).
Listing 23.23. Klasa SearchActivity
//plik: SearchActivity.java
public class SearchActivity extends Activity
{
private final static String tag ="SearchActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(tag,"Jestem tworzona");
setContentView(R.layout.layout_test_search_activity);

// uzyskuje i przetwarza tu kwerend wyszukiwania


final Intent queryIntent = getIntent();

//dziaanie kwerendy
final String queryAction = queryIntent.getAction();

826 Android 3. Tworzenie aplikacji


Log.d(tag,"Utworz dzialanie intencji:"+queryAction);
final String queryString =
queryIntent.getStringExtra(SearchManager.QUERY);
Log.d(tag,"Utworz kwerende intencji:"+queryString);
if (Intent.ACTION_SEARCH.equals(queryAction))
{
this.doSearchQuery(queryIntent);
}
else if (Intent.ACTION_VIEW.equals(queryAction))
{
this.doView(queryIntent);
}
else {
Log.d(tag,"Utworz intencje NIE z poziomu wyszukiwania");
}
return;
}
@Override
public void onNewIntent(final Intent newIntent)
{
super.onNewIntent(newIntent);
Log.d(tag,"wywoluje mnie nowa intencja");

// uzyskuje tu i przetwarza kwerend wyszukiwania


final Intent queryIntent = newIntent;

//dziaanie kwerendy
final String queryAction = queryIntent.getAction();
Log.d(tag,"Nowe dzialanie intencji:"+queryAction);
final String queryString =
queryIntent.getStringExtra(SearchManager.QUERY);
Log.d(tag,"Nowa kwerenda intencji:"+queryString);
if (Intent.ACTION_SEARCH.equals(queryAction))
{
this.doSearchQuery(queryIntent);
}
else if (Intent.ACTION_VIEW.equals(queryAction))
{
this.doView(queryIntent);
}
else {
Log.d(tag,"Nowa intencja utworzona NIE z poziomu wyszukiwania");
}
return;
}
private void doSearchQuery(final Intent queryIntent)
{
final String queryString =
queryIntent.getStringExtra(SearchManager.QUERY);
appendText("Szukasz obiektu:" + queryString);
}

Rozdzia 23 Wyszukiwanie w Androidzie

827

private void appendText(String msg)


{
TextView tv = (TextView)this.findViewById(R.id.text1);
tv.setText(tv.getText() + "\n" + msg);
}
private void doView(final Intent queryIntent)
{
Uri uri = queryIntent.getData();
String action = queryIntent.getAction();
Intent i = new Intent(action);
i.setData(uri);
startActivity(i);
this.finish();
}
}

Rozpoczniemy analiz kodu rdowego od okrelenia sposobu wywoania naszej aktywnoci


wyszukiwania.

Szczegy wywoania klasy SearchActivity


Tak jak wszystkie aktywnoci, rwnie aktywno wyszukiwania musi zosta wywoana za
pomoc intencji. Jednak zaoenie, e za wywoanie aktywnoci jest zawsze odpowiedzialna
intencja action, jest niewaciwe. Okazuje si, e aktywno wyszukiwania jest jawnie wywoywana poprzez specyfikacj jej skadowej nazwy.
Zastanwmy si, dlaczego jest to takie istotne. Jak wiemy, w naszym dostawcy propozycji
jawnie definiujemy intencj action w krotce propozycji. Jeeli dziaaniem intencji jest VIEW,
a jej danymi adres URL HTTP, to niewiadomy programista mgby uzna, e w odpowiedzi zostanie uruchomiona przegldarka, a nie aktywno wyszukiwania. Takie zjawisko
byoby z pewnoci bardzo podane. Jednak poniewa intencja posiada dostp do nazwy aktywnoci wyszukiwania oraz dziaania i danych intencji, ostatecznie to nazwa aktywnoci uzyskuje pierwszestwo.
Nie jestemy pewni, dlaczego zostao wprowadzone takie ograniczenie oraz w jaki sposb mona je omin. Faktem jest jednak, e niezalenie od dziaania intencji definiowanej przez
dostawc propozycji to wanie aktywno wyszukiwania zostanie wywoana. W naszym przykadzie uruchomimy po prostu przegldark z poziomu aktywnoci wyszukiwania i zamkniemy t aktywno.
Zademonstrujemy to rozwizanie na podstawie intencji uruchamianej przez system po klikniciu propozycji w celu wywoania naszej aktywnoci wyszukiwania:
launching Intent {
act=android.intent.action.VIEW
dat=http://www.google.com
flg=0x10000000
cmp=com.androidbook.search.custom/.SearchActivity (has extras)
}

Zwrmy uwag na specyfikacj skadnika intencji. Wskazuje ona bezporednio aktywno


wyszukiwania. Zatem bez wzgldu na okrelone przez nas dziaanie intencji Android bdzie
zawsze wywoywa aktywno wyszukiwania. W wyniku tego wywoanie przegldarki jest
zadaniem tej aktywnoci.

828 Android 3. Tworzenie aplikacji


Przyjrzyjmy si teraz, co si dzieje z tymi intencjami w aktywnoci wyszukiwania.

Odpowied na dziaania ACTION_SEARCH i ACTION_VIEW


Wiemy, e do wywoania aktywnoci wyszukiwania proces wyszukiwania w Androidzie jawnie
wykorzystuje jej nazw. Jednak intencja wywoujca przechowuje rwnie okrelone dziaanie.
Kiedy pole QSB wywouje t aktywno po klikniciu przez uytkownika ikony wyszukiwania,
mamy do czynienia z dziaaniem ACTION_SEARCH.
Dziaanie moe by inne w przypadku jego wywoania przez propozycj wyszukiwania. Zaley
to od skonfigurowania propozycji przez dostawc. W naszym przypadku dostawca propozycji
konfiguruje dziaanie ACTION_VIEW.
Z powodu obecnoci rnych dziaa aktywno wyszukiwania musi je rozpoznawa. Przedstawiamy poniej kod umoliwiajcy okrelenie, czy ma zosta wywoana metoda wyszukiwania kwerendy, czy metoda widoku (segment kodu zosta zaczerpnity z listingu 23.23):
if (Intent.ACTION_SEARCH.equals(queryAction))
{
this.doSearchQuery(queryIntent);
}
else if (Intent.ACTION_VIEW.equals(queryAction))
{
this.doView(queryIntent);
}

Widzimy, e dla dziaania widoku wywoujemy metod doView(), a w przypadku dziaania wyszukiwania doSearchQuery().
Za pomoc funkcji doView() odczytujemy dziaanie oraz identyfikator URI danych i zapeniamy nimi now intencj, a nastpnie wywoujemy aktywno. W ten sposb zostanie przywoana przegldarka. Zakoczymy aktywno w taki sposb, aby przycisk cofania spowodowa powrt
do wywoujcego j procesu wyszukiwania.
W metodzie doSearchQuery() wywietlamy jedynie tekst kwerendy wyszukiwania w widoku.
Przyjrzyjmy si ukadowi graficznemu wykorzystanemu do obsugi funkcji doSearchQuery().

Ukad graficzny aktywnoci wyszukiwania


Listing 23.24 przedstawia prosty ukad graficzny, wykorzystywany przez aktywno wyszukiwania po uruchomieniu metody doSearchQuery(). Jedyny istotny fragment zosta zaznaczony pogrubion czcionk.
Listing 23.24. Ukad graficzny klasy SearchActivity
<?xml version="1.0" encoding="utf-8"?>
<!-- plik: layout/layout_search_activity.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"

Rozdzia 23 Wyszukiwanie w Androidzie

829

android:layout_height="wrap_content"
android:text="@string/search_activity_main_text"
/>
</LinearLayout>

Nadszed odpowiedni moment na zaprezentowanie zawartoci pliku strings.xml, odpowiedzialnego za wywietlanie niektrych cigw znakw w naszej aplikacji.

Plik strings.xml
Przedstawiony na listingu 23.25 plik strings.xml definiuje cigi znakw tekstowych ukadu graficznego oraz takie elementy, jak nazwa aplikacji, niektre cigi znakw konfiguracji wyszukiwania itp.
Listing 23.25. Plik strings.xml
<?xml version="1.0" encoding="utf-8"?>

<!-- plik: values/strings.xml -->


<resources>
<string name="search_activity_main_text">
Jest to aktywno wyszukiwania.
\n\n
Ten widok zostanie wywoany, jeeli zostanie
uyte dziaanie ACTION_SEARCH, a nie ACTION_VIEW.
\n\n
Dziaanie ACTION_SEARCH zostaje uruchomione po klikniciu ikony wyszukiwania.
\n\n
Dziaanie ACTION_VIEW zostaje uruchomione po klikniciu propozycji.
</string>
<string name="app_name">Niestandardowa aplikacja propozycji</string>
<string name="search_label">Demonstracja niestandardowej propozycji</string>
<string name="search_hint">Demonstracja podpowiedzi niestandardowej
propozycji</string>
</resources>

Odpowied na metody onCreate() i onNewIntent()


Jeeli przyjrzymy si kodowi z listingu 23.23, zauwaymy, e fragmenty w metodach onCreate()
i onNewIntent() s niemal identyczne. Jest to dosy powszechny wzorzec.
W zalenoci od trybu uruchamiania aktywnoci wyszukiwania po jej wywoaniu przywoywana
jest metoda onCreate() lub onNewIntent().
W umieszczonej pod koniec rozdziau sekcji Odnoniki znajduje si cze do uytecznych
materiaw na temat trybw uruchamiania aktywnoci wyszukiwania.

Uwagi na temat zakoczenia aktywnoci wyszukiwania


Wspomnielimy wczeniej o sposobie odpowiedzi na metod
pokazalimy kod tej funkcji (jest to wycig z listingu 23.23).

doView().

Na listingu 23.26

830 Android 3. Tworzenie aplikacji


Listing 23.26. Zakoczenie aktywnoci wyszukiwania
private void doView(final Intent queryIntent)
{
Uri uri = queryIntent.getData();
String action = queryIntent.getAction();
Intent i = new Intent(action);
i.setData(uri);
startActivity(i);
this.finish();
}

Celem tej funkcji jest wywoanie przegldarki. Gdybymy na kocu nie wywoali funkcji
finish(), uytkownik po klikniciu przycisku cofania zobaczyby widok aktywnoci wyszukiwania, a nie z powrotem ekran wyszukiwania, jak naleaoby si spodziewa.
W idealnym przypadku, aby zapewni uytkownikowi jak najlepszy komfort pracy, kontrolka nie powinna nigdy przechodzi przez aktywno wyszukiwania. Zakoczenie tej aktywnoci rozwizuje problem. Kod z listingu 23.26 daje nam rwnie okazj zbadania, w jaki
sposb przenosimy dziaanie i dane intencji z oryginalnej intencji (ustanowionej przez dostawc propozycji), a nastpnie przekazujemy je do nowej intencji przegldarki.
Wprowadzilimy w tym podrozdziale mnstwo nowych wiadomoci. Przedstawilimy szczegowo implementacje dostawcy propozycji oraz aktywnoci wyszukiwania. W midzyczasie
pokazalimy rwnie plik metadanych wyszukiwania i plik strings.xml. Analiz plikw wymaganych do implementacji naszego projektu zamkniemy badaniem pliku manifestu aplikacji.

Plik manifest niestandardowego dostawcy propozycji


W pliku manifecie zbieramy wszystkie skadniki naszej aplikacji. Tak samo jak w przypadku innych przykadowych aplikacji, deklarujemy tutaj skadniki naszego dostawcy propozycji, takie
jak aktywno wyszukiwania oraz waciwy kod dostawcy propozycji. Plik ten suy rwnie do
zadeklarowania moliwoci wyszukiwania lokalnego przez aplikacj poprzez ustanowienie
aktywnoci wyszukiwania domylnym procesem przeszukiwania. Zwrmy rwnie uwag
na filtry intencji zdefiniowane dla aktywnoci wyszukiwania.
Poszczeglne wymienione powyej informacje zostay wyrnione pogrubion czcionk w kodzie pliku manifestu (listing 23.27).
Listing 23.27. Plik manifest niestandardowego dostawcy propozycji
//plik: AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.search.custom"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon"
android:label="Niestandardowy dostawca propozycji">

<!-****************************************************************
* Kod zwizany z wyszukiwaniem: aktywno wyszukiwania
****************************************************************

Rozdzia 23 Wyszukiwanie w Androidzie

831

-->
<activity android:name=".SearchActivity"
android:label="Etykieta aktywnoci wyszukiwania"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>

<!-- Deklaracja domylnego wyszukiwania -->


<meta-data android:name="android.app.default_searchable"
android:value=".SearchActivity" />

<!-- Deklaracja dostawcy propozycji -->


<provider android:name="SuggestUrlProvider"
android:authorities="com.androidbook.search.
custom.suggesturlprovider"
/>
</application>
<uses-sdk android:minSdkVersion="4" />
</manifest>

Jak wida, zaznaczylimy trzy elementy:


definicj aktywnoci wyszukiwania oraz zwizany z ni plik XML metadanych
wyszukiwania,
definicj aktywnoci wyszukiwania jako domylnego procesu wyszukiwania w aplikacji,
definicj dostawcy propozycji oraz jego uprawnienia.
Po utworzeniu niezbdnego kodu czas uruchomi aplikacj i sprawdzi, jak si ona prezentuje
na emulatorze.

Korzystanie z niestandardowego dostawcy propozycji


Po skompilowaniu i wdroeniu aplikacji za pomoc narzdzi ADT nie ujrzymy pojawiajcych
si aktywnoci, poniewa adna nie zostaa uruchomiona. Zamiast tego zobaczymy w konsoli
Eclipse komunikat, e aplikacja zostaa poprawnie zainstalowana.
Oznacza to, e dostawca propozycji jest gotowy na przetwarzanie wpisw w globalnym polu QSB.
Zanim to jednak nastpi, musimy doczy naszego dostawc do udziau w procesie wyszukiwania globalnego.
Pokazalimy na pocztku rozdziau, w jaki sposb moemy uruchomi aplikacj ustawie wyszukiwania. Przedstawimy teraz szybsze rozwizanie, korzystajce z tej samej funkcji wyszukiwania, ktr omawiamy w tym rozdziale.
Otwrzmy globalne pole QSB i wpiszmy w nim ustaw. System wywietli nazw aplikacji Ustawienia jako jedn z propozycji wyszukiwania (rysunek 23.26).

832 Android 3. Tworzenie aplikacji

Rysunek 23.26. Wywoywanie aplikacji ustawie za pomoc procesu wyszukiwania

Wykorzystujemy t sam wiedz, ktr uzyskalimy na temat pola QSB, aby wywoa aplikacj
Ustawienia. Skorzystajmy z rozwizania, ktre omwilimy na pocztku rozdziau, i doczmy
nasz aplikacj do propozycji. Gdy to zrobimy, wpiszmy w polu QSB tekst widoczny na rysunku 23.27.

Rysunek 23.27. Wicej wynikw od niestandardowego dostawcy propozycji

Zwrmy uwag na sposb prezentowania propozycji dostarczanych przez naszego dostawc.


Jeli teraz klikniemy ikon wyszukiwania widoczn w lewym grnym rogu ekranu i zmienimy
aplikacj wyszukujc na Niestandardowy dostawca propozycji, nastpnie za przejdziemy do
jednej z propozycji prezentowanych przez niestandardowego dostawc, po czym klikniemy

Rozdzia 23 Wyszukiwanie w Androidzie

833

ikon wyszukiwania, Android bezporednio uruchomi aktywno wyszukiwania z pominiciem


przegldarki, co zostao pokazane na rysunku 23.28 (w ten sposb zostaj zademonstrowane
dwa rodzaje omawianych przez nas dziaa intencji: wyszukiwanie i widok).

Rysunek 23.28. Kwerenda wyszukiwania wywoujca wyniki wyszukiwania

Przykad ten wic pozwala na porwnanie dziaania ACTION_SEARCH i ACTION_VIEW.


Jeli klikniemy teraz pierwsz propozycj z rysunku 23.27 (darmowy sownik), system wywoa
przegldark, co zostao zaprezentowane na rysunku 23.29.

Rysunek 23.29. Darmowy sownik

834 Android 3. Tworzenie aplikacji


Jeeli klikniemy propozycj otwarcia witryny Google (rysunek 23.27), ujrzymy przegldark
widoczn na rysunku 23.30.

Rysunek 23.30. Wyszukiwanie definicji na stronie google.com

Na rysunku 23.31 przedstawiamy widok, jaki pojawi si, jeli nie dodamy przyrostka .m w polu
wyszukiwania globalnego.

Rysunek 23.31. Niestandardowy dostawca pozbawiony podpowiedzi

Odnotujmy fakt, e nasz dostawca propozycji nie przekaza adnego wyniku.

Rozdzia 23 Wyszukiwanie w Androidzie

835

W ten sposb koczymy dyskusj dotyczc tematu budowania od podstaw funkcjonalnego,


niestandardowego dostawcy propozycji. Chocia omwilimy kady aspekt procesu wyszukiwania, istnieje jeszcze kilka tematw, ktrych do tej pory nie poruszylimy. Nale do nich pojcia
przyciskw dziaania oraz danych wyszukiwania specyficznych dla aplikacji. Omwienie tych
tematw jest naszym kolejnym celem.

Zastosowanie przyciskw dziaania


i danych wyszukiwania specyficznych dla aplikacji
Przyciski dziaania oraz specyficzne dla aplikacji dane wyszukiwania zwikszaj elastyczno
procesu wyszukiwania w Androidzie.
Przyciski dziaania umoliwiaj przystosowanie przyciskw funkcyjnych urzdzenia do obsugi
funkcji wyszukiwania. Specyficzne dla aplikacji dane wyszukiwania stanowi dodatkowe informacje, ktre s przekazywane aktywnoci wyszukiwania.
Naley zauway, e kody zawarte na nastpnych listingach nie tworz projektu, ktry
mona przetestowa. Su one wycznie do zilustrowania koncepcji omawianych w tekcie.

Zacznijmy od przyciskw dziaania.

Wykorzystanie przyciskw dziaania


w procesie wyszukiwania
Do tej pory zademonstrowalimy wiele sposobw wywoania procesu wyszukiwania:
poprzez ikon wyszukiwania dostpn w polu QSB,
poprzez przycisk wyszukiwania, stanowicy jeden z przyciskw dziaania
(ukazanych na rysunku 23.1),
jawnie za pomoc ikony lub przycisku, ktre s wywietlane przez aktywno,
poprzez nacinicie dowolnego przycisku na podstawie deklaracji trybu type-to-search.
W tym podrozdziale przyjrzymy si technice wywoywania procesu wyszukiwania za pomoc
przyciskw dziaania. Przyciskami dziaania nazywamy zestaw przyciskw umieszczonych na
urzdzeniu, ktrym s przypisane okrelone dziaania. Przykady kilku takich przyciskw
dziaania zostay przedstawione na listingu 23.28.
Listing 23.28. Lista kodw przyciskw dziaania
keycode_dpad_up
keycode_dpad_down
keycode_dpad_left
keycode_dpad_right
keycode_dpad_center
keycode_back
keycode_call
keycode_camera
keycode_clear
keycode_endcall
keycode_home

836 Android 3. Tworzenie aplikacji


keycode_menu
keycode_mute
keycode_power
keycode_search
keycode_volume_up
keycode_volume_down

Przyciski te s zdefiniowane w interfejsie API klasy KeyEvent, ktrej opis mona znale na
nastpujcej stronie: http://developer.android.com/reference/android/view/KeyEvent.html.
Nie wszystkie wymienione przyciski dziaania mona wykorzysta w procesie wyszukiwania,
jednak cz z nich nie sprawia pod tym wzgldem problemw, na przykad keycode_call.
Naley samodzielnie sprawdzi kady przycisk dziaania pod ktem dziaania i przydatnoci.

Po wybraniu interesujcego nas przycisku dziaania moemy powiadomi o tym system poprzez umieszczenie informacji na ten temat w metadanych w sposb przedstawiony na listingu 23.29.
Listing 23.29. Przykadowa definicja przycisku dziaania
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
android:hint="@string/search_hint"
android:searchMode="showSearchLabelAsBadge"
android:includeInGlobalSearch="true"
android:searchSuggestAuthority="com.androidbook.
search.simplesp.SimpleSuggestionProvider"
android:searchSuggestSelection=" ? "
>
<actionkey
android:keycode="KEYCODE_CALL"
android:queryActionMsg="call"
android:suggestActionMsg="call"
android:suggestActionMsgColumn="call_column" />
<actionkey
android:keycode="KEYCODE_DPAD_CENTER"
android:queryActionMsg="doquery"
android:suggestActionMsg="dosuggest"
android:suggestActionMsgColumn="my_column" />
.....
</searchable>

Moemy rwnie przyporzdkowa kilka przyciskw dziaania dla tego samego kontekstu wyszukiwania. Poniej wyjaniamy, do czego suy kady element actionkey oraz w jaki sposb
jest stosowany do przetwarzania nacinicia przycisku.
keycode jest to kod przycisku zdefiniowany w interfejsie API klasy KeyEvent,
ktry powinien zosta uyty do wywoania aktywnoci wyszukiwania. Moliwo
wcinicia przycisku identyfikowanego przez taki kod pojawia si dwukrotnie. Pierwszy

Rozdzia 23 Wyszukiwanie w Androidzie

837

raz wystpuje ona podczas wpisywania przez uytkownika tekstu wyszukiwania w polu
wyszukiwania, po ktrym nie zostaj wywietlone propozycje. Jeeli funkcja przycisku
dziaania nie zostaa zaimplementowana, uytkownik przewanie kliknie ikon
wyszukiwania w polu QSB. W przypadku zaimplementowania przycisku dziaania
w metadanych wyszukiwania Android pozwala uytkownikowi na wcinicie tego
przycisku zamiast ikony wyszukiwania. Druga sytuacja pojawia si, gdy uytkownik
przejdzie do okrelonej propozycji i wcinie przycisk dziaania. W obydwu przypadkach
dla aktywnoci wyszukiwania system wywouje dziaanie ACTION_SEARCH. Informacj
na ten temat przenosi dodatkowy cig znakw SearchManager.ACTION_KEY. Jeeli
znajduje si w nim jaka warto, to wiadomo, e nastpi wywoanie w odpowiedzi
na wcinicie przycisku dziaania.
queryActionMsg dowolny tekst wpisany w tym elemencie zostaje przekazany jako
warto dodatkowego cigu znakw SearchManager.ACTION_MSG do aktywnoci
wyszukiwania wywoujcej intencj. Jeeli odczytamy t informacj z intencji i jest ona
identyczna z danymi zdefiniowanymi w metadanych, wiadomo bdzie, e nastpuje
bezporednie wywoanie z poziomu pola QSB w wyniku wcinicia przycisku dziaania.
Bez takiego testu nie bdziemy mieli pewnoci, czy dziaanie ACTION_SEARCH zostao
bezporednio wywoane wobec propozycji w odpowiedzi na wcinicie przycisku dziaania.
suggestActionMsg dowolny tekst wpisany w tym elemencie zostaje przekazany
jako warto dodatkowego cigu znakw SearchManager.ACTION_MSG do aktywnoci
wyszukiwania wywoujcej intencj. Dodatkowe przyciski dla tego argumentu i pola
queryActionMsg s takie same. Jeeli przypiszemy tym polom identyczn warto,
na przykad call, nie dowiemy si, w jaki sposb uytkownik wywoa przycisk dziaania.
W wielu przypadkach jest to nieistotne, zatem mona obydwu argumentom przypisa
tak sam warto. Jednak w razie koniecznoci odrnienia obydwu sposobw
wywoania musimy wstawi tu warto inn ni obecna w argumencie queryActionMsg.
suggestActionMsgColumn wartoci argumentw queryActionMsg
i suggestActionMsg s stosowane globalnie wobec danej aktywnoci wyszukiwania
oraz dostawcy propozycji. Nie ma moliwoci zmiany znaczenia dziaania opartego
na propozycji. Mona jednak umieci w metadanych informacj o istnieniu dodatkowej
kolumny w kursorze propozycji. W ten sposb Android pobierze informacje z tej
kolumny i przele j aktywnoci jako cz intencji wywoujcej ACTION_SEARCH.
Co ciekawe, warto tej dodatkowej kolumny jest przesyana w intencji za pomoc
identycznego, dodatkowego klucza, mianowicie SearchManager.ACTION_MSG.

Spord wymienionych atrybutw obowizkowy jest kod przycisku. Ponadto musi by obecny
przynajmniej jeden z pozostaych trzech atrybutw, aby przycisk dziaania zosta uruchomiony.
Jeeli chcemy korzysta z atrybutu suggestActionMsgColumn, musimy zapeni t kolumn w klasie dostawcy propozycji. Gdybymy chcieli uywa obydwu przyciskw, musielibymy w kodzie
z listingu 23.29 umieci dwie dodatkowe kolumny typu string zdefiniowane w kursorze propozycji (listing 23.22), mianowicie kolumny call_column i my_column. W takim przypadku
nasza tablica kolumn kursora powinna przypomina tabel przedstawion na listingu 23.30.
Listing 23.30. Przykadowe kolumny przyciskw dziaania w kursorze propozycji
private static final String[] COLUMNS = {
"_id", // musi zawiera t kolumn
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,

838 Android 3. Tworzenie aplikacji


SearchManager.SUGGEST_COLUMN_INTENT_DATA,
SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
"call_column",
"my_column"
};

Praca ze specyficznym
dla aplikacji kontekstem wyszukiwania
Proces wyszukiwania w Androidzie umoliwia aktywnoci przekazanie dodatkowych danych
wyszukiwania do wywoanej aktywnoci wyszukiwania. Omwimy teraz szczegy tego procesu.
Pokazalimy wczeniej, e aktywno aplikacji moe przesoni metod onSearchRequested(),
dziki czemu otrzymujemy warto false i proces wyszukiwania zostaje wyczony. Co ciekawe,
w ten sam sposb moemy przekaza aktywnoci wyszukiwania dodatkowe dane, specyficzne
dla aplikacji. Na listingu 23.31 zosta zaprezentowany przykad.
Listing 23.31. Przekazywanie dodatkowego kontekstu
public boolean onSearchRequested()
{
Bundle applicationData = new Bundle();
applicationData.putString("string_key","jaka warto typu string");
applicationData.putLong("long_key",290904);
applicationData.putFloat("float_key",2.0f);
startSearch(null,

// Pocztkowy cig znakw kwerendy wyszukiwania


//nie zaznaczaj pocztkowej kwerendy
applicationData, // dodatkowe dane
false // nie wymusza wyszukiwania globalnego
false,

);
return true;
}

Rnorodne funkcje interfejsu API Bundle s omwione pod adresem:


http://developer.android.com/reference/android/os/Bundle.html.

Po uruchomieniu w ten sposb procesu wyszukiwania aktywno moe wykorzysta obiekt


SearchManager.APP_DATA do odczytania kompletu danych aplikacji. Listing 23.32 przedstawia
sposb odczytania kadego z powyszych pl.
Listing 23.32. Odzyskiwanie dodatkowego kontekstu
Bundle applicationData =
queryIntent.getBundleExtra(SearchManager.APP_DATA);
if (applicationData != null)
{
String s = applicationData.getString("string_key");

Rozdzia 23 Wyszukiwanie w Androidzie

839

long l = applicationData.getLong("long_key");
float f = applicationData.getFloat("float_key");
}

Wczeniej omwilimy pobienie metod startSearch(). Jej opis znajdziemy pod nastpujcym
adresem URL, stanowicym cz dokumentacji klasy Activity: http://developer.android.com/
reference/android/app/Activity.html.
Take tutaj metoda ta przyjmuje cztery wymienione poniej argumenty:
initialQuery (argument typu string),
selectInitialQuery (argument logiczny),
applicationDataBundle (argument typu Bundle),
globalSearchOnly (argument logiczny).
Jeeli pierwszy argument zostanie zastosowany, wypeni tekst kwerendy w polu QSB.
Warto true w drugim argumencie spowoduje zaznaczenie tekstu. Uytkownik bdzie mg
zamieni cay zaznaczony tekst kwerendy na jego poprawion wersj. W przypadku wartoci
false kursor zostanie umieszczony na kocu tekstu kwerendy.
Trzeci argument stanowi oczywicie przygotowywany przez nas zestaw danych.
Jeeli w czwartym argumencie zostanie umieszczona warto true, zawsze bdzie wywoywany
proces wyszukiwania globalnego. Po wprowadzeniu wartoci false najpierw zostanie wywoane
wyszukiwanie lokalne (jeeli jest dostpne); w przeciwnym razie bdzie stosowane wyszukiwanie
globalne.

Odnoniki
Na zakoczenie tego rozdziau chcielibymy podzieli si list zasobw, ktre uznalimy za
przydatne podczas jego pisania.
www.google.com/googlephone/AndroidUsersGuide.pdf zawarto tu przydatne materiay
dla wersji 2.2.1 Androida, pomagajce zrozumie techniki korzystania z funkcji
wyszukiwania przez uytkownika.
www.google.com/help/hc/pdfs/mobile/AndroidUsersGuide-30-100.pdf instrukcja
obsugi wersji 3.0 Androida. Adres ten by do czsto zmieniany w cigu ostatnich
kilku miesicy. W przypadku kolejnej zmiany powinnimy bez trudu odnale
odpowiedni plik, wpisujc w wyszukiwarce Google haso: Android Users Guide.
http://developer.android.com/reference/android/app/SearchManager.html adres
ten zawiera gwn dokumentacj dotyczc procesu wyszukiwania w Androidzie,
utworzon przez firm Google. To samo cze stanowi rwnie rdo materiaw
na temat gwnego obiektu odpowiedzialnego za proces wyszukiwania,
mianowicie klasy SearchManager.
http://developer.android.com/reference/android/app/Activity.html#onNewIntent(andr
oid.content.Intent) w miar tworzenia wasnych aktywnoci wyszukiwania warto
czasem konfigurowa je w trybie singleTop, dziki czemu zostanie wygenerowana
metoda onNewIntent(). Tutaj znajdziemy informacje na jej temat.

840 Android 3. Tworzenie aplikacji

http://developer.android.com/resources/samples/SearchableDictionary/index.html
pod tym adresem znajduje si przykadowa implementacja dostawcy propozycji.
cze to wskazuje przede wszystkim kod rdowy implementacji.
http://developer.android.com/reference/android/provider/SearchRecentSuggestions.html
materiay dotyczce interfejsu API przeszukiwania ostatnich propozycji.
http://developer.android.com/guide/topics/fundamentals.html zaprezentowane tutaj
dane pomagaj zrozumie aktywnoci, zadania oraz tryby uruchamiania, zwaszcza
tryb singleTop, czsto uywany jako aktywno wyszukiwania.
http://developer.android.com/reference/android/os/Bundle.html dziki temu
odnonikowi poznamy rne funkcje dostpne w obiekcie Bundle. S to informacje
przydatne zwaszcza podczas implementacji danych wyszukiwania specyficznych
dla aplikacji.
http://www.androidbook.com/notes_on_search na tej stronie znajdziemy wyniki
bada autorw dotyczcych procesu wyszukiwania w Androidzie. Nawet po
opublikowaniu tej ksiki bdziemy aktualizowa zawarto tej witryny.
ftp://ftp.helion.pl/przyklady/and2ta.zip znajdziemy tu projekty testowe przygotowane
z myl o niniejszej ksice. Obiektem naszego zainteresowania jest plik umieszczony
w katalogu ProAndroid3_R23_Wyszukiwanie.

Wyszukiwanie w tabletach
W wersji 3.0 Androida zasadnicza struktura interfejsu API wyszukiwania pozostaa niezmieniona. Jednak pole QSB oraz ustawienia wyszukiwania (dostrzegalne zwaszcza z punktu widzenia uytkownika) zostay nieco zmodyfikowane w celu umoliwienia korzystania z wikszej
powierzchni. Poza tym wszystkie omwione w tym rozdziale koncepcje znajduj zastosowanie
rwnie w przypadku tabletw.

Podsumowanie
W tym rozdziale zaprezentowalimy przede wszystkim szczegowe omwienie dziaania procesu wyszukiwania w Androidzie. Wyjanilimy, w jaki sposb aktywnoci i dostawcy propozycji
wsppracuj z procesem wyszukiwania. Zademonstrowalimy sposb wykorzystania klasy
SearchRecentSuggestionsProvider.
Zaprojektowalimy od podstaw niestandardowego dostawc propozycji, a w midzyczasie dokadnie opisalimy kursor podpowiedzi i jego kolumny. Przyjrzelimy si identyfikatorom URI
odpowiedzialnym za uzyskiwanie danych od dostawcw propozycji. Zaprezentowalimy wiele
fragmentw kodu rdowego, uatwiajcych opracowanie i zaimplementowanie wasnych
strategii wyszukiwania.
Dziki elastycznoci samego kursora propozycji wyszukiwanie w Androidzie przeksztaca si
z prostego procesu w przewodnik po informacjach dostpnych w zasigu rki.

R OZDZIA

24
Analiza interfejsu
przetwarzania tekstu na mow

Poczwszy od wersji 1.6 rodowiska Android, dostpny sta si silnik odpowiedzialny za syntez mowy, nazwany Pico. Za jego pomoc aplikacje w Androidzie
mog przeksztaca cigi znakw tekstowych na dwik mow z akcentem typowym dla wybranego jzyka. Technologia przetwarzania tekstu na mow umoliwia uytkownikowi korzystanie z urzdzenia bez koniecznoci spogldania na
ekran. W przypadku platformy mobilnej jest to niezmiernie istotna funkcja. Ilu
ludziom zdarzyo si wyj przypadkiem na rodek jezdni w trakcie czytania wiadomoci tekstowej? Czy nie wystarczyoby po prostu odsucha tej wiadomoci?
A gdyby mona byo posucha przewodnika turystycznego, zamiast czyta go
w trakcie zwiedzania? Istnieje olbrzymia liczba aplikacji, w przypadku ktrych
zaimplementowanie mowy zwikszyoby ich uyteczno. W tym rozdziale
przyjrzymy si klasie TextToSpeech i wyjanimy, co zrobi, aby wprowadzony
przez nas tekst zosta wypowiedziany przez urzdzenie. Nauczymy si take zarzdza dostpnymi ustawieniami regionalnymi, jzykami i gosami.

Podstawy technologii przetwarzania tekstu


na mow w Androidzie
Zanim wykorzystamy funkcj TTS (ang. Text To Speech tekst na mow) we wasnej
aplikacji, sprawdmy, jak dziaa w rzeczywistoci. W emulatorze lub w urzdzeniu
(wersja systemu co najmniej 1.6) otwrzmy gwny ekran Ustawienia, stamtd
przejdmy do widoku Ustawienia gosowe i wybierzmy element Ustawienia przetwarzania tekstu na mow (lub, w zalenoci od posiadanej wersji, Synteza mowy).
Kliknijmy opcj Posuchaj przykadu, dziki czemu usyszymy sowa This is an
example of speech synthesis in English with Pico (co oznacza: Jest to przykad
syntezy mowy w jzyku angielskim za pomoc silnika Pico). Zwrmy uwag na
pozostae elementy listy (rysunek 24.1).

842 Android 3. Tworzenie aplikacji

Rysunek 24.1. Ekran ustawie silnika przetwarzania tekstu na mow

Moemy zmieni uywany jzyk oraz prdko mowy. Opcja Jzyk zarwno tumaczy wymawiane sowa, jak i zmienia akcent gosu, chocia cigle wymawiany bdzie tekst: Jest to przykad syntezy mowy w tumaczeniu na inne jzyki. Pamitajmy, e funkcja TTS obsuguje jedynie generowanie gosu. Tumaczenie zapewnia oddzielny skadnik, na przykad usuga tumacza
Google, omwiona w rozdziale 11. W trakcie przykadowej implementacji funkcji TTS w naszej
aplikacji bdziemy chcieli, aby syntezowany gos mwi w sposb zgodny z wybranym jzykiem,
zatem eby francuski tekst by wypowiadany przez gos mwicy w sposb waciwy dla jzyka
francuskiego. Prdko mowy jest konfigurowana w zakresie od Bardzo wolno do Bardzo szybko.
Naley zachowa ostrono przed ustawieniem opcji Zawsze uywaj moich ustawie. Wybranie jej w opcjach systemowych przez dowolnego uytkownika moe spowodowa nieprzewidywalne zachowanie aplikacji, poniewa definiowane przez ni parametry mog przesoni
ustawienia tego programu.
Wraz z pojawieniem si wersji 2.2 Androida uzyskalimy moliwo korzystania z mechanizmw TTS innych ni Pico (zatem we wczeniejszych wersjach systemu w widoku ustawie nie
byaby dostpna opcja Domylny mechanizm). Uzyskujemy w ten sposb wiksze moliwoci,
poniewa silnik Pico nie nadaje si do wszystkich zastosowa. Nawet w przypadku posiadania
kilku mechanizmw TTS w urzdzeniu znajduje si tylko jedna usuga przetwarzania mowy.
Jest ona wspdzielona przez wszystkie aktywnoci urzdzenia, zatem musimy mie wiadomo, e nie tylko okrelona aplikacja moe korzysta z tej funkcji. Oznacza to take, e nie
moemy by pewni, kiedy (jeeli w ogle) nasz tekst zostanie wypowiedziany. Jednak dziki
interfejsowi funkcji TTS posiadamy dostp do metod zwrotnych, zatem mamy pojcie, co si
dzieje z wysanym przez nas tekstem. Usuga TTS bdzie ledzia wybrany przez nas mechanizm
przetwarzania mowy oraz bdzie go wykorzystywaa do zlecanych operacji. Moe ona korzysta
z dowolnego silnika wywoywanego przez aktywno, zatem pozostae aplikacje mog korzysta
z innych silnikw przetwarzania mowy, a my nie musimy si przejmowa tym aspektem.
Sprawdmy, co si dzieje podczas konfigurowania tych ustawie funkcji TTS. Android uruchamia poza wzrokiem uytkownika usug przetwarzania tekstu na mow oraz silnik Pico,
czyli wielojzyczny mechanizm syntezy mowy. Aktywno ustawie, ktr uruchomilimy,
zainicjalizowaa silnik przetwarzajcy biecy jzyk i prdko mowy. Po klikniciu opcji Posu-

Rozdzia 24 Analiza interfejsu przetwarzania tekstu na mow

843

chaj przykadu aktywno preferencji przesya tekst do usugi, po czym z kolei silnik przetwarzania mowy wymawia ten tekst poprzez gonik. Silnik Pico rozbija tekst na fragmenty, ktre
potrafi wymwi, i skada te porcje dwikw w cakiem naturalny sposb. Algorytmy tworzce
ten silnik s o wiele bardziej zoone moemy jednak udawa, e mamy do czynienia z magi.
Na szczcie dla nas magia ta zajmuje bardzo niewiele pamici oraz pojemnoci dyskowej, zatem silnik Pico jest idealnym dodatkiem do telefonu.
W poniszym przykadzie utworzymy aplikacj, ktra bdzie odczytywaa na gos wpisywane
przez nas informacje. Nie jest ona skomplikowana; zostaa zaprojektowana w celu pokazania,
jak atwo mona zaimplementowa funkcj przetwarzania tekstu na mow. Na pocztku utwrzmy nowy projekt w Androidzie, korzystajc z artefaktw zamieszczonych na listingu 24.1.
Na kocu rozdziau zamieszczamy odnoniki do pliku zawierajcego omawiane tu projekty.
W ten sposb mona zaimportowa te projekty bezporednio do rodowiska Eclipse.
Listing 24.1. Kody XML i Java prostej wersji demonstracyjnej funkcji TTS
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText android:id="@+id/wordsToSpeak"
android:hint="Wpisz sowa do wymwienia"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/speak"
android:text="Powiedz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doSpeak"
android:enabled="false" />
</LinearLayout>

// Jest to plik MainActivity.java


import
import
import
import
import
import
import
import
import

android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.speech.tts.TextToSpeech;
android.speech.tts.TextToSpeech.OnInitListener;
android.util.Log;
android.view.View;
android.widget.Button;
android.widget.EditText;

public class MainActivity extends Activity implements OnInitListener {


private EditText words = null;
private Button speakBtn = null;

844 Android 3. Tworzenie aplikacji


private static final int REQ_TTS_STATUS_CHECK = 0;
private static final String TAG = "TTS Demo";
private TextToSpeech mTts;

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
words = (EditText)findViewById(R.id.wordsToSpeak);
speakBtn = (Button)findViewById(R.id.speak);

// Upewnia si, czy funkcja TTS istnieje i jest sprawna


Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, REQ_TTS_STATUS_CHECK);
}
public void doSpeak(View view) {
mTts.speak(words.getText().toString(),
TextToSpeech.QUEUE_ADD, null);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQ_TTS_STATUS_CHECK) {
switch (resultCode) {
case TextToSpeech.Engine.CHECK_VOICE_DATA_PASS:

// Funkcja TTS jest sprawna i dziaa


mTts = new TextToSpeech(this, this);
Log.v(TAG, "Silnik Pico jest poprawnie zainstalowany");
break;
case TextToSpeech.Engine.CHECK_VOICE_DATA_BAD_DATA:
case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_DATA:
case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_VOLUME:

// Brakujce dane, instaluje je


Log.v(TAG, "Potrzebny jest jezyk: " + resultCode);
Intent installIntent = new Intent();
installIntent.setAction(
TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
break;
case TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL:
default:
Log.e(TAG, "Niepowodzenie. Funkcja TTS jest najwidoczniej niedostepna");
}

}
else {

// Otrzyma co innego
}
}
public void onInit(int status) {

// Skoro funkcja TTS dziaa, aktywujemy przycisk


if( status == TextToSpeech.SUCCESS) {

Rozdzia 24 Analiza interfejsu przetwarzania tekstu na mow

845

speakBtn.setEnabled(true);
}
}
@Override
public void onPause()
{
super.onPause();

// Przy zmianie aktywnoci pierwszoplanowej przestaje mwi


if( mTts != null)
mTts.stop();
}
@Override
public void onDestroy()
{
super.onDestroy();
mTts.shutdown();
}
}

W powyszym przykadzie interfejsem uytkownika jest widok EditText, umoliwiajcy wpisywanie sw do gonego odczytania, oraz przycisk inicjalizujcy syntez mowy (rysunek 24.2).
Nasz przycisk zawiera metod doSpeak(), pobierajc cig znakw tekstowych z widoku
EditText i kolejkujc ten tekst za pomoc funkcji speak() zawierajcej argument QUEUE_ADD.
Pamitajmy, e silnik TTS jest wspdzielony, wic nasz tekst moe by kolejkowany za dowolnym innym obiektem (nie zdarza si to jednak zbyt czsto). Inn opcj poza QUEUE_ADD jest
QUEUE_FLUSH, powodujca usunicie z kolejki innego tekstu i natychmiastowe odtworzenie naszego. Na kocu metody onCreate() uruchamiamy obiekt Intent, dajcy od funkcji TTS, aby powiadomia nas o poprawnoci wpisanego tekstu. Poniewa wymagamy zwrotu
odpowiedzi, stosujemy metod startActivityForResult() i przekazujemy kod dania. Odpowied otrzymujemy w metodzie onActivityResult(), w ktrej szukamy argumentu
CHECK_VOICE_DATA_PASS. Poniewa silnik TTS moe przekaza rne typy kodu resultCode
oznaczajcego poprawno wykonanych dziaa, nie moemy korzysta z argumentu RESULT_OK.
Inne uzyskiwane wartoci moemy pozna, przegldajc instrukcj przeczania.

Rysunek 24.2. Demonstracja interfejsu uytkownika silnika TTS

Jeeli otrzymamy z powrotem argument CHECK_VOICE_DATA_PASS, zostanie utworzony obiekt


TextToSpeech. Zauwamy, e aktywno MainActivity implementuje obiekt nasuchujcy
OnInitListener. Dziki temu otrzymujemy (wraz z metod onInit()) metod zwrotn
w przypadku utworzenia i udostpnienia interfejsu TTS. Jeeli wewntrz metody onInit()
zostanie przekazana warto SUCCESS, urzdzenie jest gotowe do odczytania tekstu, a w interfejsie uytkownika zostaje uaktywniony przycisk. Warto rwnie zwrci uwag na wywoanie
funkcji stop() w metodzie onPause(), a take na wywoanie funkcji shutdown() w metodzie

846 Android 3. Tworzenie aplikacji


onDestroy().

Funkcj stop() wywoujemy, poniewa jeeli nasza aplikacja nie bdzie przetwarzana na pierwszym planie, bdzie to oznacza, e uytkownik jest zajty czym innym, zatem gone odczytywanie tekstu powinno zosta przerwane. Nie chcemy zakca adnej zwizanej z dwikiem czynnoci w innej aktywnoci, ktra pojawia si na ekranie. Za pomoc
metody shutdown() powiadamiamy system, e skoczylimy korzysta z silnika TTS, a zasoby, jeli nie s ju wykorzystywane, staj si niepotrzebne i mog zosta zwolnione.

Warto poeksperymentowa na tym przykadzie. Sprawdmy rne zdania lub wyraenia. Umiemy duy blok tekstu, aby sprawdzi, jak syntezator si sprawuje przy duszych wypowiedziach. Zastanwmy si teraz, co by si stao, gdyby w trakcie czytania takiego duego bloku
tekstu praca naszej aplikacji zostaa przerwana, na przykad z powodu innej aplikacji uzyskujcej dostp do silnika TTS, dziaajcej w trybie QUEUE_FLUSH, albo gdyby nasza aplikacja zesza
po prostu na dalszy plan. Przetestujmy takie zachowanie, wciskajc przycisk ekranu startowego
w trakcie odczytywania duej partii tekstu. Z powodu implementacji funkcji stop() w metodzie onPause() proces przetwarzania tekstu na mow zostaje zatrzymany, nawet jeli aplikacja
cay czas dziaa w tle. Skd mamy wiedzie, w ktrym miejscu aplikacja skoczya odczyt, w przypadku gdy znowu znajdzie si na pierwszym planie? Byaby to przydatna funkcjonalno,
gdyby istnia sposb zapamitywania punktu przerwania odczytywania tekstu, aby po ponownym uruchomieniu aplikacji proces gonego odczytywania tekstu rozpocz si od punktu
przerwania, a przynajmniej w jego pobliu. Istnieje rozwizanie, lecz jest nieco pracochonne.

Uywanie wyrae do ledzenia toku wypowiedzi


Silnik TTS moe zwrotnie wywoa aplikacj po odczytaniu fragmentu tekstu, w wiecie przetwarzania tekstu na mow zwanego wyraeniem. Wywoanie to konfigurujemy za pomoc
metody setOnUtteranceCompletedListener() wobec wystpienia silnika TTS, w powyszym przykadzie wobec zmiennej mTts. W trakcie wywoywania metody speak() moemy
doda par nazwa klucz, abymy zostali powiadomieni przez silnik TTS o zakoczeniu odtwarzania wyraenia. Poprzez wysyanie niepowtarzalnych identyfikatorw wyrae do silnika
TTS wiemy, ktre wyraenia zostay przeczytane, a ktre jeszcze czekaj w kolejce. Jeeli aplikacja wrci na pierwszy plan po uprzednim przerwaniu jej dziaania, moemy wznowi przetwarzanie tekstu na mow od pierwszego nieodczytanego wyraenia. W tym celu naley w powyszym przykadowym projekcie zmieni kod, tak jak zostao to pokazane na listingu 24.2, lub
zajrze do projektu TTSDemo2, zamieszczonego na naszej stronie internetowej.
Listing 24.2. Zmiany aktywnoci MainActivity umoliwiajce ledzenie wyrae
// Dodajmy nastpujce instrukcje importowania
import java.util.HashMap;
import java.util.StringTokenizer;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;

// Zmiemy klas MainActivity


public class MainActivity extends Activity implements OnInitListener,
OnUtteranceCompletedListener {

// Dodajmy nastpujce prywatne pola


private int uttCount = 0;
private int lastUtterance = -1;
private HashMap<String, String> params = new HashMap<String, String>();

Rozdzia 24 Analiza interfejsu przetwarzania tekstu na mow

847

// Zmodyfikujmy metod onInit


public void onInit(int status) {

// Teraz silnik TTS jest przygotowany, wic uaktywniamy przycisk


if( status == TextToSpeech.SUCCESS) {
speakBtn.setEnabled(true);
mTts.setOnUtteranceCompletedListener(this);
}
}

// Dodajemy now metod onUtteranceCompleted


public void onUtteranceCompleted(String uttId) {
Log.v(TAG, "Utworzono komunikat dla obiektu uttId: " + uttId);
lastUtterance = Integer.parseInt(uttId);
}

// Modyfikujemy metod doSpeak


public void doSpeak(View view) {
StringTokenizer st = new StringTokenizer(words.getText().toString(),",.");
while (st.hasMoreTokens()) {
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
String.valueOf(uttCount++));
mTts.speak(st.nextToken(), TextToSpeech.QUEUE_ADD, params);
}
}

W pierwszej kolejnoci trzeba si upewni, e nasza aktywno MainActivity implementuje rwnie interfejs OnUtteranceCompletedListener. Dziki temu uzyskamy metod zwrotn z silnika
TTS po zakoczeniu odczytywania wyrae. Musimy take zmodyfikowa metod doSpeak()
przycisku, aby przekazywaa dodatkowe informacje, czce identyfikator wyraenia z kadym
fragmentem wysyanego tekstu. W naszej nowej wersji aplikacji separatorami wyrae bd
przecinki i kropki. Nastpnie utworzymy ptl przekazywania wyrae za pomoc argumentu
QUEUE_ADD, a nie QUEUE_FLUSH (nie chcemy sami sobie przerywa!), oraz unikatowego identyfikatora wyraenia, ktry w istocie jest prostym licznikiem inkrementacyjnym, oczywicie
przekonwertowanym na dane typu String. Z tego powodu identyfikatorem wyraenia moe
by dowolny tekst; nie jestemy ograniczeni wycznie do cyfr. W rzeczywistoci moemy wykorzysta dowolny cig znakw, chocia zbyt dugie identyfikatory typu string mog by niekorzystne pod ktem wydajnoci. Musimy tak zmodyfikowa metod onInit(), abymy mogli
rejestrowa wywoania oznaczajce zakoczenie przetwarzania wyraenia. Powinnimy take
wprowadzi na kocu metod zwrotn onUtteranceCompleted() wywoujc silnik TTS po
zakoczeniu przetwarzania wyraenia. W naszym przykadzie kade zakoczone wyraenie
spowoduje utworzenie wpisu w dzienniku w narzdziu LogCat.
Po uruchomieniu naszej nowej aplikacji wpiszmy jaki tekst zawierajcy przecinki i kropki,
a nastpnie kliknijmy przycisk Powiedz. Obserwujmy okno narzdzia LogCat w trakcie suchania gosu odczytujcego tekst. Zauwamy, e tekst zostanie natychmiast zakolejkowany, a po
zakoczeniu kadego wyraenia nastpuje wykonanie metody zwrotnej, powodujcej wywietlenie odpowiedniego wpisu w dzienniku. Jeeli w trakcie odczytu tekstu przerwiemy dziaanie
tej aplikacji, na przykad wciskajc przycisk ekranu startowego, zostan przerwane zarwno
proces przetwarzania tekstu na mow, jak i metody zwrotne. Znamy teraz ostatnie odczytane
wyraenie i moemy rozpocz od tego miejsca dalsze odsuchiwanie.

848 Android 3. Tworzenie aplikacji

Zastosowanie plikw dwikowych


do przetwarzania tekstu na mow
W silniku TTS wprowadzono rozwizanie pozwalajce na poprawne wypowiadanie sw i wyrae, ktre w domylnym przypadku byyby le wymawiane. Jeli na przykad wpiszemy Don
Quixote jako wymawiany tekst, imi to zostanie niepoprawnie przeczytane. Trzeba jednak
uczciwie przyzna, e cho silnik TTS potrafi dobrze przewidzie brzmienie poszczeglnych
wyrazw, to trudno si spodziewa, e zna wszystkie wyjtki. Jak zatem mona sobie z tym
poradzi? Jednym ze sposobw jest zarejestrowanie fragmentu dwikowego, ktry bdzie
odtwarzany zamiast domylnego gosu. Aby uzyska efekt brzmienia tego samego gosu,
spowodujemy, e silnik TTS wypowie sowo w podany sposb, zarejestrujemy wynik, a nastpnie poinformujemy mechanizm przetwarzania tekstu na mow o zastpieniu domylnego dwiku nagranym przed chwil dwikiem. Sztuka polega na dostarczeniu tekstu, ktry
bdzie odczytywany w podany przez nas sposb. Zobaczmy, jak si to robi.
Utwrzmy nowy projekt Androida w rodowisku Eclipse. Wykorzystajmy plik XML z listingu
24.3 do utworzenia gwnego ukadu graficznego. Cay proces uprocimy, umieszczajc tekst
bezporednio w pliku ukadu graficznego, a nie za pomoc odnonikw do cigw znakw.
W przypadku zwykej aplikacji umiecilibymy tu identyfikator zasobu typu string. Wygld
ukadu graficznego zosta przedstawiony na rysunku 24.3.
Listing 24.3. Plik XML ukadu graficznego demonstrujcy zapisany plik audio z zarejestrowan
wymow tekstu
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText android:id="@+id/wordsToSpeak"
android:text="Dohn Keyhotay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/speakBtn"
android:text="Powiedz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doButton"
android:enabled="false" />
<TextView android:id="@+id/filenameLabel"
android:text="Nazwa pliku:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText android:id="@+id/filename"
android:text="/sdcard/donquixote.wav"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>

Rozdzia 24 Analiza interfejsu przetwarzania tekstu na mow

849

<Button android:id="@+id/recordBtn"
android:text="Rejestruj"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
android:onClick="doButton"/>
<Button android:id="@+id/playBtn"
android:text="Odtwrz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doButton"
android:enabled="false" />
<TextView android:id="@+id/useWithLabel"
android:text="Wykorzystaj z:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText android:id="@+id/realText"
android:text="Don Quixote"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/assocBtn"
android:text="Skojarz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doButton"
android:enabled="false" />
</LinearLayout>

Rysunek 24.3. Interfejs uytkownika aplikacji demonstracyjnej kojarzcej plik dwikowy z tekstem

850 Android 3. Tworzenie aplikacji


Potrzebne jest pole, w ktrym bdzie przechowywany specjalny tekst, nagrywany przez silnik
TTS do pliku dwikowego. W ukadzie graficznym zamiecilimy rwnie nazw tego pliku.
Ostatnim etapem bdzie powizanie naszego pliku dwikowego z rzeczywistym cigiem znakw,
dla ktrego ten plik ma by odtwarzany.
Przyjrzyjmy si teraz kodowi Java aktywnoci MainActivity (listing 24.4). W metodzie
onCreate() konfigurujemy procedury obsugi klikni przyciskw Powiedz, Odtwrz, Rejestruj
i Skojarz, a nastpnie inicjalizujemy silnik TTS za pomoc intencji. Pozostaa cz kodu skada
si z wywoa przetwarzajcych wyniki zwracane z intencji sprawdzajcej poprawno konfiguracji silnika TTS, przetwarzajcych wyniki inicjalizacji z silnika TTS oraz ze zwykych metod
zwrotnych, wstrzymujcych i zamykajcych nasz aktywno.
Listing 24.4. Kod Java przedstawiajcy proces zapisywania pliku dwikowego dla tekstu
import java.io.File;
import java.util.ArrayList;
import
import
import
import
import
import
import
import
import
import
import
import

android.app.Activity;
android.content.Intent;
android.media.MediaPlayer;
android.os.Bundle;
android.speech.tts.TextToSpeech;
android.speech.tts.TextToSpeech.OnInitListener;
android.util.Log;
android.view.View;
android.view.View.OnClickListener;
android.widget.Button;
android.widget.EditText;
android.widget.Toast;

public class MainActivity extends Activity implements OnInitListener {


private EditText words = null;
private Button speakBtn = null;
private EditText filename = null;
private Button recordBtn = null;
private Button playBtn = null;
private EditText useWith = null;
private Button assocBtn = null;
private String soundFilename = null;
private File soundFile = null;
private static final int REQ_TTS_STATUS_CHECK = 0;
private static final String TAG = "Demonstracja silnika TTS";
private TextToSpeech mTts = null;
private MediaPlayer player = null;

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
words = (EditText)findViewById(R.id.wordsToSpeak);
filename = (EditText)findViewById(R.id.filename);
useWith = (EditText)findViewById(R.id.realText);

Rozdzia 24 Analiza interfejsu przetwarzania tekstu na mow

speakBtn = (Button)findViewById(R.id.speakBtn);
recordBtn = (Button)findViewById(R.id.recordBtn);
playBtn = (Button)findViewById(R.id.playBtn);
assocBtn = (Button)findViewById(R.id.assocBtn);

// Upewnia si, e silnik TTS istnieje i jest sprawny


Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, REQ_TTS_STATUS_CHECK);
}
public void doButton(View view) {
switch(view.getId()) {
case R.id.speakBtn:
mTts.speak(words.getText().toString(),
TextToSpeech.QUEUE_ADD, null);
break;
case R.id.recordBtn:
soundFilename = filename.getText().toString();
soundFile = new File(soundFilename);
if (soundFile.exists())
soundFile.delete();
if(mTts.synthesizeToFile(words.getText().toString(),
null, soundFilename) == TextToSpeech.SUCCESS) {
Toast.makeText(getBaseContext(),
"Utworzono plik dwikowy",
Toast.LENGTH_SHORT).show();
playBtn.setEnabled(true);
assocBtn.setEnabled(true);
}
else {
Toast.makeText(getBaseContext(),
"Ups! Nie utworzono pliku dwikowego",
Toast.LENGTH_SHORT).show();
}
break;
case R.id.playBtn:
try {
player = new MediaPlayer();
player.setDataSource(soundFilename);
player.prepare();
player.start();
}
catch(Exception e) {
Toast.makeText(getBaseContext(),
"Hm. Nie mona odtworzy pliku",
Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
break;
case R.id.assocBtn:
mTts.addSpeech(useWith.getText().toString(), soundFilename);
Toast.makeText(getBaseContext(),
"Powizano!",
Toast.LENGTH_SHORT).show();

851

852 Android 3. Tworzenie aplikacji


break;
}
}
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == REQ_TTS_STATUS_CHECK) {
switch (resultCode) {
case TextToSpeech.Engine.CHECK_VOICE_DATA_PASS:

// Silnik TTS jest utworzony i uruchomiony


mTts = new TextToSpeech(this, this);
Log.v(TAG, "Silnik Pico zostal poprawnie zainstalowany!");
ArrayList<String> available =
data.getStringArrayListExtra("availableVoices");
break;
case TextToSpeech.Engine.CHECK_VOICE_DATA_BAD_DATA:
case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_DATA:
case TextToSpeech.Engine.CHECK_VOICE_DATA_MISSING_VOLUME:

// Brakuje danych, naley je zainstalowa


Log.v(TAG, "Potrzebny pakiet jezykowy: " + resultCode);
Intent installIntent = new Intent();
installIntent.setAction(
TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
break;
case TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL:
default:
Log.e(TAG, "Wystapil blad. Silnik TTS jest niedostepny");
}
}
else {

// Jaka inna operacja


}
}
public void onInit(int status) {

// Po przygotowaniu silnika TTS uaktywniamy przyciski


if( status == TextToSpeech.SUCCESS) {
speakBtn.setEnabled(true);
recordBtn.setEnabled(true);
}
}
@Override
public void onPause()
{
super.onPause();

// Jeeli aplikacja schodzi z pierwszego planu, zatrzymujemy odtwarzanie


if(player != null) {
player.stop();
}

// Jeeli aplikacja schodzi z pierwszego planu, przerywamy syntez mowy


if( mTts != null)
mTts.stop();
}

Rozdzia 24 Analiza interfejsu przetwarzania tekstu na mow

853

@Override
public void onDestroy()
{
super.onDestroy();
if(player != null) {
player.release();
}
if( mTts != null) {
mTts.shutdown();
}
}
}

Aby ten przykadowy kod zadziaa, musimy w pliku AndroidManifest.xml doda uprawnienie
android.permission.WRITE_EXTERNAL_STORAGE. Po uruchomieniu tego przykadu powinnimy ujrze interfejs UI, zilustrowany na rysunku 24.3.
Zarejestrujemy poprawne brzmienie tekstu Don Quixote, nie bdziemy wic korzysta z rzeczywistych sw. Musimy skonstruowa tekst, ktry zostanie wypowiedziany z waciwym
brzmieniem sw. Kliknijmy przycisk Powiedz, aby usysze, jak brzmi utworzona przez nas
imitacja tekstu. Nie najgorzej! Wybierzmy teraz przycisk Rejestruj, dziki czemu brzmienie tego
wyrazu zostanie zapisane w pliku WAV. Po pomylnym zarejestrowaniu dwiku stan si aktywne przyciski Odtwrz i Skojarz. Kliknijmy przycisk Odtwrz, a usyszymy nagrany plik WAV
bezporednio w odtwarzaczu MediaPlayer. Jeeli odpowiada nam brzmienie tego wyrazu,
uyjmy przycisku Skojarz. W silniku TTS zostanie wywoana metoda addSpeech(), ktra
powie nastpnie nasz nowy plik dwikowy z cigiem znakw umieszczonym w polu Wykorzystaj z. Jeeli cay proces przebiegnie pomylnie, wpiszmy w grnym widoku EditText wyraz
Don Quixote i kliknijmy przycisk Powiedz. Teraz imi to zostaje odczytane we waciwy sposb.
Zwrmy uwag, e metoda synthesizeToFile() zapisuje dwik wycznie do formatu WAV,
niezalenie od wstawionego rozszerzenia. Istnieje jednak moliwo skojarzenia innych formatw dwikowych za pomoc metody addSpeech() na przykad plikw MP3. Pliki MP3
nie mog zosta utworzone za pomoc metody synthesizeToFile() silnika TTS.
Zastosowania tej metody w przypadku mowy s bardzo ograniczone. W przypadku scenariusza z nieskojarzonymi sowami to znaczy gdy nie wiemy, jakie sowa bd wypowiadane
nie ma moliwoci przygotowania zawczasu wszystkich plikw dwikowych, ktre posuyyby do naprawienia wyrazw niepoprawnie syntezowanych przez silnik Pico. W scenariuszach zawierajcych skojarzone sowa na przykad odczyt prognozy pogody moemy
przetestowa wszystkie wyrazy w aplikacji, aby znale i odpowiednio zmodyfikowa te, ktre
nie brzmi waciwie. Moemy na przykad przygotowa wczeniej plik dwikowy gotowy do
wypowiedzenia nazwy firmy lub nawet nazwiska!
Istnieje jednak ograniczenie stosowania tej metody: tekst przekazywany metodzie speak() musi
idealnie odpowiada tekstowi wykorzystanemu do wywoania metody addSpeech(). Niestety,
nie moemy zapewni pliku dwikowego dla pojedynczego wyrazu, a nastpnie oczekiwa, e
silnik TTS zastosuje ten plik dla wspomnianego sowa, podczas gdy jest ono przekazywane
metodzie speak() jako cz duszego zdania. Aby nasz plik dwikowy zosta odtworzony,
musimy wprowadzi tekst dokadnie odpowiadajcy temu plikowi. Jeden wyraz mniej lub
wicej, a silnik Pico gubi si i stara si syntezowa dwik najlepiej, jak potrafi.

854 Android 3. Tworzenie aplikacji


Pewnym rozwizaniem jest podzielenie tekstu na wyrazy i przekazywanie kadego z nich do
silnika TTS oddzielnie. Chocia pliki dwikowe bd w ten sposb odtwarzane (oczywicie
musielibymy oddzielnie zarejestrowa brzmienie wyrazw Don i Quixote), sama mowa bdzie brzmiaa bardzo nierwnomiernie, jak gdyby kady wyraz by jednoczenie caym zdaniem. Takie podejcie nie przeszkadza w pewnych aplikacjach. Wykorzystywanie plikw dwikowych nadaje si najlepiej w przypadku predefiniowanych zestaww wyrazw lub wyrae,
o ktrych doskonale wiemy, w jakich momentach s wypowiadane.
Co zatem powinnimy zrobi, gdy wiemy, e pojawi si w zdaniach sowa, ktre nie bd poprawnie wypowiadane przez silnik Pico? Jedn z metod jest wyszukanie kopotliwych wyrazw i zastpienie ich imitacjami tych sw, ktre po odczytaniu brzmiayby tak, jak powinny
brzmie waciwe wyrazy. Nie musimy pokazywa uytkownikowi tekstu umieszczanego w metodzie speak(). Moe wystarczy wic zastpi wyraz Quixote sowem Keyhotay, zanim
wywoamy metod speak(). W rezultacie otrzymamy waciwe brzmienie wyrazw, a uytkownik
nie bdzie mia pojcia o naszej sztuczce. Z perspektywy zarzdzania zasobami przechowywanie
cigu znakw imitacji jest o wiele bardziej skuteczne od przechowywania pliku dwikowego,
nawet jeli oznacza to cige wywoywanie silnika Pico. Musia by on wywoywany dla pozostaej czci tekstu, wic tak naprawd nie tracimy wiele. Nie naley jednak zbytnio przecenia
zdolnoci przewidywania silnika Pico. Mamy na myli, e algorytmy silnika Pico potrafi w inteligentny sposb skada wyrazy i jeeli sprbujemy wykonywa ich obowizki, moemy szybko
popa w kopoty.
W powyszym przykadzie zarejestrowalimy plik dwikowy dla fragmentu tekstu, dziki czemu
podczas czytania tego tekstu silnik TTS uzyska dostp do rda audio, zamiast dokona syntezy
mowy. Mona si spodziewa, e odtworzenie maego pliku dwikowego obcia mniejsz ilo
zasobw urzdzenia ni uruchomienie silnika TTS i nawizanie z nim poczenia. Zatem jeeli
posiadamy zarzdzan pul wyrazw, ktre maj by wymawiane przez urzdzenie, moe nam
si opaci utworzenie biblioteki plikw dwikowych, nawet jeli silnik Pico bezbdnie je wymawia. W ten sposb aplikacja bdzie dziaa szybciej. Prawdopodobnie w przypadku posiadania niewielkiej liczby plikw dwikowych bdziemy rwnie obcia mniejsz ilo pamici.
W przypadku takiego rozwizania przydatne moe si okaza wywoanie nastpujcej metody:
TextToSpeech.addSpeech(String text, String packagename, int soundFileResourceId)

Jest bardzo prosty sposb dodania plikw dwikowych do silnika TTS. Argument text definiuje cig znakw, dla ktrego bdzie odtwarzany plik audio, packagename stanowi nazw
pakietu, w ktrym jest przechowywany ten zasb, a soundFileResourceId jest identyfikatorem
zasobu tego pliku dwikowego. Pliki dwikowe powinny by przechowywane w katalogu
/res/raw aplikacji. Podczas uruchamiania aplikacji dodajemy przygotowane pliki dwikowe do
silnika TTS poprzez odniesienie si do ich identyfikatora zasobw (np. R.raw.quixote).
Oczywicie bdzie potrzebna jaka baza danych albo predefiniowana lista, dziki ktrej bdzie
wiadomo, jakiemu tekstowi odpowiada dany plik dwikowy. Jeeli internacjonalizujemy aplikacj, moemy przechowywa alternatywne pliki dwikowe w odpowiednim podkatalogu
/res/raw; na przykad /res/raw-fr dla plikw jzyka francuskiego.

Zaawansowane funkcje silnika TTS


Po zapoznaniu si z podstawami technologii przetwarzania tekstu na mow zbadajmy nieco
bardziej zaawansowane funkcje silnika Pico. Zaczniemy od konfigurowania strumieni dwikowych, co umoliwia kierowanie wypowiedzi do odpowiedniego kanau wyjciowego audio.

Rozdzia 24 Analiza interfejsu przetwarzania tekstu na mow

855

Nastpnie omwimy pojcia ikony akustycznej oraz odtwarzania ciszy. Rozdzia zamkniemy
tematami konfiguracji ustawie jzykowych oraz wywoa rozmaitych metod.

Konfiguracja strumieni audio


Skorzystalimy wczeniej z parametrw HashMap do przekazania dodatkowych argumentw
silnikowi TTS. Jeden z przekazywanych argumentw (KEY_PARAM_STREAM) okrela, ktre rdo dwikowe ma zosta uyte przez silnik TTS dla odtwarzanego tekstu. W tabeli 24.1 zostaa
zamieszczona lista dostpnych strumieni audio.
Tabela 24.1. Dostpne strumienie audio
Strumie audio

Opis

STREAM_ALARM

Strumie dwikowy dla alarmw.

STREAM_DTMF

Strumie dwikowy dla sygnaw DTMF


(np. sygnaw wciskania przyciskw).

STREAM_MUSIC

Strumie dwikowy dla odtwarzania muzyki.

STREAM_NOTIFICATION

Strumie dwikowy dla powiadomie.

STREAM_RING

Strumie dwikowy dla dzwonka telefonicznego.

STREAM_SYSTEM

Strumie dwikowy dla dwikw systemowych.

STREAM_VOICE_CALL

Strumie dwikowy dla rozmw telefonicznych.

Jeeli syntezowane wyraenie jest zwizane z alarmem, powinnimy zdefiniowa w silniku


TTS odtwarzanie dwiku poprzez strumie audio przeznaczony dla alarmw. Zatem przed
wywoaniem metody speak() musimy wprowadzi nastpujcy wiersz kodu:
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
String.valueOf(AudioManager.STREAM_ALARM));

Na listingu 24.2 pokazalimy sposb konfigurowania i przekazywania parametrw HashMap


dla metody speak(). Do parametrw HashMap precyzujcych strumie audio moemy rwnie
doczy identyfikator wyraenia.

Stosowanie ikon akustycznych


Istnieje jeszcze jeden rodzaj dwiku, nazwany ikon akustyczn (ang. earcon), ktry moe
by odtwarzany przez silnik TTS. Ikona akustyczna nie suy do reprezentowania tekstu, lecz
do informowania za pomoc dwiku o jakim zdarzeniu lub do wskazywania elementu, ktry
nie jest tekstem. Moe ona stanowi dwik powiadamiajcy o odczytywaniu wypunktowania
w prezentacji albo o przejciu na kolejn stron. Jeli z kolei tworzymy multimedialny przewodnik
turystyczny, aplikacja moe w ten sposb zaprosi uytkownika do przejcia do nastpnego
punktu podry.
Aby przypisa ikon akustyczn do odtwarzania, musimy wywoa metod addEarcon(),
ktra w podobny sposb jak metoda addSpeech() pobiera dwa lub trzy argumenty. Pierwszym argumentem, podobnym do pola tekstowego w metodzie addSpeech(), jest nazwa ikony
akustycznej. Zgodnie z konwencj powinnimy zamieci nazw ikony akustycznej w nawiasie
kwadratowym, np. [boing]. W przypadku metody pobierajcej dwa argumenty tym drugim
argumentem jest cig znakw reprezentujcy nazw pliku. W przypadku wstawienia trzech

856 Android 3. Tworzenie aplikacji


argumentw drugim argumentem jest nazwa pakietu, a trzecim identyfikator zasobu odnoszcego si do pliku audio, przechowywanego najprawdopodobniej w katalogu /res/raw. Do
odtworzenia ikony akustycznej wykorzystujemy metod playEarcon(), ktra ze swoimi trzema argumentami bardzo przypomina metod speak(). Na listingu 24.5 zamiecilimy przykad korzystania z ikony akustycznej.
Listing 24.5. Przykadowy kod wykorzystujcy ikony akustyczne
String turnPageEarcon = "[turnPage]";
mTts.addEarcon(turnPageEarcon, "com.androidbook.tts.demo",
R.raw.turnpage);
mTts.playEarcon(turnPageEarcon, TextToSpeech.QUEUE_ADD, params);

Powodem stosowania ikon akustycznych zamiast standardowego odtwarzania plikw dwikowych w programie MediaPlayer jest mechanizm kolejkowania, zastosowany w silniku TTS.
Nie trzeba oblicza stosownego momentu, w ktrym zostanie odtworzony sygna akustyczny,
ani polega na waciwym dopasowaniu czasowym metod zwrotnych; wystarczy, e zakolejkujemy ikony akustyczne wrd tekstu wysyanego do silnika TTS. Jestemy wtedy pewni, e nasze
ikony akustyczne bd odtwarzane we waciwym momencie, wic moemy wykorzysta takie
samo rozwizanie do zaprezentowania dwikw uytkownikowi. Istnieje take moliwo
wprowadzenia wywoania metody onUtteranceCompleted() powiadamiajcej nas o aktualnej
pozycji odtwarzania.

Odtwarzanie ciszy
Silnik TTS zawiera jeszcze jedn metod, ktr moemy wykorzysta playSilence(). Podobnie jak w przypadku metod speak() i playEarcon(), take ta funkcja posiada trzy argumenty,
z ktrych drugi definiuje tryb kolejki, a w trzecim umieszczone s opcjonalne parametry
HashMap. Pierwszy parametr metody playSilence() jest typu long i okrela w milisekundach
czas trwania ciszy. Najprawdopodobniej metoda ta bdzie stosowana w trybie QUEUE_ADD,
umoliwiajcym oddzielenie w czasie dwch rnych tekstowych cigw znakw.
Innymi sowy, moemy pomidzy dwoma tekstami odtwarza cisz bez koniecznoci zarzdzania czasem oczekiwania w aplikacji. Po prostu wywoujemy metod speak(), nastpnie
playSilence() i znowu speak() w celu osignicia zamierzonego efektu. Poniej przedstawiamy przykadowy sposb wykorzystania metody playSilence() do wprowadzenia dwusekundowego opnienia:
mTts.playSilence(2000, TextToSpeech.QUEUE_ADD, params);

Wybr innych mechanizmw przetwarzania tekstu na mow


Aby zdefiniowa dany silnik TTS, moemy wykorzysta metod setEngineByPackageName(),
w ktrej argumentem jest nazwa pakietu danego mechanizmu. W przypadku silnika Pico pakiet nosi nazw com.svox.pico. Aby umoliwi uytkownikowi korzystanie z domylnego
silnika TTS, stosujemy metod getDefaultEngine(). Te dwie metody nie mog zosta wywoane przed metod onInit(), gdy w przeciwnym wypadku nie zadziaaj. Nie s one rwnie
interpretowane przez wersje Androida starsze od 2.2.

Rozdzia 24 Analiza interfejsu przetwarzania tekstu na mow

857

Stosowanie metod jzykowych


Nie poruszylimy dotychczas tematyki wymawiania sw w rnych jzykach, zatem zajmiemy
si ni teraz. Technologia TTS odczytuje tekst za pomoc gosu przemawiajcego w jzyku tego
tekstu, to znaczy, e tekst napisany w jzyku woskim bdzie czytany przez Wocha. W celu zapewnienia poprawnoci wypowiedzi rozpoznawane s rne cechy tekstu. Z tego powodu stosowanie gosu przemawiajcego w jzyku innym od uywanego w tekcie nie ma sensu. Odczytywanie tekstu napisanego w jzyku francuskim przez syntezator generujcy wyrazy w jzyku
woskim spowoduje prawdopodobnie kopoty; najlepiej dopasowa ustawienia regionalne tekstu
do ustawie regionalnych gosu.
Silnik TTS zawiera pewne metody obsugujce jzyki, suce zarwno do rozpoznawania uywanego jzyka, jak i do ustawienia wasnego. Domylnie jest dostpnych tylko kilka pakietw
jzykowych, jednak wiksza ich liczba bdzie dostpna w sklepie Android Market. Na listingu
24.1 zosta zaprezentowany fragment kodu w metodzie zwrotnej onActivityResult(), za
pomoc ktrego zostaa utworzona klasa Intent wyszukujca brakujcy pakiet jzykowy. Oczywicie istnieje moliwo, e pakiet danego jzyka nie zosta jeszcze utworzony, jednak ich liczba
z kadym dniem ronie.
Metod pozwalajc na sprawdzenie dostpnoci jzyka jest isLanguageAvailable(Locale
locale). Poniewa ustawienia lokalne mog reprezentowa zarwno kraj, jak i jzyk, a czasem take rne ich odmiany, nie wystarczy przekazanie odpowiedzi true albo false. Istniej nastpujce odpowiedzi: TextToSpeech.LANG_COUNTRY_AVAILABLE, oznaczajca, e
obsugiwane s pastwo i jzyk; TextToSpeech.LANG_AVAILABLE obsugiwany jest jzyk, lecz
nie pastwo; oraz TextToSpeech.LANG_NOT_SUPPORTED ani pastwo, ani jzyk nie s obsugiwane. Jeeli otrzymamy warto TextToSpeech.LANG_MISSING_DATA, to znaczy, e jzyk jest
obsugiwany, lecz silnik TTS nie znalaz plikw danych. Aplikacja powinna skierowa uytkownika do Android Market lub innego rda, w ktrym bdzie mona znale pliki pakietu
jzykowego. Na przykad moe by obsugiwany jzyk francuski, lecz nie kanadyjski francuski.
W takim przypadku po przekazaniu argumentu Locale.CANADA_FRENCH silnikowi TTS uzyskamy
odpowied TextToSpeech.LANG_AVAILABLE, a nie TextToSpeech.LANG_COUNTRY_AVAILABLE.
Kolejn warto, jaka moe zosta zwrcona (TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE),
stanowi specjalny przypadek, gdy ustawienia lokalne obejmuj rwnie wariant jzykowy; w takim
wypadku obsugiwany jest kraj oraz jzyki.
Stosowanie metody isLanguageAvailable() stanowi mudny sposb okrelania wszystkich
jzykw obsugiwanych przez silnik TTS. Na szczcie moemy si dowiedzie, ktre jzyki
s dostpne od rki. Jeeli przyjrzymy si uwanie listingowi 24.4, a dokadniej metodzie
zwrotnej onActivityResult(), w miejscu, w ktrym odczytujemy odpowied intencji, zauwaymy, e obiekt danych zawiera list jzykw obsugiwanych przez silnik przetwarzania
tekstu na mow. Znajdziemy tam zmienn typu ArrayList, nazwan available, pod przypadkiem CHECK_VOICE_DATA_PASS. Stanowi ona tablic zawierajc wartoci jzykw. Wartoci te
przyjmuj na przykad posta eng-USA lub fra-FRA. Podczas gdy wartoci ustawie regionalnych posiadaj skadni jj-kk, gdzie jj stanowi dwuznakow reprezentacj jzyka, a kk analogicznie definiuje kraj, w przypadku silnika TTS do utworzenia obiektu okrelajcego ustawienia
regionalne wykorzystywana jest trzyliterowa skadnia jjj-kkk. Niestety, otrzymujemy z powrotem tablic zawierajc cigi znakw, a nie ustawienia regionalne, zatem musimy
wprowadzi jaki mechanizm analizowania skadni lub mapowania w celu okrelenia, ktre
jzyki s rzeczywicie dostpne dla danego silnika TTS.

858 Android 3. Tworzenie aplikacji


Jzyk ustawiamy za pomoc metody setLanguage(Locale locale). Zostaj zwrcone takie
same kody jak w przypadku metody isLanguageAvailable(). Jeeli chcemy skorzysta z tej
metody, wywoajmy j ju po inicjalizacji silnika TTS, to znaczy wewntrz metody onInit() lub
pniej. W przeciwnym wypadku nie zadziaa mechanizm wyboru jzyka. Biece, domylne
ustawienie lokalne urzdzenia poznajemy za pomoc metody Locale.getDefault(), ktra przekazuje warto tego ustawienia, na przykad en_US. Metoda getLanguage() klasy TextToSpeech
pozwala pozna biece ustawienie lokalne silnika TTS. Podobnie jak w przypadku metody
setLanguage(), nie naley wywoywa metody getLanguage() przed wystpieniem funkcji
onInit(). Wartoci przekazywane przez t metod wygldaj na przykad jak eng_USA. Zwrmy uwag, e czon definiujcy jzyk jest oddzielony od czonu okrelajcego pastwo za pomoc znaku podkrelenia, a nie mylnika. Chocia wydaje si, e Android jest wyrozumiay
w kwestii wartoci ustawie lokalnych, byoby mio, gdyby w przyszoci interfejs ten zosta w jaki
sposb ujednolicony. Umieszczenie w naszym przykadzie nastpujcego wiersza, ustanawiajcego
jzyk stosowany w silniku TTS, jest dopuszczalne:
switch(mTts.setLanguage(Locale.getDefault())) {
case TextToSpeech.LANG_COUNTRY_AVAILABLE:

Na pocztku rozdziau wspomnielimy o gwnym ustawieniu Zawsze uywaj moich ustawie,


przesaniajcym ustawienia jzykowe aplikacji. W wersji 2.2 Androida metoda areDefaults
Enforced() klasy TextToSpeech pozwala na okrelenie, czy uytkownik zaznaczy t opcj.
Metoda ta przyjmuje wartoci true i false. Moemy zadecydowa wewntrz aplikacji, czy wybr danego jzyka moe zosta przesonity, w wyniku czego aplikacja moe podj odpowiednie dziaanie.
Na zakoczenie omwienia technologii przetwarzania tekstu na mow wspomnimy o kilku innych metodach, ktre moemy wykorzysta. Funkcja setPitch(float pitch) zmienia wysoko gosu bez wpywu na prdko mwienia. Standardowa warto wysokoci gosu wynosi
1.0. Najmniejsz wartoci rozrnialn przez zmys suchu jest 0.5, najwysz natomiast
2.0; moemy ustawia wartoci wysze i nisze, nie sycha jednak adnej rnicy po przekroczeniu wartoci progowych. Takie same wartoci progowe zostay zdefiniowane dla metody
setSpeechRate(float rate). Oznacza to, e moemy przekaza tej metodzie zmiennoprzecinkowy argument o wartoci mieszczcej si w zakresie od 0.5 do 2.0, gdzie 1.0 oznacza
standardow prdko mwienia. Wartoci wysze od 1.0 definiuj szybsz mow, a nisze od
zwykej wartoci zwalniaj szybko wypowiedzi. Kolejn przydatn metod jest isSpeaking().
Poprzez zwrot wartoci false lub true wskazuje ona, czy silnik TTS w biecym momencie
przeprowadza proces syntezy mowy (w tym rwnie odtwarzanie ciszy generowane przez metod playSilence()). Jeeli chcemy zosta powiadomieni o zakoczeniu wypowiadania wszelkich zakolejkowanych tekstw, moemy zaimplementowa odbiorc BroadcastReceiver wobec
transmisji ACTION_TTS_QUEUE_PROCESSING_COMPLETED.

Odnoniki
Poniej prezentujemy przydatne adresy, pod ktrymi mona znale materiay rozwijajce zagadnienia omawiane w tym rozdziale:
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu list projektw, ktre
moemy pobra i zaimportowa do rodowiska Eclipse. Projekty utworzone z myl
o niniejszym rozdziale zostay umieszczone w katalogu ProAndroid3_R24_TekstNaMow.

Rozdzia 24 Analiza interfejsu przetwarzania tekstu na mow

859

W katalogu znajdziemy rwnie plik Czytaj.TXT, zawierajcy dokadne instrukcje


importowania projektw do rodowiska Eclipse.
http://groups.google.com/group/tts-for-android adres ten kieruje nas do grupy
dyskusyjnej, zajmujcej si interfejsem przetwarzania tekstu na mow.
https://groups.google.com/group/eyes-free cze do strony grupy zajmujcej si
projektem Eyes-Free, mechanizmem o otwartym kodzie rdowym, dostarczajcym
funkcje uatwie dostpu do systemu Android. Znajdziemy tam rwnie odnoniki
do kodu rdowego.

Podsumowanie
W tym rozdziale pokazalimy, w jaki sposb aplikacja systemu Android moe przemwi do
uytkownika. Android zosta wyposaony w bardzo przyjemny silnik TTS, pozwalajcy na atwe
wykorzystanie tej funkcji. Dla programisty nie ma tu zbyt wiele do nauki. Silnik Pico obsuy
wikszo operacji. Pokazalimy, e jeeli syntezator natrafi na problematyczne sowo, istniej
sposoby uzyskania podanego efektu jego wymawiania. Zaawansowane funkcje silnika rwnie znacznie uatwiaj ycie. Podczas pracy z technologi przetwarzania tekstu na mow musimy pamita o kilku podstawowych zasadach: o oszczdzaniu zasobw, rozsdnym wspdzieleniu silnika TTS oraz waciwym wykorzystaniu syntezatora mowy.

860 Android 3. Tworzenie aplikacji

R OZDZIA

25
Ekrany dotykowe

Wiele urzdze obsugiwanych przez system Android posiada ekran dotykowy.


W przypadku braku klawiatury fizycznej dane musz by wprowadzane przez
uytkownika za pomoc takiego dotykowego ekranu. Nieraz wic aplikacje musz
mie moliwo przetwarzania danych wprowadzanych poprzez dotyk. Czytelnicy
najprawdopodobniej mieli ju do czynienia z wirtualn klawiatur, ktra jest
wywietlana w momentach, gdy naley wprowadzi jakie dane. W rozdziale 17.,
powiconym programowaniu aplikacji wywietlajcych mapy, dotyk posuy nam
do przesuwania mapy. Jeszcze nie omawialimy takich implementacji interfejsu
ekranu dotykowego, teraz jednak pokaemy, w jaki sposb mona wykorzysta
jego moliwoci.
Podzielilimy ten rozdzia na cztery zasadnicze czci. Cz pierwsza zostaa
powicona obiektom klasy MotionEvent, ktre powiadamiaj aplikacj o tym,
e uytkownik dotyka ekranu. Zajmiemy si w tej czci take obiektami klasy
VelocityTracker oraz funkcj przecigania elementw na ekranie. W drugim
podrozdziale skupimy si na wielodotykowoci (ang. multi-touch), czyli moliwoci wprowadzania danych za pomoc wielu palcw jednoczenie. W czci trzeciej omwimy funkcje dotyku w aplikacjach obsugujcych mapy, poniewa s
w nich stosowane specjalne klasy i metody, uatwiajce powizanie map z ekranem
dotykowym. Ostatni podrozdzia dotyczy gestw, czyli wyspecjalizowanego rozwizania, w ktrym zaprogramowane sekwencje dotykowe s interpretowane jako
polecenia.

Klasa MotionEvent
W tej czci rozdziau skupimy si na tym, w jaki sposb system przekazuje do
aplikacji informacje o zdarzeniach dotykowych, czyli o tym, e uytkownik
dotkn ekranu. Na razie skupimy si na dotykaniu ekranu za pomoc jednego
palca (funkcja wielodotykowoci zostanie omwiona w dalszej czci rozdziau).
Ekran dotykowy jest wykonany ze specjalnych tworzyw, ktre mog przetworzy
informacj o naciniciu na wsprzdne ekranu. Informacja o dotyku jest przetwarzana na dane, ktre s z kolei przekazywane oprogramowaniu.

862 Android 3. Tworzenie aplikacji

Obiekt MotionEvent
Dotknicie ekranu przez uytkownika powoduje utworzenie obiektu MotionEvent. Obiekt ten
zawiera informacje o czasie i miejscu dotknicia oraz inne informacje na temat tego zdarzenia.
Zostaje on przekazany odpowiedniej metodzie danej aplikacji. Moe to by na przykad metoda
onTouchEvent() klasy View. Nie zapominajmy, e klasa View jest nadrzdna w stosunku do
do licznej grupy innych klas, w tym takich jak Layout, Button, List, Surface, Clock i tak
dalej. Oznacza to, e za pomoc zdarze dotykowych moemy oddziaywa na te wszystkie
rodzaje obiektw klasy View. Wywoana metoda moe przeanalizowa obiekt MotionView
w celu podjcia decyzji o rodzaju przeprowadzanej czynnoci. Na przykad klasa MapView moe
wykorzystywa zdarzenia dotykowe do przesuwania mapy na boki, dziki czemu uytkownik
rcznie wyszukuje interesujce go punkty. Obiekt wirtualnej klawiatury moe odbiera zdarzenia dotykowe, ktre po wciniciu klawisza dotykowego s przetwarzane na znaki wprowadzane
do innej czci interfejsu uytkownika.
Obiekt MotionEvent bierze udzia w sekwencji zdarze zwizanych ze zjawiskiem dotykania
ekranu przez uytkownika. Sekwencja taka zostaje uruchomiona w chwili dotknicia ekranu, jest
kontynuowana w trakcie przesuwania palca po ekranie, a koczy si po jego oderwaniu od wywietlacza. Zdarzenia przyoenia palca do ekranu (dziaanie ACTION_DOWN), przesuwania palca
(ACTION_MOVE) i oderwania palca (ACTION_UP) powoduj tworzenie obiektw MotionEvent.
Dziaania typu ACTION_MOVE mog jednorazowo powodowa tworzenie kilku obiektw Motion
Event, gdy palec porusza si po ekranie, zanim pojawi si ostatnie dziaanie ACTION_UP.
Kady obiekt MotionEvent zawiera informacje o rodzaju wykonywanego aktualnie dziaania, lokalizacji dotknicia, sile nacisku, obszarze dotyku, czasie wykonania dziaania oraz o momencie
zainicjalizowania dziaania ACTION_DOWN. Istnieje rwnie czwarty typ dziaania ACTION_
CANCEL. Powiadamia ono aplikacj o zakoczeniu sekwencji dotykowej bez przetwarzania jej
na czynno. Ostatnim rodzajem dziaania jest ACTION_OUTSIDE, ktre jest ustanawiane w specjalnym przypadku, gdy dotknicie odbywa si poza oknem danej aplikacji, lecz musi by
zarejestrowane.
Istnieje rwnie inny sposb odbierania zdarze dotykowych, polegajcy na zarejestrowaniu
w obiekcie klasy View procedury obsugi metod zwrotnych dla zdarze dotykowych. Klasa odbierajca zdarzenia musi zaimplementowa interfejs View.OnTouchListener i powinna zosta
wywoana metoda setOnTouchListener() klasy View w celu skonfigurowania procedury obsugi
tego obiektu. Implementacja klasy interfejsu View.OnTouchListener musi zawiera metod
onTouch(). Podczas gdy parametrem metody onTouchEvent() jest jedynie obiekt MotionEvent,
metoda onTouch() przyjmuje jako parametry obiekty klas View i MotionEvent. Wynika to
z faktu, e klasa OnTouchListener moe odbiera obiekty MotionEvent dla wielu widokw.
Stanie si to bardziej zrozumiae po przeanalizowaniu przykadowej aplikacji.
Jeeli procedura obsugi obiektu MotionEvent (poprzez metod onTouchEvent() lub onTouch())
obsuy dane zdarzenie i informacja o nim nie musi by przekazywana dalej, metoda powinna
powrci z wartoci true. W ten sposb system otrzymuje informacj, e nie trzeba przesya
zdarzenia do innych widokw. Jeeli klasa View nie potrzebuje informacji o tym zdarzeniu
ani o adnym przyszym zdarzeniu zwizanym z t sekwencj dotyku, powraca z wartoci false.
Metoda onTouchEvent() bazowej klasy View nie wykonuje adnej czynnoci i przekazuje
warto false. Jej klasy podrzdne mog, ale nie musz zachowywa si w ten sam sposb. Na
przykad kontrolka Button obsuy zdarzenie dotyku, poniewa dotknicie jest rwnowane
klikniciu, zatem powraca z wartoci true z metody onTouchEvent(). Po otrzymaniu
dziaania ACTION_DOWN kontrolka Button zmieni swj kolor, aby w ten sposb poinformowa,

Rozdzia 25 Ekrany dotykowe

863

e przetwarza zdarzenie kliknicia. Obiekt ten czeka rwnie na dziaanie ACTION_UP, ktre oznacza zakoczenie czynnoci przez uytkownika, dziki czemu zostaje uruchomiony proces kliknicia. Jeeli kontrolka Button przekazaa z metody onTouchEvent() warto false, nie otrzyma
ju adnego obiektu MotionEvent, informujcego o zdjciu przez uytkownika palca z ekranu.
Jeeli chcemy, aby zdarzenia dotyku definioway now czynno na okrelonym obiekcie View,
moemy rozszerzy klas, przesoni metod onTouchEvent() i wstawi wasny algorytm.
Moemy take zaimplementowa interfejs View.OnTouchListener i skonfigurowa procedur
obsugi metod zwrotnych wobec klasy View. Jeeli skonfigurujemy procedur obsugi wywoania
metody onTouch(), zostan do niej dostarczone wszelkie obiekty MotionView, zanim przejd
do metody onTouchEvent() klasy View. Metoda onTouchEvent() zostanie wywoana jedynie
w przypadku przekazania wartoci false przez metod onTouch(). Przejdmy do przykadowej aplikacji, ktra powinna uatwi zrozumienie omwionych zjawisk.
Na kocu rozdziau zamiecilimy adres URL strony, z ktrej mona pobra
i zaimportowa bezporednio do rodowiska Eclipse projekty utworzone z myl
o niniejszym rozdziale.

Na listingu 25.1 przedstawilimy kod XML ukadu graficznego aplikacji. Utwrzmy nowy projekt w rodowisku Eclipse i wstawmy w nim ten ukad graficzny.
Listing 25.1. Plik XML ukadu graficznego dla aplikacji TouchDemo1
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/layout1"
android:tag="trueLayoutTop"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
>
<com.androidbook.touch.demo1.TrueButton android:text="returns true"
android:id="@+id/trueBtn1"
android:tag="trueBtnTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.androidbook.touch.demo1.FalseButton android:text="returns false"
android:id="@+id/falseBtn1"
android:tag="falseBtnTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/trueBtn1" />
</RelativeLayout>
<RelativeLayout

864 Android 3. Tworzenie aplikacji


android:id="@+id/layout2"
android:tag="falseLayoutBottom"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#FF00FF"
>
<com.androidbook.touch.demo1.TrueButton android:text="returns true"
android:id="@+id/trueBtn2"
android:tag="trueBtnBottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.androidbook.touch.demo1.FalseButton android:text="returns false"
android:id="@+id/falseBtn2"
android:tag="falseBtnBottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/trueBtn2" />
</RelativeLayout>
</LinearLayout>

Naley wyjani kilka spraw na temat tego ukadu graficznego. Wprowadzilimy znaczniki do
obiektw interfejsu uytkownika. Bdziemy odnosi si do tych znacznikw w kodzie rdowym w przypadku wystpowania zdarze dla tych obiektw. Wykorzystalimy take menedery
RelativeLayout do rozmieszczenia obiektw. Zwrmy uwag na zastosowanie niestandardowych kontrolek (TrueButton i FalseButton). Z kodu Java dowiadujemy si, e klasy te
wywodz si z klasy Button. Z tego powodu moemy stosowa wobec nich wszystkie atrybuty
standardowych przyciskw. Na rysunku 25.1 ukazalimy wygld tego ukadu graficznego, a na
listingu 25.2 prezentujemy kod Java naszych przyciskw.

Rysunek 25.1. Interfejs uytkownika aplikacji TouchDemo1

Rozdzia 25 Ekrany dotykowe

Listing 25.2. Kod Java klas Button dla aplikacji TouchDemo1


// Jest to plik BooleanButton.java
import
import
import
import
import

android.content.Context;
android.util.AttributeSet;
android.util.Log;
android.view.MotionEvent;
android.widget.Button;

public abstract class BooleanButton extends Button {


protected boolean myValue() {
return false;
}
public BooleanButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
String myTag = this.getTag().toString();
Log.v(myTag, "-----------------------------------");
Log.v(myTag, MainActivity.describeEvent(this, event));
Log.v(myTag, "metoda super onTouchEvent() przekazuje " +
super.onTouchEvent(event));
Log.v(myTag, "a ja przekazuje " + myValue());
return(myValue());
}
}

// Jest to plik TrueButton.java


import android.content.Context;
import android.util.AttributeSet;
public class TrueButton extends BooleanButton {
protected boolean myValue() {
return true;
}
public TrueButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
}

// Jest to plik FalseButton.java


import android.content.Context;
import android.util.AttributeSet;
public class FalseButton extends BooleanButton {
public FalseButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
}

865

866 Android 3. Tworzenie aplikacji


Wprowadzilimy klas BooleanButton, abymy mogli wielokrotnie stosowa metod onTouch
do ktrej dodalimy funkcj zapisywania informacji w dzienniku. Nastpnie utworzylimy klasy TrueButton i FalseButton, ktre bd inaczej reagoway na przekazywane
im obiekty MotionEvent. Stanie si to bardziej oczywiste po przyjrzeniu si kodowi gwnej
aktywnoci, ukazanemu na listingu 25.3.

Event(),

Listing 25.3. Kod Java naszej gwnej aktywnoci


// Jest to plik MainActivity.java
import
import
import
import
import
import
import
import

android.app.Activity;
android.os.Bundle;
android.util.Log;
android.view.MotionEvent;
android.view.View;
android.view.View.OnTouchListener;
android.widget.Button;
android.widget.RelativeLayout;

public class MainActivity extends Activity implements OnTouchListener {

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
RelativeLayout layout1 = (RelativeLayout) findViewById(R.id.layout1);
layout1.setOnTouchListener(this);
Button trueBtn1 = (Button)findViewById(R.id.trueBtn1);
trueBtn1.setOnTouchListener(this);
Button falseBtn1 = (Button)findViewById(R.id.falseBtn1);
falseBtn1.setOnTouchListener(this);
RelativeLayout layout2 = (RelativeLayout) findViewById(R.id.layout2);
layout2.setOnTouchListener(this);
Button trueBtn2 = (Button)findViewById(R.id.trueBtn2);
trueBtn2.setOnTouchListener(this);
Button falseBtn2 = (Button)findViewById(R.id.falseBtn2);
falseBtn2.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
String myTag = v.getTag().toString();
Log.v(myTag, "-----------------------------");
Log.v(myTag, "Umieszczono widok " + myTag + " w metodzie onTouch");
Log.v(myTag, describeEvent(v, event));
if( "true".equals(myTag.substring(0, 4))) {

/* Log.v(myTag, "*** wywolywanie mojej metody onTouchEvent() ***");


v.onTouchEvent(event);
Log.v(myTag, "*** powrot z metody onTouchEvent() ***"); */
Log.v(myTag, "i przekazuj warto true");
return true;
}
else {
Log.v(myTag, "i przekazuj warto false");

Rozdzia 25 Ekrany dotykowe

867

return false;
}
}
protected static String describeEvent(View view, MotionEvent event) {
StringBuilder result = new StringBuilder(300);
result.append("Dziaanie: ").append(event.getAction()).append("\n");
result.append("Lokacja: ").append(event.getX()).append(" x ")
.append(event.getY()).append("\n");
if( event.getX() < 0 || event.getX() > view.getWidth() ||
event.getY() < 0 || event.getY() > view.getHeight()) {
result.append(">>> Zdarzenie dotyku opucio widok <<<\n");
}
result.append("Flagi krawdzi: ").append(event.getEdgeFlags()).append("\n");
result.append("Sia nacisku: ").append(event.getPressure()).append(" ");
result.append("Rozmiar: ").append(event.getSize()).append("\n");
result.append("Czas dotknicia: ").append(event.getDownTime()).append("ms\n");
result.append("Czas zdarzenia: ").append(event.getEventTime()).append("ms");
result.append(" Szacowany: ").append(event.getEventTime()-event.getDownTime());
result.append(" ms\n");
return result.toString();
}
}

Kod naszej aktywnoci konfiguruje metody zwrotne dla przyciskw i ukadw graficznych
w taki sposb, aby zdarzenia dotyku (na przykad obiekty MotionEvent) byy przetwarzane dla
kadego elementu interfejsu uytkownika. Utworzylimy rozbudowany system zapisywania
informacji o zdarzeniach w dzienniku, zatem bdziemy doskonale wiedzie, co si wydarzyo
podczas dotknicia ekranu. Po skompilowaniu i uruchomieniu aplikacji powinnimy ujrze taki
sam ekran jak na rysunku 25.1.
Aby w peni wykorzysta aplikacj, musimy otworzy w rodowisku Eclipse narzdzie LogCat,
aby na bieco obserwowa komunikaty wywietlane podczas dotykania ekranu. Aplikacja dziaa
rwnie dobrze na emulatorze, jak w rzeczywistym urzdzeniu. Zalecamy take zmaksymalizowanie okna narzdzia LogCat, aby mc atwiej przewija ekran i przeglda wszystkie komunikaty wygenerowane przez aplikacj. Okno to zostanie zmaksymalizowane po dwukrotnym
klikniciu zakadki LogCat. Przejdmy teraz do interfejsu UI aplikacji i dotknijmy przycisku
przekazuje warto true, znajdujcego si u gry ekranu (w przypadku korzystania z emulatora kliknijmy krtko ten przycisk). W oknie LogCat powinny pojawi si przynajmniej dwa
komunikaty o zdarzeniach. Zostan one oznaczone jako przychodzce z obiektu trueBtnTop
i zapisane w dzienniku za pomoc metody onTouch() klasy MainActivity. Moemy spojrze na
kod metody onTouch() w pliku MainActivity.java. Po przeanalizowaniu wynikw w oknie
LogCat, mona stwierdzi, wywoania ktrych metod generuj wartoci. Na przykad warto
wywietlana po etykiecie Dziaanie: pochodzi z metody getAction(). Na listingu 25.4 zosta
przedstawiony przykadowy wpis z dziennika podczas korzystania z emulatora, a listing 25.5
ukazuje to samo, ale podczas uytkowania rzeczywistego urzdzenia.
Listing 25.4. Przykadowe komunikaty narzdzia LogCat, pochodzce z aplikacji TouchDemo1
zainstalowanej na emulatorze
trueBtnTop
trueBtnTop
trueBtnTop

----------------------------Umieszczono widok trueBtnTop w metodzie onTouch


Dziaanie: 0

868 Android 3. Tworzenie aplikacji


trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop

Lokacja: 52.0 x 20.0


Flagi krawdzi: 0
Sia nacisku: 0.0 Rozmiar: 0.0
Czas dotknicia: 163669ms
Czas zdarzenia: 163669ms Szacowany: 0 ms
i przekazuj warto true
----------------------------Umieszczono widok trueBtnTop w metodzie onTouch
Dziaanie: 1
Lokacja: 52.0 x 20.0
Flagi krawdzi: 0
Sia nacisku: 0.0 Rozmiar: 0.0
Czas dotknicia: 163669ms
Czas zdarzenia: 163831ms Szacowany: 162 ms
i przekazuj warto true

Listing 25.5. Przykadowe komunikaty narzdzia LogCat, pochodzce z aplikacji TouchDemo1


zainstalowanej na rzeczywistym urzdzeniu
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop

----------------------------Umieszczono widok trueBtnTop w metodzie onTouch


Dziaanie: 0
Lokacja: 42.8374 x 25.293747
Flagi krawdzi: 0
Sia nacisku: 0.05490196 Rozmiar: 0.2
Czas dotknicia: 24959412ms
Czas zdarzenia: 24959412ms Szacowany: 0 ms
i przekazuj warto true
----------------------------Umieszczono widok trueBtnTop w metodzie onTouch
Dziaanie: 2
Lokacja: 42.8374 x 25.293747
Flagi krawdzie: 0
Sia nacisku: 0.05490196 Size: 0.2
Czas dotknicia: 24959412ms
Czas zdarzenia: 24959530ms Szacowany: 118 ms
i przekazuj warto true
----------------------------Umieszczono widok trueBtnTop w metodzie onTouch
Dziaanie: 1
Lokacja: 42.8374 x 25.293747
Flagi krawdzi: 0
Sia nacisku: 0.05490196 Rozmiar: 0.2
Czas dotknicia: 24959412ms
Czas zdarzenia: 24959567ms Szacowany: 155 ms
i przekazuj warto true

Pierwsze zdarzenie posiada dziaanie oznaczone jako 0, ktrego odpowiednikiem jest ACTION_
Dziaanie ostatniego zdarzenia posiada etykiet 1, oznaczajc ACTION_UP. W przypadku uywania rzeczywistego urzdzenia moe si pojawi wicej zdarze. Wszystkie zdarzenia porednie midzy dziaaniami ACTION_DOWN a ACTION_UP bd prawdopodobnie
okrelane przez dziaanie o wartoci 2, reprezentujce ACTION_MOVE. Pozostaymi moliwociami s wartoci 3 dla dziaania ACTION_CANCEL i 4 dla dziaania ACTION_OUTSIDE. Podczas

DOWN.

Rozdzia 25 Ekrany dotykowe

869

uywania palcw na prawdziwym wywietlaczu czasami nie ma moliwoci dotknicia i oderwania palca bez nieznacznego przesunicia nim po ekranie, zatem pewne niespodziewane zdarzenia ACTION_MOVE nie powinny budzi zdziwienia.
Istniej rwnie inne rnice pomidzy emulatorem a rzeczywistym urzdzeniem. Zauwamy, e dokadno wskazywania pooenia na emulatorze jest rzdu liczb naturalnych
(52 na 20), podczas gdy rzeczywiste urzdzenie generuje wartoci w formie uamkw (42.8374
na 25.293747). Pooenie zdarzenia MotionEvent posiada skadowe X i Y, gdzie warto X
oznacza odlego od lewej krawdzi widoku View do dotknitego punktu, natomiast warto Y
reprezentuje dystans od grnej czci widoku View do punktu dotknicia.
Zauwamy rwnie, e sia nacisku, jak rwnie jego rozmiar w emulatorze posiadaj warto 0.
W przypadku rzeczywistego urzdzenia sia nacisku wskazuje na to, jak mocno zosta przycinity palec do ekranu dotykowego, a rozmiar oznacza obszar dotknitego wywietlacza. Jeeli
dotkniemy leciutko ekran czubkiem maego palca, wartoci siy nacisku oraz rozmiaru bd
nieznaczne. Jeli natomiast mocno przyciniemy ekran kciukiem, wartoci obydwu parametrw osign wiksze wartoci. Zgodnie z dokumentacj wartoci siy nacisku i rozmiaru
mieszcz si w przedziale od 0 do 1. Jednak ze wzgldu na rnice sprztowe okrelenie bezwzgldnych wartoci tych dwch parametrw moe by sporym problemem. Dobrze byoby porwna wartoci tych dwch parametrw pomidzy poszczeglnymi zdarzeniami MotionEvent, jednak uznanie wartoci wikszej od 0.8 za mocne wcinicie moe sprawi nam nie
lada kopot. Na danym urzdzeniu mona nigdy nie przekroczy wartoci 0.8. Moe nawet nie
uda si przekroczy wartoci 0.2.
Wartoci czasu dotyku i czasu zdarzenia s wyliczane tak samo na emulatorze i w rzeczywistym
urzdzeniu, jedyna rnica polega na tym, e w rzeczywistym urzdzeniu pojawiaj si o wiele
wiksze wartoci. Rwnie obliczenia szacowanego czasu przebiegaj w identyczny sposb.
Flagi krawdzi su do wykrycia momentu, w ktrym dotyk przekroczy fizyczn granic wywietlacza. W dokumentacji zestawu Android SDK wystpuje stwierdzenie, e flagi te su do
wskazywania momentu zetknicia palca z krawdzi ekranu (grn, doln, lew lub praw).
Jednak metoda getEdgeFlags() moe zawsze przekazywa warto 0, w zalenoci od rodzaju
uywanego urzdzenia lub emulatora. W przypadku niektrych ukadw elektronicznych
trudno wykry krawd ekranu, zatem spodziewamy si, e Android przypnie lokacj do brzegu
i skonfiguruje odpowiedni flag brzegu. Nie zawsze tak si dzieje, zatem nie powinnimy zbytnio
polega na konfiguracji flag brzegowych. Klasa MotionEvent zawiera metod setEdgeFlags(),
dziki ktrej moemy samodzielnie wyznaczy takie flagi.
Naley jeszcze wspomnie, e nasza metoda onTouch() powraca z wartoci true, poniewa
kontrolka TrueButton zostaa zakodowana w taki sposb, aby przekazywana bya wanie ta
warto. Otrzymanie tej wartoci oznacza, e klasa MotionEvent zostaa przetworzona i nie ma
potrzeby przekazywa jej dalej. W ten sposb system zostaje rwnie poinformowany, eby
w dalszym cigu przesya zdarzenia tej sekwencji dotknicia do tej metody. To dlatego otrzymujemy komunikat o zdarzeniach ACTION_UP oraz ACTION_MOVE w przypadku rzeczywistego
urzdzenia.
Dotknijmy teraz przycisku przekazuje warto false, znajdujcego si w grnej czci ekranu.
Zademonstrujemy dla pozostaej czci podrozdziau jedynie przykadowy wynik z okna LogCat,
wygenerowany po uyciu rzeczywistego urzdzenia. Wyjanilimy wszystkie rnice, zatem
osoby pracujce na emulatorze powinny umie poprawnie zinterpretowa wyniki pojawiajce
si na ekranie. Listing 25.6 prezentuje wynikowe dane narzdzia LogCat dla dotknicia przycisku
przekazuje warto false.

870 Android 3. Tworzenie aplikacji


Listing 25.6. Przykadowe wpisy narzdzia LogCat powstae po dotkniciu grnego przycisku
przekazuje warto false
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
falseBtnTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop
trueLayoutTop

----------------------------Umieszczono widok falseBtnTop w metodzie onTouch


Dziaanie: 0
Lokacja: 61.309372 x 44.281494
Flagi krawdzi: 0
Sia nacisku: 0.0627451 Rozmiar: 0.26666668
Czas dotknicia: 28612178ms
Czas zdarzenia: 28612178ms Szacowany: 0 ms
i przekazuj warto false
----------------------------------Dziaanie: 0
Lokacja: 61.309372 x 44.281494
Flagi krawdzi: 0
Sia nacisku: 0.0627451 Rozmiar: 0.26666668
Czas dotknicia: 28612178ms
Czas zdarzenia: 28612178ms Szacowany: 0 ms
super onTouchEvent() przekazuje warto true
i przekazuj warto false
----------------------------Umieszczono widok trueLayoutTop w metodzie onTouch
Dziaanie: 0
Lokacja: 61.309372 x 116.281494
Flagi krawdzi: 0
Sia nacisku: 0.0627451 Rozmiar: 0.26666668
Czas dotyku: 28612178ms
Czas zdarzenia: 28612178ms Szacowany: 0 ms
i przekazuj warto true
----------------------------Umieszczono widok trueLayoutTop w metodzie onTouch
Dziaanie: 2
Lokacja: 61.309372 x 111.90039
Flagi krawdzi: 0
Sia nacisku: 0.0627451 Rozmiar: 0.26666668
Czas dotyku: 28612178ms
Czas zdarzenia: 28612217ms Szacowany: 39 ms
i przekazuj warto true
----------------------------Umieszczono widok trueLayoutTop w metodzie onTouch
Dziaanie: 1
Lokacja: 55.08958 x 115.30792
Flagi krawdzi: 0
Sia nacisku: 0.0627451 Rozmiar: 0.26666668
Czas dotknicia: 28612178ms
Czas zdarzenia: 28612361ms Szacowany: 183 ms
i przekazuj warto true

Widzimy tutaj zupenie odmienne zachowanie, wyjanijmy zatem, co tu zaszo. Android odbiera
zdarzenie ACTION_DOWN w obiekcie MotionEvent i przekazuje je naszej metodzie onTouch() klasy
MainActivity. Metoda ta rejestruje informacj w oknie LogCat i powraca z wartoci false.
Android dowiaduje si w ten sposb, e zdarzenie nie zostao przetworzone, zatem wyszukuje kolejn metod do wywoania, ktr w naszym wypadku okazuje si przesonita metoda
onTouchEvent() klasy FalseButton. Poniewa klasa ta jest rozszerzeniem klasy BooleanButton,

Rozdzia 25 Ekrany dotykowe

871

kod metody onTouchEvent() znajdziemy w pliku BooleanButton.java. W metodzie onTouch


Event() ponownie zapisujemy informacj w oknie LogCat, wywoujemy nadrzdn klas tej
metody, a nastpnie znowu otrzymujemy warto false. Zwrmy uwag, e zapisana w dzienniku informacja o pooeniu jest taka sama jak poprzednio. Naleao si tego spodziewa, poniewa
w tym momencie jest nadal przetwarzany ten sam obiekt klasy View, w widoku FalseButton.
Widzimy, e nadrzdna klasa przekae warto true z metody onTouchEvent(), i znamy tego
powd. Jeeli spojrzymy na ten przycisk w interfejsie UI, powinnimy dostrzec zmian jego
koloru w stosunku do przycisku przekazuje warto true. Przycisk przekazuje warto false
wyglda, jakby zosta czciowo wcinity, tj. zachowuje si jak przycisk, ktry nacinito, lecz
go nie puszczono. Nasza niestandardowa metoda powrcia z wartoci false zamiast z wartoci true. Poniewa kolejny raz poinformowalimy w ten sposb system, e zdarzenie nie zostao obsuone, Android nie wyle zdarzenia ACTION_UP do przycisku, co jest rwnoznaczne z pominiciem informacji o zdjciu palca z wywietlacza. Zatem przycisk ten znajduje si
cay czas w stanie nacinicia. Gdybymy zgodnie z dziaaniem klasy nadrzdnej przekazali
warto true, otrzymalibymy w kocu zdarzenie ACTION_UP, a przycisk odzyskaby domylny
kolor. Reasumujc: za kadym razem, gdy otrzymujemy warto false z elementu interfejsu
UI dla otrzymanego obiektu MotionEvent, Android przestaje wysya obiekty MotionEvent do
tego elementu interfejsu UI i rozpoczyna wyszukiwanie kolejnego skadnika interfejsu uytkownika, ktry obsuy element MotionEvent.
By moe Czytelnik zwrci uwag, e przycisk przekazuje warto true nie zmieni koloru po
jego dotkniciu. Dlaczego tak si stao? Poniewa metoda onTouch() zostaa wywoana przed
metodami obsugi przycisku, a e metoda ta przekazaa warto true, system nie zadecydowa
o wywoaniu metody onTouchEvent() kontrolki przekazuje warto true. Jeeli tu przed otrzymaniem wartoci true z metody onTouch() dodamy wiersz v.onTouchEvent(event);, kolor
przycisku ulegnie zmianie. Pojawi si take wicej informacji w dzienniku LogCat, poniewa
metoda onTouchEvent() zacznie wywietla w nim komunikaty.
Wrmy do wynikw w oknie LogCat. Android dwukrotnie bez skutku prbowa znale odbiorc dla zdarzenia ACTION_DOWN, wic teraz przechodzi do nastpnego widoku View (w naszym przypadku bdzie to ukad graficzny stanowicy pojemnik przycisku), ktry potencjalnie
mgby odebra to zdarzenie. Ten ukad graficzny nosi nazw trueLayoutTop i zobaczylimy, e
odebra to zdarzenie.
Zwrmy uwag, e nasza metoda onTouch() zostaa wywoana ponownie, teraz jednak
z widokiem ukadu graficznego, a nie z widokiem przycisku. Wszystkie informacje dotyczce
obiektu MotionEvent zostay przekazane metodzie onTouch() klasy trueLayoutTop w niezmienionej postaci, nawet informacje o czasie, oprcz wsprzdnej Y lokacji. Zmienia ona warto
ze wsprzdnej 44.281494 przycisku na 116.281494 ukadu graficznego. Jest to logiczne rozwizanie, poniewa przycisk nie znajduje si w lewym grnym rogu ukadu graficznego, jest
natomiast umieszczony poniej przycisku przekazuje warto true. Zatem warto wsprzdnej
Y dotyku dla ukadu graficznego jest wiksza od wartoci takiego samego dotyku w odniesieniu
do przycisku; dotykamy ekranu w wikszej odlegoci od grnej krawdzi ukadu graficznego
ni od grnej krawdzi przycisku. Poniewa metoda onTouch() klasy trueLayoutTop przekazuje
warto true, Android przesya pozostae zdarzenia do ukadu graficznego i widzimy wpisy
w dzienniku odpowiadajce zdarzeniom ACTION_MOVE i ACTION_UP. Dotknijmy teraz przycisku
przekazuje warto false, a zostanie wywietlony ten sam zbir komunikatw dziennika, to znaczy
zostanie wywoana metoda onTouch() dla klasy falseBtnTop, metoda onTouchEvent() dla
klasy falseBtnTop, a nastpnie, w przypadku pozostaych zdarze, metoda onTouch() dla klasy
trueLayoutTop. Android zaprzestaje jedynie wysyania do przycisku zdarze odpowiedzialnych

872 Android 3. Tworzenie aplikacji


za sekwencje pojedynczego dotknicia. W przypadku nowej sekwencji dotyku Android bdzie j
wysya do przycisku, dopki wywoana metoda nie przekae znowu wartoci false, co w naszym przykadzie jest jej zaprogramowanym zadaniem.
Dotknijmy teraz obszaru w grnym ukadzie graficznym, lecz nie dotykajmy przycisku, a nastpnie przesumy palec po ekranie i oderwijmy go od wywietlacza (na emulatorze wystarczy
wykona podobny ruch mysz). Zwrmy uwag na strumie komunikatw dziennika, gdzie
pierwszym zarejestrowanym dziaaniem jest ACTION_DOWN, nastpnie pojawia si wiele zdarze
ACTION_MOVE, a wpisy zamyka zdarzenie ACTION_UP.
Dotknijmy teraz przycisku przekazuje warto true, teraz jednak poprzesuwajmy palcem po
wywietlaczu i oderwijmy go od ekranu. Listing 25.7 przedstawia nowe informacje, ktre
pojawiy si w oknie LogCat.
Listing 25.7. Rejestry dziennika LogCat dotyczce zdarzenia dotyku wykraczajcego poza widok
[ wpisy dziennika dotyczce zdarzenia ACTION_DOWN oraz zdarze ACTION_MOVE ]
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop

Umieszczono widok trueBtnTop w metodzie onTouch


Dziaanie: 2
Lokacja: 150.41768 x 22.628128
>>> Zdarzenie dotyku opucio widok <<<
Flagi krawdzi: 0
Sia nacisku: 0.047058824 Rozmiar: 0.13333334
Czas dotyku: 31690859ms
Czas zdarzenia: 31691344ms Szacowany: 485 ms
i przekazuj warto true

[ wicej komunikatw dotyczcych zdarze ACTION_MOVE ]


trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop
trueBtnTop

Umieszczono widok trueBtnTop w metodzie onTouch


Dziaanie: 1
Lokacja: 291.5864 x 223.43854
>>> Zdarzenie dotyku opucio widok <<<
Flagi krawdzi: 0
Sia nacisku: 0.047058824 Rozmiar: 0.13333334
Czas dotyku: 31690859ms
Czas zdarzenia: 31692493ms Szacowany: 1634 ms
i przekazuj warto true

Nawet jeeli zdejmiemy palec z przycisku, bdziemy otrzymywa zwizane z nim powiadomienia o zdarzeniach dotyku. Pierwszy rejestr na listingu 25.7 prezentuje wpis zdarzenia zachodzcego poza obrbem przycisku. W naszym przypadku wsprzdna X zdarzenia znajduje si z prawej strony od krawdzi przycisku. Cigle jednak wystpuj wywoania obiektu
MotionEvent, a do momentu wystpienia zdarzenia ACTION_UP, poniewa cigle otrzymujemy
warto true z metody onTouch(). Nawet jeli w kocu oderwiemy palec od ekranu dotykowego
i nie bdzie si on znajdowa na przycisku, metoda onTouch() bdzie cigle wywoywana w celu
przekazywania zdarzenia ACTION_UP, poniewa nieprzerwanie jest przekazywana warto true.
Naley o tym pamita podczas korzystania z obiektw MotionEvent. Po przesuniciu palca poza
widok moemy zadecydowa o anulowaniu wykonywanej czynnoci i otrzyma warto false
z metody onTouch(), dziki czemu nie bdzie powiadamiana o nastpnych zdarzeniach.
Ewentualnie moemy pozwoli na dalsze otrzymywanie zdarze (poprzez przekazanie wartoci
true z metody onTouch()) i wykonywanie operacji jedynie po powrocie palca do widoku.

Rozdzia 25 Ekrany dotykowe

873

Sekwencja zdarze dotyku zostaa powizana z grnym przyciskiem przekazuje warto true po
otrzymaniu wartoci true z metody onTouch(). W ten sposb Android zosta poinformowany,
e moe przesta szuka elementu odbierajcego obiekty MotionEvent i wysya nam wszystkie
nastpne obiekty MotionEvent zwizane z t sekwencj dotyku. Nawet jeli natrafimy palcem na
inny widok, sekwencja dotyku cigle bdzie dotyczya pierwotnego widoku.
Zobaczmy, co si dzieje z doln poow aplikacji. Dotknijmy umieszczonego tam przycisku
przekazuje warto true. Widzimy, e nastpuje takie same zjawisko jak w przypadku dotknicia
grnego przycisku przekazuje warto true. Poniewa metoda onTouch() powraca z wartoci
true, Android bdzie przesya reszt zdarze w sekwencji dotyku, dopki palec styka si
z wywietlaczem. Teraz dotknijmy dolnego przycisku przekazuje warto false. I znowu metody
onTouch() oraz onTouchEvent() powracaj z wartociami false (obydwie s zwizane z kontrolk falseBtnBottom). Jednak tym razem nastpnym widokiem, ktry otrzyma obiekt Motion
Event, jest ukad graficzny falseLayoutBottom, rwnie przekazujcy warto false. I to tyle.
Poniewa metoda onTouchEvent() wywoaa metod onTouchEvent() klasy bazowej, przycisk zmieni kolor, wskazujc tym samym, e zosta czciowo wcinity. Pozostanie on jednak
w tym stanie, poniewa nigdy nie otrzymamy zdarzenia ACTION_UP w tej sekwencji, gdy nasze
metody cay czas zwracaj warto false. W przeciwiestwie do poprzedniego przykadu, nawet
ukad graficzny nie reaguje na takie zdarzenie. Gdybymy dotknli przycisku przekazuje warto false i przytrzymali go, a nastpnie poprzesuwali palec po ekranie, nie zobaczylibymy adnego wpisu w oknie LogCat, poniewa obiekty MotionEvent nie s wysyane. Zawsze przekazywana jest warto false, system nie bdzie wic informowa o zdarzeniach z tej sekwencji
dotyku. Jeeli rozpoczniemy now sekwencj dotyku, ujrzymy rejestry pojawiajce si w narzdziu LogCat. Jeeli zainicjalizujemy tak sekwencj w dolnym ukadzie graficznym, lecz nie na
obszarze przycisku, zostanie zarejestrowane pojedyncze zdarzenie w obiekcie falseLayoutBottom,
przekazujce warto false, i nic poza tym (dopki nie rozpoczniemy nowej sekwencji dotyku).
Dotychczas wykorzystywalimy przyciski do ukazania efektw zdarze MotionEvent zwizanych z ekranem dotykowym. Warto zauway, e w normalnej sytuacji zaimplementowalibymy operacje na przyciskach za pomoc metody onClick(). Wybralimy do tego przykadu
przyciski, poniewa nie s trudne do utworzenia oraz s klasami podrzdnymi w stosunku do
klasy View, mog wic odbiera zdarzenia dotyku tam samo jak inne widoki. Pamitajmy, e
omwione techniki odnosz si do dowolnego widoku View w aplikacji, bez wzgldu na to, czy
mamy do czynienia ze standardow, czy niestandardow klas widoku.

Wielokrotne wykorzystywanie obiektw MotionEvent


Podczas przegldania dokumentacji klasy MotionEvent moemy natrafi na metod recycle().
Wielokrotne wykorzystywanie obiektw MotionEvent otrzymywanych w metodach onTouch()
lub onTouchEvent() moe by kuszcym pomysem, nie powinnimy jednak tego robi. Jeeli
metoda zwrotna nie obsuy obiektu MotionEvent i powrci z wartoci false, prawdopodobnie obiekt ten zostanie przekazany innej metodzie, widokowi lub aktywnoci. Z tego wynika, e
nie naley go jeszcze przeznacza do ponownego wykorzystania. Nawet jeli to zdarzenie zostao
obsuone i otrzymalimy warto true, nie powinnimy go przetwarza w ten sposb chodzi
o kwestie przynalenoci tego obiektu.
Jeeli przyjrzymy si obiektowi MotionEvent, zauwaymy kilka odmian metody obtain().
Suy ona albo do tworzenia kopii obiektu MotionEvent, albo do tworzenia jego zupenie nowego
wystpienia. To wanie taki nowy obiekt lub kopia obiektu mog zosta ponownie wykorzystane.
Jeeli na przykad nie chcemy pozbywa si obiektu, ktry zosta przekazany za pomoc metody

874 Android 3. Tworzenie aplikacji


zwrotnej, powinnimy przy uyciu metody obtain() utworzy jego kopi, poniewa po wyjciu
z tej metody obiekt ten zostanie ponownie wykorzystany, co moe da nieoczekiwane rezultaty.
Gdy skoczymy korzysta z kopii obiektu, wywoujemy wobec niej metod recycle().

Stosowanie klasy VelocityTracker


Android zawiera klas VelocityTracker, ktra obsuguje sekwencj dotyku. Dobrze byoby
zna prdko palca poruszajcego si po powierzchni ekranu. Jeli na przykad uytkownik
szybko przesuwa palec po ekranie, moe to oznacza wykonanie gestu przerzucania (ang. fling),
ktry aplikacja przetwarza na odpowiedni operacj. Klasa VelocityTracker dokonuje odpowiednich oblicze.
Aby skorzysta z tej klasy, musimy najpierw utworzy jej instancj poprzez wywoanie statycznej
metody VelocityTracker.obtain(). Nastpnie moemy doda do niej obiekty MotionEvent
wraz z metod addMovement(MotionEvent ev). Metoda ta byaby wywoywana przez procedur obsugi obiektw MotionEvent, otrzymywanych od takich metod, jak onTouch() lub
onTouchEvent(). Klasa VelocityTracker wykorzystuje obiekty MotionEvent do okrelenia
zachowania sekwencji dotyku. Po otrzymaniu przez klas VelocityTracker przynajmniej
dwch obiektw MotionEvent moemy zastosowa inne metody do okrelenia sytuacji.
Dwie metody klasy VelocityTracker getXVelocity() i getYVelocity() przekazuj wyliczon prdko palca, odpowiednio, w kierunkach X i Y. Warto przekazana przez te dwie
metody bdzie symbolizowaa liczb pikseli w jednostce czasu. Mog to by piksele na milisekund,
piksele na sekund lub dowolna inna jednostka. Aby zdefiniowa jednostk czasu w klasie
VelocityTracker, zanim zostan wywoane dwie metody typu getter, musimy wywoa jej metod
computeCurrentVelocity(int units). Warto atrybutu units reprezentuje liczb milisekund
w czasie mierzenia prdkoci. Jeeli chcemy, aby bya mierzona liczba pikseli na milisekund,
wstawiamy warto jednostki rwn 1; w przypadku chci mierzenia liczby pikseli w sekundzie
wpisujemy warto 1000. Warto zwrcona przez metody getXVelocity() i getYVelocity()
bdzie dodatnia, jeli wektor prdkoci zostanie skierowany w prawo (dla kierunku X) lub w d
(dla kierunku Y). Wartoci bd ujemne dla prdkoci skierowanej w lewo lub do gry.
Po otrzymaniu obiektu klasy VelocityTracker wraz z metod obtain() wywoujemy metod
recycle(). Na listingu 25.8 umieszczono przykadow procedur obsugi metody onTouch
Event() dla aktywnoci. Okazuje si, e aktywno posiada metod zwrotn onTouchEvent(),
ktra jest wywoywana za kadym razem, gdy aden widok nie przetworzy zdarzenia dotknicia.
Poniewa korzystamy ze standardowego, pustego ukadu graficznego, aden widok nie obsuguje zdarze dotknicia.
Listing 25.8. Przykadowa aktywno wykorzystujca klas VelocityTracker
import
import
import
import
import

android.app.Activity;
android.os.Bundle;
android.util.Log;
android.view.MotionEvent;
android.view.VelocityTracker;

public class MainActivity extends Activity {


private static final String TAG = "VelocityTracker";

/** Wywoywana podczas pierwszego utworzenia aktywnoci. */


@Override

Rozdzia 25 Ekrany dotykowe

875

public void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
private VelocityTracker vTracker = null;
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch(action) {
case MotionEvent.ACTION_DOWN:
if(vTracker == null) {
vTracker = VelocityTracker.obtain();
}
else {
vTracker.clear();
}
vTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
vTracker.addMovement(event);
vTracker.computeCurrentVelocity(1000);
Log.v(TAG, "Predkosc w osi X wynosi " + vTracker.getXVelocity() +
" pikseli na sekunde");
Log.v(TAG, "Predkosc w osi Y wynosi " + vTracker.getYVelocity() +
" pikseli na sekunde");
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
vTracker.recycle();
break;
}
return true;
}
}

Czytelnikowi naley si jeszcze kilka najwaniejszych uwag na temat klasy VelocityTracker.


Oczywicie w przypadku dodania tylko jednego obiektu MotionEvent do klasy VelocityTracker
(na przykad zdarzenia ACTION_DOWN) warto obliczonej prdkoci bdzie zawsze rwna 0.
Musimy doda jednak punkt pocztkowy, aby poczwszy od niego moga zosta obliczona prdko dla kolejnych zdarze ACTION_MOVE. Okazuje si, e prdkoci zgoszone po zdarzeniu ACTION_UP rwnie maj zdefiniowan warto 0. Zatem nie spodziewajmy si, e
prdkoci w kierunkach X i Y odczytane po dodaniu zdarzenia ACTION_UP bd reprezentoway
ruch. Jeeli na przykad tworzymy gr, w ktrej uytkownik rzuca obiekt na ekranie, powinnimy wykorzysta prdkoci zebrane od momentu ostatniego zdarzenia ACTION_MOVE
do obliczenia trajektorii lotu obiektu w widoku gry.
Klasa VelocityTracker wymaga duej iloci zasobw, zatem naley rozsdnie z niej korzysta.
Powinnimy si take upewni, e klasa ta bdzie moga by wielokrotnie wykorzystywana,
jeli bdzie potrzebna innym aplikacjom. W Androidzie moe by uruchomionych kilka
klas VelocityTracker, jednak pochaniaj one wiele pamici. Kiedy wic utworzona klasa
VelocityTracker przestanie by potrzebna, naleaoby j zwolni. Na listingu 25.8, zamiast
ponownie wykorzystywa star sekwencj obsugi dotyku, stosujemy metod clear() do uruchomienia nowej sekwencji (jeli na przykad generujemy nowe zdarzenie ACTION_DOWN, a obiekt
VelocityTracker ju istnieje).

876 Android 3. Tworzenie aplikacji

Analiza funkcji przecigania


Skoro ju Czytelnik pozna kod sucy do odbierania obiektw MotionEvent, warto wykorzysta t wiedz w ciekawy sposb. Wyjanimy sposb implementacji funkcji przecigania obiektw. Rozpocznijmy od procesu przesuwania. W nastpnej przykadowej aplikacji utworzymy
bia kropk i przecigniemy j do innego miejsca w ukadzie graficznym. Za pomoc kodu
pokazanego na listingu 25.9 utwrzmy nowy projekt i skonfigurujmy plik XML ukadu graficznego, a take dodajmy now klas, nazwan Dot. Pamitajmy, e nazwa pakietu w pliku XML
ukadu graficznego dla elementu Dot musi dokadnie pasowa do nazwy pakietu stosowanego
dla tej aplikacji. Zwrmy take uwag, e moemy pozostawi gwn klas Activity bez
zmian, gdy nie musimy jej modyfikowa. Interfejs UI tej aplikacji zosta zaprezentowany na
rysunku 25.2.
Listing 25.9. Przykadowy plik XML ukadu graficznego i kod Java przykadowej aplikacji sucej
do przecigania obiektw
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<com.androidbook.touch.dragdemo1.Dot
android:id="@+id/dot"
android:tag="trueDot"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

import
import
import
import
import
import
import

android.content.Context;
android.graphics.Canvas;
android.graphics.Color;
android.graphics.Paint;
android.util.AttributeSet;
android.view.MotionEvent;
android.view.View;

public class Dot extends View {


private static final float RADIUS = 20;
private float x = 30;
private float y = 30;
private float initialX;
private float initialY;
private float offsetX;
private float offsetY;
private Paint backgroundPaint;
private Paint myPaint;
public Dot(Context context, AttributeSet attrs) {
super(context, attrs);

Rozdzia 25 Ekrany dotykowe

877

backgroundPaint = new Paint();


backgroundPaint.setColor(Color.BLUE);
myPaint = new Paint();
myPaint.setColor(Color.WHITE);
myPaint.setAntiAlias(true);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch(action) {
case MotionEvent.ACTION_DOWN:

// Musi zapamita pooenie rodka punktu startowego


// naszego obiektu Dot oraz miejsce, w ktrym nastpuje dotknicie
initialX = x;
initialY = y;
offsetX = event.getX();
offsetY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
x = initialX + event.getX() - offsetX;
y = initialY + event.getY() - offsetY;
break;
}
return(true);
}
@Override
public void draw(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
canvas.drawRect(0, 0, width, height, backgroundPaint);
canvas.drawCircle(x, y, RADIUS, myPaint);
invalidate();
}
}

Po uruchomieniu tej aplikacji ujrzymy bia kropk na niebieskim tle. Moemy j dotkn
i przesuwa po obszarze ekranu. Po oderwaniu palca od ekranu kropka nie wraca na pozycj
pocztkow i jest gotowa do dalszego przesuwania. Znacznie uprocilimy cay proces, aby
zaprezentowa jedynie podstawy przesuwania obiektu po ekranie. Metoda draw() umieszcza
kropk w biecej pozycji X i Y. Dziki obiektom MotionEvent przekazywanym w metodzie
onTouchEvent() moemy w miar przesuwania palca po ekranie modyfikowa wsprzdne
X i Y. Rejestrujemy pooenie pocztkowe kropki oraz pocztkowy punkt dotknicia ekranu
w momencie otrzymania zdarzenia ACTION_DOWN. Poniewa uytkownik nie zawsze dotyka
rodka obiektu, wsprzdne dotyku nie s tosame z wsprzdnymi pooenia obiektu.
Musimy rwnie wzi pod uwag przypadek, gdy punktem odniesienia dla naszego obiektu
jest nie rodek, a lewy grny rg ekranu.

878 Android 3. Tworzenie aplikacji

Rysunek 25.2. Interfejs uytkownika aplikacji demonstrujcej zjawisko przesuwania obiektu

Kiedy palec porusza si po ekranie, dopasowujemy pooenie obiektu poprzez obliczanie


rnic wsprzdnych X i Y na podstawie otrzymywanych zdarze MotionEvent. Po zakoczeniu ruchu (na przykad z powodu wywoania dziaania ACTION_UP) definiujemy kocowe pooenie obiektu, stanowice ostatnie wsprzdne dotyku. Troszeczk tu oszukujemy, poniewa
widok Dot jest umieszczony na ekranie wzgldem punktu (0, 0). Oznacza to, e moemy po
prostu narysowa kropk zalen od punktu (0, 0), w przeciwiestwie do innego punktu referencyjnego. Gdyby nasz obiekt nie odnosi si do punktu (0, 0), musielibymy wstawi dodatkowe pozycje dla pooenia tego obiektu. Nie musimy si take przejmowa w naszym przykadzie paskami przewijania, ktre skomplikowayby obliczenia pooenia obiektu na ekranie.
Podstawowa zasada jest jednak zawsze taka sama. Znajc pocztkow pozycj przesuwanego
obiektu oraz ledzc zmiany wartoci dotyku, poczwszy od zdarzenia ACTION_DOWN do zdarzenia
ACTION_UP, moemy dokadnie ustali pooenie obiektu na ekranie.
Upuszczanie obiektu na inny obiekt wymaga jedynie znajomoci pooenia elementw na
ekranie. Nie zaprezentujemy przykadu upuszczania obiektu, wyjanimy jednak zasady dziaania
tego mechanizmu. Jak widzielimy wczeniej, w czasie przesuwania obiektu po ekranie znamy
jego relatywne pooenie w stosunku do jednego lub kilku punktw na ekranie. Moemy rwnie
pozna rozmiary i pooenie innych elementw. Majc te dane, moemy okreli, czy przenoszony
obiekt znajduje si ponad innym obiektem. Zazwyczaj stosowanym algorytmem jest iterowanie
po dostpnych obiektach, nad ktrymi moemy umieci nasz element, oraz sprawdzanie, czy
biece pooenie przesuwanego obiektu pokrywa si z pooeniem obiektu znajdujcego si
pod spodem. W tym celu mog zosta wykorzystane rozmiar i pooenie kadego obiektu
(czasami rwnie ksztat). Jeeli otrzymamy zdarzenie ACTION_UP, oznaczajce, e uytkownik
upuci przenoszony element, oraz obiekt ten znajduje si nad innym elementem, musz zosta
uruchomione algorytmy przetwarzajce dziaanie upuszczenia. Moe to by na przykad proces
przenoszenia obiektu do kosza, z ktrego umieszczony obiekt moe zosta usunity, albo proces
kopiowania lub przenoszenia pliku do folderu.
W wersji 3.0 Androida (Honeycomb) zostaa wprowadzona bezporednia obsuga
funkcji przecigania. Zostaa ona omwiona w rozdziale 31.

Rozdzia 25 Ekrany dotykowe

879

Wielodotykowo
Po omwieniu obsugi ekranu za pomoc jednego palca moemy przej do kwestii wielodotykowoci. Technologia wielodotykowoci zyskaa olbrzymie zainteresowanie od czasu konferencji
TED w 2006 roku, w czasie ktrej Jeff Han zademonstrowa wielodotykow powierzchni dla
komputerowego interfejsu UI. Uywanie wielu palcw na ekranie otwiera mnstwo moliwoci
manipulowania jego zawartoci. Na przykad rozsunicie dwch palcw na pliku graficznym
moe skutkowa powikszeniem obrazu. Przyoenie kilku palcw na takim obrazie i ich obrt
zgodnie z ruchem wskazwek zegara moe obrci ten rysunek. Obsuga wielodotykowoci zostaa wprowadzona w wersji 2.0 zestawu Android SDK. Pojawia si moliwo wykorzystywania maksymalnie trzech palcw jednoczenie (z technicznego punktu widzenia) do wykonywania
takich czynnoci, jak przyblianie, obracanie oraz wszelkie inne operacje, podczas ktrych korzysta si z technologii wielodotykowoci. Piszemy: z technicznego punktu widzenia, poniewa pierwsze urzdzenia wykorzystujce funkcj wielodotykowoci pozwalay wycznie na obsug dwch palcw. Jednak po duszym zastanowieniu mona stwierdzi, e nie ma w tym
adnej magii. Jeeli elektronika urzdzenia pozwala na wykrywanie wielodotykowoci oraz informuje aplikacj o przesuwaniu palcw po ekranie i o ich zdjciu z wywietlacza, aplikacja ta
moe odczyta, co uytkownik chcia przekaza tym gestem. Mimo e nie ma tu adnej magii,
technologia ta nie naley do najprostszych wynalazkw. W tym podrozdziale sprbujemy pomc
w zrozumieniu idei wielodotykowoci.
W wersji 2.2 Androida wprowadzono w klasie MotionEvent pewne zmiany,
jeszcze bardziej komplikujce ju i tak zoony mechanizm wielodotykowoci,
w tym rwnie przedawnienie dwch omawianych w tym podrozdziale staych
(ACTION_POINTER_ID_MASK oraz ACTION_POINTER_ID_SHIFT). Oznacza to,
e w przypadku urzdze starszych generacji bdziemy stosowa mechanizmy
omwione poniej, natomiast sposb wprowadzania funkcji wielodotykowoci
w urzdzeniach posiadajcych wersj 2.2 Androida lub nowsze zostanie wyjaniony
w dalszej czci podrozdziau.

Funkcja wielodotykowoci przed wersj 2.2 Androida


Podstawy wielodotykowoci s dokadnie takie same jak w przypadku pojedynczego dotyku.
Tak jak poprzednio, po wykryciu zdarzenia dotknicia s tworzone obiekty MotionEvent, a nastpnie obiekty te s przekazywane do odpowiednich metod. Program moe przeczyta informacje o dotkniciach i zadecydowa, jak je zinterpretowa. Na podstawowym poziomie
metody klasy MotionEvent s identyczne: to znaczy wywoujemy metody getAction(),
getDownTime(), getX() i tak dalej. Jednak w przypadku dotknicia przez liczb palcw wiksz
od jednego obiekt MotionEvent musi zawrze informacje o dotkniciach wszystkich palcw
wraz z odpowiednim wyjanieniem. Warto dziaania z metody getAction() jest przeznaczona dla obsugi dotknicia jednego palca, nie dla wszystkich. Warto getDownTime() jest
definiowana przez dotknicie powierzchni przez pierwszy palec i w przypadku tej metody czas
jest mierzony, dopki przynajmniej jeden z palcw dotyka ekranu. Wartoci pooenia z metod
getX() oraz getY(), a take metody getPressure() i getSize() mog pobiera argumenty
dla danego palca; musimy zatem skorzysta z jakiej wartoci indeksu, aby mc rozrnia dotyk
poszczeglnych palcw. Mamy do dyspozycji wczeniej opisane wywoania metod, ktre nie
pobieraj adnych argumentw identyfikujcych palec (na przykad wywoania metod getX()
i getY()), zatem dotknicie ktrego palca bdzie rdem wartoci dla tych metod? Mona to

880 Android 3. Tworzenie aplikacji


odkry samemu, jest to jednak do czasochonne zajcie. Dlatego jeeli nie bierzemy cay czas
pod uwag wielodotykowoci, moemy uzyska dziwne rezultaty. Przeanalizujemy dokadnie
ten temat, eby szczegowo wyjani, co naley robi.
Podstawow metod klasy MotionEvent wykorzystywan w przypadku obsugi wielodotykowoci jest getPointerCount(). Zostaje w niej zdefiniowana liczba palcw reprezentowanych
w obiekcie MotionEvent, nie musi ona jednak informowa o faktycznej liczbie palcw dotykajcych ekran wielodotykowy jest to zalene od sprztu oraz od wersji systemu Android.
Moe si zdarzy, e w pewnych urzdzeniach metoda getPointerCount() nie bdzie zgaszaa
wszystkich palcw dotykajcych ekranu, a tylko niektre. Idmy jednak dalej. Jak tylko zostanie
zgoszona wiksza liczba palcw w obiektach MotionEvent, musimy si zaj obsug indeksu
oraz identyfikatorw wskanika.
Klasa MotionEvent przechowuje informacje dla wskanikw, poczwszy od indeksu 0 do wartoci reprezentujcej liczb palcw zgoszonych do tego obiektu. Indeks wskanika zawsze
rozpoczyna si od wartoci 0; jeeli zostay zgoszone trzy palce, indeksy ich wskanika przybior
wartoci 0, 1 i 2. Wywoania takich metod jak getX() musz zawiera warto indeksu wskanika palca, o ktrym chcemy uzyska informacje. Identyfikatory wskanika stanowi wartoci
typu cakowitego, wskazujce obserwowany palec. Identyfikator wskanika posiada warto 0
dla pierwszego palca, ktry dotkn ekranu, jednak nie zawsze jest rozpoczynany od tej wartoci
w przypadku palcw naprzemiennie przytykanych i odrywanych od ekranu. Uznajmy identyfikator wskanika za nazw palca ledzonego przez system. Na przykad wyobramy sobie dwuetapow sekwencj dotyku, wykonywan przez dwa palce w nastpujcej kolejnoci: przykadamy do ekranu palec 1, nastpnie palec 2, odrywamy palec 1 i po nim podnosimy palec 2.
Pierwszy przyoony palec otrzyma identyfikator od wartoci 0. Drugi przyoony palec
otrzymuje identyfikator 1. Po podniesieniu pierwszego palca drugi palec nadal bdzie posiada
identyfikator 1. W tym momencie indeks wskanika dla palca 2 uzyska warto 0, poniewa indeks ten zawsze rozpoczyna si od wartoci 0. W omawianym przykadzie drugi palec
(identyfikator wskanika 1) rozpoczyna jako indeks wskanika 1, gdy dotknie ekranu jako
pierwszy, a nastpnie przeskakuje do indeksu wskanika 0 po podniesieniu pierwszego palca. Jednak nawet jeli wycznie drugi palec pozostanie na powierzchni ekranu, bdzie nadal
zdefiniowany identyfikatorem o wartoci 1. Nasze aplikacje bd korzystay z identyfikatorw
wskanika do powizania zdarze z okrelonym palcem, nawet jeeli w tym czasie ekranu
dotykaj take inne palce. Spjrzmy na przykad.
Listing 25.10 przedstawia nowy plik XML ukadu graficznego oraz kod Java dla aplikacji
obsugujcej funkcj wielodotykowoci. Utwrzmy nowy projekt, posikujc si informacjami
zawartymi na listingu 25.10, i uruchommy go. Rysunek 25.3 stanowi ilustracj interfejsu uytkownika tej aplikacji.
Listing 25.10. Plik XML ukadu graficznego i kod Java aplikacji demonstrujcej technologi
wielodotykowoci
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout1"
android:tag="trueLayout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
>

Rozdzia 25 Ekrany dotykowe

881

<TextView android:text="Dotknij palcami ekranu i spjrz na zawarto okna LogCat"


android:id="@+id/message"
android:tag="trueText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
</RelativeLayout>

// Jest to plik MainActivity.java


import
import
import
import
import
import
import

android.app.Activity;
android.os.Bundle;
android.util.Log;
android.view.MotionEvent;
android.view.View;
android.view.View.OnTouchListener;
android.widget.RelativeLayout;

public class MainActivity extends Activity implements OnTouchListener {

/** Wywoywane podczas pierwszego utworzenia aktywnoci. */


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
RelativeLayout layout1 = (RelativeLayout) findViewById(R.id.layout1);
layout1.setOnTouchListener(this);
}
public boolean onTouch(View v, MotionEvent event) {
String myTag = v.getTag().toString();
Log.v(myTag, "-----------------------------");
Log.v(myTag, "Umieszczono widok " + myTag + " w metodzie onTouch");
Log.v(myTag, describeEvent(event));
logAction(event);
if( "true".equals(myTag.substring(0, 4))) {
return true;
}
else {
return false;
}
}
protected static String describeEvent(MotionEvent event) {
StringBuilder result = new StringBuilder(500);
result.append("Dziaanie: ").append(event.getAction()).append("\n");
int numPointers = event.getPointerCount();
result.append("Ilo wskanikw: ")
result.append(numPointers).append("\n");
int ptrIdx = 0;
while (ptrIdx < numPointers) {
int ptrId = event.getPointerId(ptrIdx);
result.append("Indeks wskanika: ").append(ptrIdx);
result.append(", identyfikator wskanika: ").append(ptrId).append("\n");
result.append(" Lokacja: ").append(event.getX(ptrIdx));

882 Android 3. Tworzenie aplikacji


result.append(" x ").append(event.getY(ptrIdx)).append("\n");
result.append(" Sia nacisku: ")
result.append(event.getPressure(ptrIdx));
result.append(" Rozmiar: ").append(event.getSize(ptrIdx))
result.append("\n");
ptrIdx++;
}
result.append("Czas dotknicia: ").append(event.getDownTime())
result.append("ms\n").append("Czas zdarzenia: ");
result.append(event.getEventTime()).append("ms");
result.append(" Szacowany: ");
result.append(event.getEventTime()-event.getDownTime());
result.append(" ms\n");
return result.toString();
}
private void logAction(MotionEvent event) {
int action = event.getAction();
int ptrIndex = (action & MotionEvent.ACTION_POINTER_ID_MASK) >>>
MotionEvent.ACTION_POINTER_ID_SHIFT;
action = action & MotionEvent.ACTION_MASK;
if(action == 5 || action == 6)
action = action - 5;
int ptrId = event.getPointerId(ptrIndex);
Log.v("Action", "Indeks wskaznika: " + ptrIndex);
Log.v("Action", "Identyfikator wskaznika: " + ptrId);
Log.v("Action", "Prawdziwa wartosc dzialania: " + action);
}
}

Rysunek 25.3. Aplikacja demonstrujca zastosowanie wielodotykowoci

Rozdzia 25 Ekrany dotykowe

883

Aplikacja dziaa na emulatorze, nie ma jednak moliwoci symulowania dotknicia ekranu kilkoma palcami. W takim przypadku zostan wywietlone wyniki podobne do przedstawionych
w poprzednim przykadzie. Na listingu 25.11 przedstawiamy przykadowe komunikaty narzdzia LogCat dla opisanej kilka stron wczeniej sekwencji dotyku, to znaczy: najpierw palec 1
dotyka ekranu, nastpnie ekranu dotyka palec 2, potem palec 1 odrywa si od ekranu, wreszcie
to samo dzieje si z palcem 2.
Listing 25.11. Przykadowe wyniki narzdzia LogCat dla aplikacji wielodotykowej
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
Action
Action
Action
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
Action
Action
Action
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
Action
Action
Action
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout

----------------------------Uzyskano widok trueLayout w metodzie onTouch


Dziaanie: 0
Ilo wskanikw: 1
Indeks wskanika: 0, Id wskanika: 0
Pooenie: 114.88211 x 499.77502
Sia nacisku: 0.047058824 Rozmiar: 0.13333334
Czas dotknicia: 33733650ms
Czas zdarzenia: 33733650ms Szacowany: 0 ms
Indeks wskanika: 0
Identyfikator wskanika: 0
Prawdziwa warto dziaania: 0
----------------------------Uzyskano widok trueLayout w metodzie onTouch
Dziaanie: 2
Ilo wskanikw: 1
Indeks wskanika: 0, Id wskanika: 0
Pooenie: 114.88211 x 499.77502
Sia nacisku: 0.05882353 Rozmiar: 0.13333334
Czas dotknicia: 33733650ms
Czas zdarzenia: 33733740ms Szacowany: 90 ms
Indeks wskanika: 0
Identyfikator wskanika: 0
Prawdziwa warto dziaania: 2
----------------------------Uzyskano widok trueLayout w metodzie onTouch
Dziaanie: 261
Ilo wskanikw: 2
Indeks wskanika: 0, Id wskanika: 0
Pooenie: 114.88211 x 499.77502
Sia nacisku: 0.05882353 Rozmiar: 0.13333334
Indeks wskanika: 1, Id wskanika: 1
Pooenie: 320.30692 x 189.67395
Sia nacisku: 0.050980393 Rozmiar: 0.13333334
Czas dotknicia: 33733650ms
Czas zdarzenia: 33733962ms Szacowany: 312 ms
Indeks wskanika: 1
Identyfikator wskanika: 1
Prawdziwa warto dziaania: 0
----------------------------Uzyskano widok trueLayout w metodzie onTouch
Dziaanie: 2
Ilo wskanikw: 2
Indeks wskanika: 0, Id wskanika: 0
Pooenie: 111.474594 x 499.77502
Sia nacisku: 0.05882353 Rozmiar: 0.13333334
Indeks wskanika: 1, Id wskanika: 1

884 Android 3. Tworzenie aplikacji


trueLayout
trueLayout
trueLayout
trueLayout
Action
Action
Action
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
Action
Action
Action
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
Action
Action
Action
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
trueLayout
Action
Action
Action

Pooenie: 320.30692 x 189.67395


Sia nacisku: 0.050980393 Rozmiar: 0.13333334
Czas dotknicia: 33733650ms
Czas zdarzenia: 33734189ms Szacowany: 539 ms
Indeks wskanika: 0
Identyfikator wskanika: 0
Prawdziwa warto dziaania: 2
----------------------------Uzyskano widok trueLayout w metodzie onTouch
Dziaanie: 6
Ilo wskanikw: 2
Indeks wskanika: 0, Id wskanika: 0
Pooenie: 111.474594 x 499.77502
Sia nacisku: 0.05882353 Rozmiar: 0.13333334
Indeks wskanika: 1, Id wskanika: 1
Pooenie: 320.30692 x 189.67395
Sia nacisku: 0.050980393 Rozmiar: 0.13333334
Czas dotknicia: 33733650ms
Czas zdarzenia: 33734228ms Szacowany: 578 ms
Indeks wskanika: 0
Identyfikator wskanika: 0
Prawdziwa warto dziaania: 1
----------------------------Uzyskano widok trueLayout w metodzie onTouch
Dziaanie: 2
Ilo wskanikw: 1
Indeks wskanika: 0, Id wskanika: 1
Pooenie: 318.84656 x 191.45105
Sia nacisku: 0.050980393 Rozmiar: 0.13333334
Czas dotknicia: 33733650ms
Czas zdarzenia: 33734240ms Szacowany: 590 ms
Indeks wskanika: 0
Identyfikator wskanika: 1
Prawdziwa warto dziaania: 2
----------------------------Uzyskano widok trueLayout w metodzie onTouch
Dziaanie: 1
Ilo wskanikw: 1
Indeks wskanika: 0, Id wskanika: 1
Pooenie: 314.95224 x 190.5625
Sia nacisku: 0.050980393 Rozmiar: 0.13333334
Czas dotknicia: 33733650ms
Czas zdarzenia: 33734549ms Szacowany: 899 ms
Indeks wskanika: 0
Identyfikator wskanika: 1
Prawdziwa warto dziaania: 1

Zastanwmy si, co si dzieje w tej aplikacji. Pierwszym zdarzeniem jest dziaanie ACTION_DOWN
(warto dziaania rwna 0) wywoane przyoeniem pierwszego palca. Mwi nam o tym metoda
getAction(). Aby dowiedzie si, jakie wyniki s generowane przez poszczeglne metody, wystarczy spojrze na kod metody describeEvent() umieszczony w pliku MainActivity.java.
Otrzymujemy jeden wskanik, ktrego indeks i identyfikator maj przypisan warto 0.
Nastpnie zostanie prawdopodobnie wygenerowanych kilka zdarze ACTION_MOVE (warto dziaania rwna 2) dla tego palca, chocia na listingu 25.11 widzimy tylko jedno z nich. Cigle posiadamy jeden wskanik zawierajcy wymienione wczeniej wartoci.

Rozdzia 25 Ekrany dotykowe

885

Chwil pniej dotykamy ekranu drugim palcem. Dziaanie otrzymuje teraz warto dziesitn
rwn 261. Co to oznacza? Warto dziaania skada si z dwch czci: wskanika, dla ktrego
jest wykonywane dziaanie, oraz rodzaju przeprowadzanej czynnoci. Po przeksztaceniu wartoci
dziesitnej 261 na warto szesnastkow otrzymujemy 0x00000105. Dziaanie jest oznaczone
przez najmniejszy bajt (w naszym wypadku 5), natomiast identyfikator wskanika jest definiowany przez nastpny dostpny bajt (u nas jest to 1). Zwrmy uwag, e jestemy informowani o identyfikatorze wskanika, a nie o jego indeksie. Po dotkniciu ekranu trzecim
palcem dziaanie uzyska warto 0x00000205 (517 w systemie dziesitnym). Dotknicie czwartym palcem zmienioby warto na 0x00000305 (dziesitne 773) i tak dalej. Nie widzielimy
jeszcze dziaania o wartoci rwnej 5, jest ono jednak znane jako ACTION_POINTER_DOWN.
Rni si od dziaania ACTION_DOWN jedynie tym, e jest wykorzystywane w mechanizmie
wielodotykowoci.
Przyjrzyjmy si nastpnej parze rekordw okna LogCat z listingu 25.11. Pierwszy rejestr odpowiada za zdarzenie ACTION_MOVE (warto dziaania rwna 2). Pamitajmy, e trudno
utrzyma palce w bezruchu na prawdziwym ekranie. Demonstrujemy tylko jedno zdarzenie
ACTION_MOVE, moe ich jednak wystpi wicej. Po podniesieniu pierwszego palca otrzymujemy warto dziaania rwn 0x00000006 (w systemie dziesitnym jest to warto 6).
Podobnie jak we wczeniejszym przypadku, indeks wskanika wynosi 0, a dziaanie nosi nazw ACTION_POINTER_UP (analogicznym dziaaniem w mechanizmie wykorzystujcym jeden palec jest ACTION_UP). Jeeli podniesiemy drugi palec, otrzymamy dziaanie 0x00000106
(262 w kodzie decymalnym). Zauwamy, e nadal otrzymujemy informacje o dwch palcach,
gdy jeden z nich generuje zdarzenie ACTION_UP.
Ostatnia para rekordw na listingu 25.11 przedstawia jeszcze jedno zdarzenie ACTION_MOVE
dla drugiego palca, po ktrym nastpuje dziaanie ACTION_UP. Tym razem widzimy warto
dziaania rwn 1 (ACTION_UP). Nie otrzymalimy wartoci 262, ale za chwil wyjanimy dlaczego. Odnotujmy rwnie fakt, e po oderwaniu pierwszego palca od ekranu indeks wskanika
uleg zmianie z wartoci 1 na 0, ale identyfikator wskanika cigle wynosi 0.
Zdarzenia ACTION_MOVE nie pozwalaj okreli, ktry palec zosta przesunity. Dla kadego
ruchu zawsze bdzie definiowana warto 2, bez wzgldu na liczb wykrywanych palcw czy to,
z ktrym palcem mamy do czynienia. Wszystkie pozycje przyoonych palcw s przechowywane w obiekcie MotionEvent, wic musimy odczytywa wsprzdne i analizowa sytuacj.
Jeeli do ekranu zostanie przyoony tylko jeden palec, identyfikator wskanika pozwoli nam
okreli, ktry palec si porusza, gdy bdzie to jedyny dostpny wskanik. Na listingu 25.11
w miejscu, gdzie pozosta przyoony tylko drugi palec, zdarzenie ACTION_MOVE posiadao indeks
o wartoci 0 oraz identyfikator wskanika rwny 1, nie byo wic problemu z okreleniem palca.
Wracajc do pocztku listingu 25.11, indeks wskanika o wartoci 0 dla pierwszego przyoonego palca posiada warto 0, dlaczego wic nie otrzymujemy wartoci 0x00000005 (dziesitnie 5) dla dziaania, skoro palec ten dotkn ekranu przed innymi palcami? Jest to, niestety,
pytanie, na ktre nie ma dobrej odpowiedzi. Moemy otrzyma warto dziaania rwn 5
w nastpujcym przypadku: najpierw dotykamy ekranu pierwszym palcem, nastpnie drugim
palcem, dziki czemu otrzymujemy wartoci dziaania, odpowiednio, 0 i 261 (pomijamy na razie zdarzenia ACTION_MOVE). Teraz podnosimy pierwszy palec (warto dziaania 6) i ponownie
przykadamy go do ekranu. Identyfikator wskanika dla drugiego palca pozosta niezmieniony
i posiada warto 1. Przez czas oderwania pierwszego palca od ekranu aplikacja miaa jedynie
informacj o identyfikatorze wskanika posiadajcym warto 1. Po ponownym dotkniciu wywietlacza pierwszy palec ponownie otrzyma identyfikator 0. Skoro uywamy wielu palcw,

886 Android 3. Tworzenie aplikacji


otrzymujemy warto dziaania rwn 5 (indeks wskanika o wartoci 0 i dziaanie o wartoci 5).
Odpowiedzi na zadane wczeniej pytanie jest zatem zapewnienie kompatybilnoci wstecznej,
nie jest to jednak powd do radoci. Wartoci dziaa 0 i 1 pochodz jeszcze sprzed ery wielodotykowoci, wic aplikacje napisane w tamtych czasach bd dziaa, pod warunkiem e bdzie
wykorzystywany tylko jeden palec. W przypadku korzystania z dwch palcw, jeeli pierwszy
palec dotknie ekranu w jednym miejscu, a po nim drugi palec dotknie innego obszaru ekranu,
podniesienie pierwszego palca nie zostanie rozpoznane przez aplikacj nieobsugujc zdarze
wielodotykowoci. Wynika to z faktu, e podniesienie tego palca spowoduje wygenerowanie
wartoci dziaania 6, a nie 1. Dopiero podniesienie drugiego palca spowoduje utworzenie wartoci dziaania rwnej 1. Taka aplikacja bdzie odbieraa opisan sekwencj dotykania ekranu
dwoma palcami, jakby w gr wchodzi tylko jeden palec tak jakby pierwszy palec mia si
magicznie teleportowa do miejsca, w ktrym znajduje si drugi.
Gdy ekranu dotyka tylko jeden palec, Android interpretuje jego zachowanie jako dziaania obsugiwane za pomoc jednego palca. Otrzymujemy zatem star warto ACTION_UP rwn 1 zamiast wielodotykowej wartoci ACTION_UP rwnej 6. Kod aplikacji musi takie przypadki obsuy z ostronoci. Identyfikator wskanika o wartoci 0 mgby wygenerowa warto zdarzenia
ACTION_DOWN w przedziale od 0 do 5, w zalenoci od wykorzystywanych wskanikw. Ostatni
podniesiony palec zawsze wygeneruje warto zdarzenia ACTION_UP rwn 1, bez wzgldu na
identyfikator wskanika.
Klasa MotionEvent zawiera pewne stae pomocnicze, ktre przydaj si do zrozumienia sytuacji: na przykad staa MotionEvent.ACTION_POINTER_3_DOWN posiada warto 0x00000205
(dziesitnie 517), ktra jak ju wiemy definiuje trzeci palec przyoony do ekranu. Wartoci te mog si jednak okaza nie tak bardzo przydatne, jeeli zdecydujemy si rozpoznawa
przyoone palce poprzez identyfikator wskanika umieszczony w drugim bajcie oraz poprzez
dziaanie widoczne w pierwszym bajcie. W rzeczywistoci byoby nawet lepiej, gdybymy uywali innych staych klasy MotionEvent do odczytywania wartoci przekazywanych przez metod
getAction(). Mylimy tu o staych MotionEvent.ACTION_POINTER_ID_MASK, MotionEvent.
ACTION_MASK i MotionEvent.ACTION_POINTER_ID_SHIFT. Poprzez poczenie wartoci dziaania
z kad wymienion mask za pomoc operacji AND oraz przeniesienie wyniku wygenerowanego
dla identyfikatora wskanika bdziemy mogli w rzetelny sposb okreli sytuacj, bez wzgldu
na to, ile palcw jest obsugiwanych przez urzdzenie. Twrcy systemu Androida musieli rwnie
to zrozumie, poniewa takie stae, jak ACTION_POINTER_3_DOWN, zostay przedawnione.
Sprawa jednak wymaga duszego zastanowienia. Omawiane stae indeksw maj w nazwach
czony ID, a nie INDEX, niedawno za wspominalimy, e w drugim bajcie jest przechowywany
indeks wskanika. Niestety, w wersjach Androida starszych od 2.2 nie mona jednoznacznie
okreli, co jest definiowane przez ten bajt. W wersji 2.2 systemu nazwy tych staych zostay pozmieniane na ACTION_POINTER_INDEX_MASK oraz ACTION_POINTER_INDEX_SHIFT, ich wartoci
jednak pozostay takie same. Drugi bajt zawsze przechowuje indeks wskanika, jednak w starszych wersjach systemu nazwy tych staych wprowadzay w bd. Zostay one zaktualizowane
w wersji 2.2 Androida, a wersje tych staych zawierajcych w nazwie czon ID zostay uznane za
przestarzae. Nie bjmy si tworzy wasnych staych, wykorzystywanych we wszystkich wersjach Androida.
W poprzednim przykadzie wprowadzilimy metod logAction(), pozwalajc na rozszyfrowanie wartoci dziaania. Na listingu 25.12 umiecilimy odpowiedni fragment kodu.

Rozdzia 25 Ekrany dotykowe

887

Listing 25.12. Przykadowy kod sucy do okrelenia rezultatu dziaania metody


MotionEvent.getAction()
int action = event.getAction();
int ptrIndex = (action & MotionEvent.ACTION_POINTER_ID_MASK) >>>
MotionEvent.ACTION_POINTER_ID_SHIFT;
action = action & MotionEvent.ACTION_MASK;
if(action == 5 || action == 6)
action = action - 5;
int ptrId = event.getPointerId(ptrIndex);

Po wykonaniu instrukcji umieszczonych na listingu 25.12 atrybut ptrId bdzie przechowywa


identyfikator wskanika zwizany z okrelonym dziaaniem, action bdzie posiada wartoci
w przedziale od 0 do 4, a w atrybucie ptrIndex zostanie umieszczona warto indeksu wskanika
stosowana przez metod getX() oraz inne metody klasy MotionEvent. Spogldajc na wartoci
przekazywane z metody getAction(), mona je interpretowa w taki sposb, e wartoci wiksze od 4 reprezentuj wartoci zwizane z identyfikatorem wskanika. Wartoci mniejsze lub
rwne 4 s zwizane jedynie z uywanym palcem, bez wzgldu na jego identyfikator wskanika.
W niektrych przypadkach moe zaistnie potrzeba odjcia wartoci 5 od wartoci dziaania,
aby mc wykorzysta zdarzenia ACTION_DOWN oraz ACTION_UP nawet w mechanizmie wielodotykowoci. W innych przypadkach nie jest to koniecznie. Decyzja zaley od programisty.

Funkcja wielodotykowoci
w systemach poprzedzajcych wersj 2.2
W wersji 2.2 Androida wprowadzono kilka istotnych zmian do mechanizmu dziaania wielodotykowoci. Wspomnielimy w poprzednim punkcie o uznaniu pewnych staych za przestarzae oraz o wprowadzeniu nowych. W tej wersji systemu pojawiy si rwnie dwie nowe
metody getActionMasked() oraz getActionIndex() uatwiajce okrelenie wskanika
oraz indeksu wykorzystywanych w danym dziaaniu. Za pomoc tych metod moemy podmieni kod z listingu 25.12 fragmentem z listingu 25.13.
Listing 25.13. Przykadowy kod pozwalajcy na okrelenie wartoci dziaania
int action = event.getActionMasked();
int ptrIndex = event.getActionIndex();
int ptrId = event.getPointerId(ptrIndex);

Kod ten jest o wiele prostszy od zaprezentowanego na listingu 25.12. Zwrmy jednak uwag,
e w dziaaniu trzeba uwzgldni nastpujce zmienne: ACTION_DOWN, ACTION_UP, ACTION_MOVE,
ACTION_CANCEL, ACTION_OUTSIDE, ACTION_POINTER_DOWN lub ACTION_POINTER_UP (wartoci
kolejno od 0 do 6). Jeeli chcemy wykorzysta tego typu rozwizanie, moemy po prostu odj 5,
w przypadku gdy metoda getActionMasked() przekae warto wiksz od 4. Moemy rwnie pomczy si z dwiema dodatkowymi wartociami.
Jak ju wczeniej wspomnielimy, zanim zaczniemy tworzy wasn mask, widoczn na przykad na listingu 25.12, musimy pamita, e poczwszy od wersji 2.2 Androida, stae ACTION_
POINTER_ID_MASK oraz ACTION_POINTER_ID_SHIFT zostay uznane za przestarzae, a na ich miejsce wprowadzono stae ACTION_POINTER_INDEX_MASK oraz ACTION_POINTER_INDEX_SHIFT,
definiujce dokadnie te same wartoci. Poniewa te zaktualizowane stae nie s rozpoznawane

888 Android 3. Tworzenie aplikacji


przez starsze wersje systemu, pozostamy lepiej przy tworzeniu staych posiadajcych wartoci, odpowiednio, 0x0000ff00 oraz 0x00000008, poniewa bd one waciwie interpretowane w kadej wersji systemu.

Obsuga map za pomoc dotyku


Aplikacje przeznaczone do pracy z mapami rwnie obsuguj zdarzenia dotyku. Widzielimy ju, e poprzez dotknicie mapy moemy wywoa kontrolk zmiany skali mapy, moemy te j przesuwa. S to wbudowane funkcje map. A w jaki sposb mona wdroy inne pomysy wykorzystania dotykowoci? Zademonstrujemy implementacj kilku ciekawych
rozwiza zwizanych z mapami, midzy innymi funkcji pobierajcej wsprzdne geograficzne
lokalizacji po jej dotkniciu na mapie. W ten sposb mamy dostp do bardzo wielu przydatnych funkcji.
Jedn z gwnych klas sucych do obsugi map jest klasa MapView. Klasa ta, podobnie jak omawiana wczeniej klasa View, zawiera metod onTouchEvent(), ktrej jedynym argumentem
jest obiekt MotionEvent. Moemy take uy metody setOnTouchListener() wobec klasy
MapView do skonfigurowania procedury obsugi metod zwrotnych, reagujcych na dotknicia.
Innymi gwnymi obiektami dla map jest zestaw nakadek Overlay, w tym takie klasy, jak
ItemizedOverlay i MyLocationOverlay. Wszystkie wymienione obiekty zostay omwione
w rozdziale 17. Klasy Overlay rwnie posiadaj metod onTouchEvent(), chocia jej sygnatura jest nieco inna od analogicznej metody uywanej w standardowej klasie View. Dla klasy
Overlay wyglda ona tak:
onTouchEvent(android.view.MotionEvent e, MapView mapView)

Moemy przesoni metod onTouchEvent(), jeli chcemy wprowadzi inne formy obsugi
map. Czciej spotykamy si z przesanianiem metod w klasie Overlay ni w klasie MapView,
zatem w tym punkcie zajmiemy si tym zagadnieniem. Podobnie jak we wczeniej omwionych przypadkach, metoda onTouchEvent() klas Overlay obsuguje obiekty MotionEvent.
Nawet w przypadku map zdarzenie MotionEvent przechowuje wsprzdne X i Y obszaru dotknitego przez uytkownika. Jest to tutaj niemal nieprzydatne, poniewa przewanie bdziemy
chcieli zna wsprzdne geograficzne dotknitego punktu, a nie wsprzdne ekranu. Na
szczcie istniej rozwizania tego problemu.
Klasa MapView zostaa wyposaona w interfejs Projection, ktry zawiera metody przetwarzajce piksele na obiekty GeoPoint i odwrotnie. Dostp do tego interfejsu uzyskujemy poprzez wywoanie metody MapView.getProjection(). Po wprowadzeniu interfejsu Projection do
konwersji moemy wykorzysta metody fromPixels() i toPixels(). Pamitajmy, e klasa
Projection jest przydatna jedynie wtedy, gdy mapa nie ulega zmianie w widoku. Wewntrz
metody onTouchEvent() moemy za pomoc metody fromPixels() przekonwertowa wartoci X
i Y pooenia na obiekt GeoPoint.
Przydatn i jednoczenie interesujc metod klasy Overlay jest metoda onTap(), bardzo podobna do omwionej wczeniej metody onTouch(), rnicej si jednak pewnym kluczowym
aspektem. Klasy Overlay nie posiadaj metody onTouch(). Sygnatura metody onTap() zostaa
pokazana poniej:
public boolean onTap(GeoPoint p, MapView mapView)

Rozdzia 25 Ekrany dotykowe

889

Oznacza to, e jeli uytkownik dotknie mapy z nakadk Overlay, zostanie wywoana metoda
onTap() wraz z obiektem GeoPoint, ktry okreli wsprzdne wskazanego miejsca. W ten sposb
zaoszczdzimy mnstwo czasu, gdy nie bdziemy musieli podejmowa prb okrelenia dotknitego miejsca na mapie. Nie musimy si ju martwi o konwersj wsprzdnych X i Y
lokacji na wsprzdne geograficzne. Zajmuje si tym system.
Przyjrzymy si teraz ponownie przykadowi z rozdziau 17., w ktrym wywietlalimy map
wraz z przyciskami pozwalajcymi na jej przegldanie w rnych trybach (satelitarny, uliczny,
widok ruchu ulicznego oraz tryb standardowy). Do tego projektu dodamy moliwo uruchamiania trybu widoku ulicznego lokacji wskazanej na mapie. W tym celu musimy umieci nakadk Overlay w widoku MapView, a po dotkniciu nakadki Overlay zdarzenie to zostanie
przeksztacone na wskazan lokalizacj na mapie. Po przeksztaceniu w taki sposb lokalizacji uruchomimy intencj wywoujc tryb widoku ulicznego. Rozpoczniemy od utworzenia
w rodowisku Eclipse kopii aplikacji MapViewDemo z rozdziau 17. (listingi 17.2 i 17.3). Nastpnie
wykorzystamy informacje z listingu 25.14 do zmodyfikowania metody onCreate() gwnej
klasy Activity oraz dodamy now klas, rwnie umieszczon na listingu 25.14, w pliku
ClickReceiver.java. Zmiany w metodzie onCreate() zostay zaznaczone pogrubion czcionk.
Interfejs, widoczny na rysunku 17.3, nie ulegnie zmianie.
Listing 25.14. Dodawanie funkcji dotyku do aplikacji MapViewDemo
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
mapView = (MapView)findViewById(R.id.mapview);
ClickReceiver clickRecvr = new ClickReceiver(this);
mapView.getOverlays().add(clickRecvr);
mapView.invalidate();
}

// Jest to plik ClickReceiver.java


import
import
import
import

android.content.Context;
android.content.Intent;
android.net.Uri;
android.util.Log;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
public class ClickReceiver extends Overlay{
private static final String TAG = "ClickReceiver";
private Context mContext;
public ClickReceiver(Context context) {
context = context;
}
@Override
public boolean onTap(GeoPoint p, MapView mapView) {
Log.v(TAG, "Otrzymano klikniecie w tym punkcie: " + p);
if(mapView.isStreetView()) {

890 Android 3. Tworzenie aplikacji


Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse
("google.streetview:cbll=" +
(float)p.getLatitudeE6() / 1000000f +
"," + (float)p.getLongitudeE6() / 1000000f
+"&cbp=1,180,,0,1.0"
));
mContext.startActivity(myIntent);
return true;
}
return false;
}
}

To wystarczy, aby uruchomi zmodyfikowan wersj aplikacji chyba e nie posiadamy aplikacji widoku ulicznego1 (StreetView) na emulatorze lub w urzdzeniu. Aplikacja StreetView
zostaa umieszczona w emulatorach CupCake (1.5) i Donut (1.6), a zostaa usunita z emulatora
clair (2.0). Jeeli brakuje nam programu StreetView na emulatorze, moemy wykorzysta
rzeczywiste urzdzenie wyposaone w t funkcjonalno i przetestowa na nim utworzon
aplikacj. Osoby posiadajce wycznie emulator mog wykona nastpujce czynnoci:
1. Skonfiguruj urzdzenie AVD oparte na interfejsie Google API w wersji 1.6 lub 1.5.
2. Uruchom emulator za pomoc urzdzenia AVD zdefiniowanego w punkcie 1.
3. Wykonaj polecenie adb pull /system/app/StreetView.apk, aby skopiowa
aplikacj StreetView.apk z emulatora na dysk twardy stacji roboczej.
4. Skonfiguruj urzdzenie AVD dla wersji interfejsu Google API, z ktrego zamierzasz
korzysta.
5. Zatrzymaj emulator z punktu 2. i uruchom emulator z punktu 4.
6. Wykonaj polecenie adb install StreetView.apk na pliku .apk skopiowanym
w punkcie 3.
Aplikacja StreetView powinna zosta zainstalowana na emulatorze, dziki czemu nasza przykadowa aplikacja zadziaa. W nowszych wersjach Androida nazwa pakietu tej aplikacji brzmi
Street.apk, zatem by moe uda nam si znale wersj nowsz od umieszczonej w Androidzie 1.6.
Po uruchomieniu wieo zmodyfikowanej aplikacji MapViewDemo wykonajmy zblienie pozwalajce na zobaczenie ulic miasta. Kliknijmy przycisk Ulica, aby ulice obsugiwane przez
aplikacj StreetView (zdjcia tych ulic s zamieszczone na przykad w bazie danych Google)
zostay zaznaczone na niebiesko. Dotknijmy teraz jednej z ulic, a zostanie wywoana metoda
onTap() klasy ClickReceiver, ktra z kolei skontaktuje za pomoc intencji aktywno aplikacji
StreetView z dotknit lokalizacj. Jeeli dotkniemy obszar mapy nieobsugiwany przez aplikacj StreetView, pojawi si pusty ekran tej aplikacji wraz z komunikatem typu nieprawidowa
panorama. Oznacza to, e serwer Google nie moe odnale zdj znajdujcych si w pobliu
wybranego miejsca. Kliknijmy przycisk cofania, aby wrci do aplikacji obsugujcej mapy,
i sprawdmy inn lokalizacj. Jeeli zajrzymy do okna LogCat, zauwaymy, e zostay w nim
zapisane wsprzdne geograficzne dotknitej lokacji. Zwrmy uwag, e obiekt GeoPoint
definiuje szeroko i dugo geograficzn danymi typu int, natomiast identyfikator URI aplikacji StreetView wymaga typu float.
1

Tryb widoku ulicznego jeszcze nie jest dostpny w Polsce przyp. red.

Rozdzia 25 Ekrany dotykowe

891

W naszej przykadowej aplikacji zdecydowalimy si na wysyanie intencji zawierajcej wsprzdne geograficzne dotknitej lokacji do aktywnoci aplikacji StreetView. Moemy sobie jednak
wyobrazi rwnie inne moliwoci. Jeeli znamy szeroko i dugo geograficzn lokalizacji,
moemy wykorzysta obiekt Geocoder do identyfikacji jej okolicy. Nic nie stoi na przeszkodzie,
aby uy informacji o lokalizacji do nawigacji typu zakrt po zakrcie. Istnieje moliwo zmierzenia odlegoci wskazanej lokalizacji od naszego biecego pooenia. Moemy nawet zachowa dane lokalizacji do pniejszego uytku.

Gesty
Gesty s specjalnym przypadkiem zdarzenia dotyku. Pojcie gest jest stosowane wobec rnorodnych czynnoci obsugiwanych przez system Android, poczwszy od prostej sekwencji dotykowej, na przykad szarpnicia lub cinicia, a do formalnych gestw klasy Gesture, ktre zostan omwione w dalszej czci rozdziau. Szarpnicia, cinicia, dugie przycinicia oraz gesty
przewijania posiadaj zdefiniowane zachowanie, aktywowane cile okrelonymi bodcami.
Wikszo osb wie, e szarpnicie oznacza gest, w ktrym palec zostaje przyoony do ekranu,
do szybko przesunity w okrelonym kierunku i na zakoczenie oderwany od wywietlacza.
Na przykad wykonanie tego gestu w aplikacji Galeria (ukazujcej obrazy sekwencyjnie, od lewej
do prawej strony) spowoduje, e kolejne obrazy przemkn przed naszymi oczami.
W niniejszym podrozdziale wykorzystamy wiedz zdobyt na temat obiektw MotionEvent
i rozszerzymy j o gesty, korzystajc z przykadowego gestu ciskania. To nie jest wcale takie
trudne, jak mogoby si wydawa. Gest ciskania nie jest jawnie obsugiwany w wersjach Androida starszych od 2.2, jeli wic chcemy zaimplementowa w nich ten ruch, musimy wasnorcznie napisa kod odczytujcy obiekty zdarze oraz wykonujcy odpowiednie dziaanie. Tym
si wanie zajmiemy. Poczwszy od wersji 2.2 systemu, uzyskujemy kilka pomocnych funkcji,
pozwalajcych na korzystanie z takich gestw jak ciskanie; poznamy je w dalszej czci rozdziau.
Zaprezentujemy nastpnie kilka klas, pomocnych w definiowaniu innych gestw, na przykad szarpni i dugich przycini. Std pozostanie tylko krok do wprowadzenia niestandardowych gestw, tj. gestw, ktre moemy sami zarejestrowa, a ktrych odtworzenie
przez uytkownika w naszej aplikacji uruchomi okrelon czynno. Najpierw jednak pobawmy
si gestem ciskania!

Gest ciskania
Jednym z ciekawszych zastosowa wielodotykowoci jest gest ciskania, wykorzystywany do
zmiany skali obrazu. Mechanizm ten opiera si na koncepcji rozsuwania i ciskania palcw:
jeeli dotkniemy wywietlacz dwoma palcami i je rozsuniemy, aplikacja powinna zareagowa
powikszeniem obrazu, jeeli natomiast je ciniemy, dany element zostanie zmniejszony.
Gest ten jest najczciej wykorzystywany w aplikacjach obsugujcych obrazy, na przykad pokazujcych mapy.
W celu zademonstrowania procesu implementacji gestu ciskania zmodyfikujemy poprzedni
aplikacj. Na listingu 25.15 widzimy now wersj klasy ClickReceiver; reszta kodu pozostaje
bez zmian. Zwrmy uwag, e ta aplikacja bdzie dziaaa na urzdzeniach wyposaonych
przynajmniej w wersj 2.2 Androida, co zostanie wyjanione po zaprezentowaniu listingu.

892 Android 3. Tworzenie aplikacji


Listing 25.15. Kod Java implementujcy gest ciskania
// Jest to plik ClickReceiver.java
import
import
import
import
import
import
import
import
import

android.content.Context;
android.content.Intent;
android.net.Uri;
android.util.FloatMath;
android.util.Log;
android.view.MotionEvent;
com.google.android.maps.GeoPoint;
com.google.android.maps.MapView;
com.google.android.maps.Overlay;

public class ClickReceiver extends Overlay {


private static final String TAG = "ClickReceiver";
private static final float ZOOMJUMP = 75f;
private Context mContext;
private boolean inZoomMode = false;
private boolean ignoreLastFinger = false;
private float mOrigSeparation;
public ClickReceiver(Context context) {
mContext = context;
}
@Override
public boolean onTap(GeoPoint p, MapView mapView) {
Log.v(TAG, "Otrzymano klikniecie w tym punkcie: " + p);
if(mapView.isStreetView()) {
Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse
("google.streetview:cbll=" +
(float)p.getLatitudeE6() / 1000000f +
"," + (float)p.getLongitudeE6() / 1000000f
+"&cbp=1,180,,0,1.0"
));
mContext.startActivity(myIntent);
return true;
}
return false;
}
public boolean onTouchEvent(MotionEvent e, MapView mapView) {
Log.v(TAG, "w metodzie onTouchEvent, dzialaniem jest " + e.getAction());
int action = e.getAction() & MotionEvent.ACTION_MASK;
if(e.getPointerCount() == 2) {
inZoomMode = true;
}
else {
inZoomMode = false;
}
if(inZoomMode) {
switch(action) {
case MotionEvent.ACTION_POINTER_DOWN:

Rozdzia 25 Ekrany dotykowe

893

// Moemy rozpocz nowy gest ciskania, wic przygotujmy si


mOrigSeparation = calculateSeparation(e);
break;
case MotionEvent.ACTION_POINTER_UP:

// Koczymy gest ciskania, wic przygotujmy si


// Ignoruje ostatni palec, gdy tylko on
// dotyka wywietlacza
ignoreLastFinger = true;
break;
case MotionEvent.ACTION_MOVE:

// Wykonujemy gest ciskania, wic decydujemy


// o zmianie poziomu przyblienia/oddalenia
float newSeparation = calculateSeparation(e);
if(newSeparation - mOrigSeparation > ZOOMJUMP) {

// palce si rozeszy, przybliamy


mapView.getController().zoomIn();
mOrigSeparation = newSeparation;
}
else if (mOrigSeparation - newSeparation > ZOOMJUMP) {

// Palce zbliyy si do siebie, oddalamy


mapView.getController().zoomOut();
mOrigSeparation = newSeparation;
}
break;
}

// Nie przekazujmy tych zdarze Androidowi, poniewa


// zajmujemy si nimi
return true;
}
else {

// W razie koniecznoci zeruje logik przybliania/oddalania


}

// Jeeli tylko jeden palec jest przyoony, odrzucamy zdarzenia,


// dopki nie zostanie uniesiony
if(ignoreLastFinger) {
if(action == MotionEvent.ACTION_UP)
ignoreLastFinger = false;
return true;
}
return super.onTouchEvent(e, mapView);
}
private float calculateSeparation(MotionEvent e) {
float x = e.getX(0) - e.getX(1);
float y = e.getY(0) - e.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
}

Do nakadki ClickReceiver dodalimy metod zwrotn onTouchEvent(). Wewntrz tej metody uzyskujemy kady obiekt MotionEvent, ktry jest kierowany z ekranu dotykowego do
widoku MapView. W wikszoci przypadkw po prostu przekazujemy je dalej. W ten sposb

894 Android 3. Tworzenie aplikacji


przesuwanie mapy bdzie moliwe w nieprzerwany sposb, bdzie te mona uruchomi funkcj StreetView poprzez stuknicie. Jednak jeeli na wywietlaczu zostanie wykryte dotknicie
dwoma palcami, moe to oznacza rozpoczcie gestu ciskania, wic przechodzimy w tryb zmiany
skali. Jeeli pojawiaj si informacje o dotyku dwoma palcami, musimy zdecydowa o dalszych
operacjach, std instrukcja switch przy okazji zdarzenia dziaania.
Gdyby pojawio si dziaanie ACTION_POINTER_DOWN (pamitajmy, e pojawia si ono jedynie
w przypadku wielodotykowoci, ktra tutaj wystpuje, gdy uywamy dwch palcw), oznaczaoby to, e z dotyku jednym palcem uytkownik przeszed na dwa palce. Po tym zdarzeniu
moemy oczekiwa gestu ciskania ze strony uytkownika. Aby okreli, czy palce oddalaj si
od siebie, czy te zbliaj si do siebie, musimy zapamita odlego dzielc je na pocztku gestu. Jest ona obliczana jako pierwiastek kwadratowy z sumy kwadratw wartoci stanowicych
rnice wsprzdnych pomidzy palcami innymi sowy, stosujemy najzwyklejsze twierdzenie Pitagorasa. Jestemy przekonani, e po przyoeniu dwch palcw do ekranu obiekt zdarzenia bdzie posiada wsprzdne zdefiniowane w indeksach 0 i 1, i nie ma znaczenia, jakie palce s wykorzystywane.
W przypadku wystpienia zdarzenia ACTION_POINTER_UP jest to ostatnie zdarzenie przekazujce
w wynikach informacje o dwch palcach, zanim obiekty MotionEvent zaczn przesya wyniki
zawierajce dane o jednym palcu. Bdzie to oznaczao, e gest ciskania zosta zakoczony. Jeeli
pozwolimy teraz systemowi obserwowa zdarzenia wywoywane dotykiem jednego palca, aplikacja moe si zacz dziwnie zachowywa. Gdyby na przykad Android otrzyma zdarzenie
ACTION_UP z ostatniego palca, mgby uzna je za stuknicie i uruchomi funkcj StreetView,
co nie byoby podanym zachowaniem. Spodziewamy si, e uytkownik uniesie ostatni palec
w momencie zakoczenia gestu ciskania, zatem do tego czasu ignorujemy wszelkie inne zdarzenia. Dokonujemy tego za pomoc wartoci true w atrybucie ignoreLastFinger, ktra
bdzie sprawdzana w momencie decydowania o losie zdarze.
Jeeli otrzymalimy zdarzenie ACTION_MOVE, palce mog si przyblia lub oddala. Poprzez
obliczenie nowej odlegoci dzielcej obydwa palce oraz porwnanie jej ze star wartoci odlegoci moemy zadecydowa, czy obraz ma zosta powikszony, pomniejszony, czy te jego
rozmiar nie zmieni si. W przypadku zmiany stopnia przyblienia musimy wyzerowa star
warto odlegoci. Uytkownik moe nie odrywa palcw od ekranu i cigle oddala lub przyblia do siebie palce, wic nasza aplikacja musi odpowiednio reagowa na te gesty. Jeeli nie
wykryjemy znaczcej zmiany w rnicy odlegoci pomidzy palcami, bdziemy cigle otrzymywa zdarzenia, dopki nie uzyskamy odpowiedniego dystansu lub uytkownik nie zakoczy
gestu ciskania.
Bez wzgldu na przychodzce dziaanie, jeeli znajdujemy si w trybie zmiany przyblienia,
otrzymujemy warto true z metody onTouchEvent(). W ten sposb informujemy system
o przetwarzaniu biecego zdarzenia oraz pozwalamy na przesyanie nastpnych. Na kocu
dziaania metody onTouchEvent() musimy zadecydowa, czy zdarzenie ma zosta pozostawione Androidowi. Za pomoc zmiennej ignoreLastFinger uniemoliwiamy pozostawianie
zdarze Androidowi, w przypadku gdy gest ciskania zosta zakoczony, lecz ostatni palec
cigle jest przyoony do wywietlacza. Po uniesieniu tego palca, co zostanie oznajmione
pojawieniem si dziaania ACTION_UP, moemy wznowi przekazywanie zdarze systemowi.
W ten sposb pozwalamy Androidowi zaj si gestami stukni i przecigania, lecz samodzielnie przetwarzamy gest ciskania. Oczywicie, sami moemy zaj si wspomnianymi gestami
we wntrzu tej metody zwrotnej.

Rozdzia 25 Ekrany dotykowe

895

W czasie testowania tej aplikacji bdziemy mogli w dalszym cigu przesuwa map w rne
strony oraz za pomoc stuknicia uruchamia tryb StreetView, jednak teraz gesty ciskania
i rozcigania pozwol nam na zwikszanie lub zmniejszanie skali mapy.
Zignorowalimy na moment moliwo dotykania ekranu trzema palcami, a nastpnie oderwania jednego z nich, dziki czemu pozostayby dwa aktywne palce. Wiele urzdze rozpoznaje maksymalnie tylko dwa jednoczenie przyoone palce do ekranu, powinnimy si jednak
spodziewa, e coraz wicej urzdze bdzie rozpoznawao wiksz liczb palcw. Jeeli chcemy
wykorzystywa gest ciskania w aplikacji, ktra nie obsuguje map, bdziemy musieli wasnorcznie zdefiniowa tryb powikszania danych obiektw. Jeeli na przykad na ekranie wywietlamy obraz, ktry chcemy powiksza i zmniejsza w reakcji na gesty rozcigania i ciskania, bdziemy musieli odpowiednio nim manipulowa po wystpieniu dziaania ACTION_MOVE,
tak jak to zostao wczeniej zaprezentowane. Niedugo zajmiemy si omwieniem podobnego
przykadu.
Wspomnielimy wczeniej, e gest ciskania nie by jawnie obsugiwany a do pojawienia si
wersji 2.2 Androida, a chocia omawiany kod dziaa rwnie w tej wersji, warto wykorzysta
nowe funkcje, pozwalajce na atwiejsze wprowadzenie tego gestu do aplikacji. Warto zauway,
e klasa MapView, poczwszy od wersji 2.2 Androida, posiada standardowo wbudowan obsug gestu ciskania; dziaa to bez adnej dodatkowej obsugi, wic nie musimy odnosi si do
adnego kodu odpowiedzialnego za ten gest w nowszych wersjach systemu. Zanim przyjrzymy
si natywnej obsudze gestu ciskania, musimy najpierw omwi klas, ktra jest dostpna
od samego pocztku GestureDetector.

Klasy GestureDetector i OnGestureListener


Co prawda implementacja gestu ciskania nie wymagaa duego wysiku, byoby jednak mio,
gdyby Android zaoferowa jak pomoc w rozpoznawaniu najpowszechniejszych gestw. Od
programisty wymagaoby to wtedy jedynie wprowadzenia w aplikacji odpowiedniej logiki,
reagujcej na dane gesty. Na szczcie Android posiada wanie taki mechanizm, chocia musielimy czeka a do wersji 2.2 Androida na klas umoliwiajc jawn obsug gestu ciskania.
Pierwsz klas tego typu jest obecna od samego pocztku istnienia Androida klasa Gesture
Detector, ktrej zadaniem jest otrzymywanie obiektw MotionEvent oraz informowanie
o sekwencji zdarze przypominajcej jeden ze standardowych gestw. Wszystkie obiekty
zdarze s przekazywane z metody zwrotnej do tej klasy, ktra z kolei wywouje inne metody
zwrotne po rozpoznaniu gestu, na przykad szarpnicia lub dugiego wcinicia. Musimy zarejestrowa obiekt nasuchujcy dla metod zwrotnych klasy GestureDetector i to wanie
w nim definiujemy logik precyzujc dziaania, jakie maj zosta wykonane po wykryciu
jednego ze standardowych gestw. Niestety, klasa ta nie rozpoznaje gestu ciskania; do tego
celu musimy skorzysta z innej, nowej klasy, do ktrej wkrtce dojdziemy.
Istnieje kilka sposobw utworzenia obiektu nasuchujcego. Pierwszym rozwizaniem jest napisanie nowej klasy, w ktrej zostanie zaimplementowany odpowiedni interfejs obiektu nasuchujcego, na przykad GestureDetector.OnGestureListener. Mamy do dyspozycji kilka abstrakcyjnych metod. Trzeba je zaimplementowa dla kadej metody zwrotnej, ktra moe wystpi.
Drug opcj jest zastosowanie jednej z prostych implementacji obiektu nasuchujcego i przesonicie odpowiednich metod zwrotnych. Na przykad klasa GestureDetector.SimpleOn
GestureListener posiada wszystkie metody zwrotne zaimplementowane w taki sposb,
e nie wykonuj one adnej operacji i przekazuj warto false. Wystarczy rozszerzy t

896 Android 3. Tworzenie aplikacji


klas i przesoni kilka metod, ktre bd przetwarzane po wykryciu odpowiednich gestw. Pozostae metody bd zaimplementowane w domylny sposb. To drugie rozwizanie wydaje si
bardziej perspektywiczne, nawet w przypadku przesonicia wszystkich metod zwrotnych, poniewa jeeli w kolejnych wersjach Androida do interfejsu zostan dodane kolejne metody
abstrakcyjne, dziki takiej prostej implementacji bdziemy mieli do dyspozycji ich domylne formy, wic bdziemy zabezpieczeni.
W wersji 2.2 Androida wprowadzono klas ScaleGestureDetector, ktra suy do rozpoznawania gestu ciskania. Zamierzamy zademonstrowa t klas wraz z odpowiednim obiektem
nasuchujcym. W tym celu pokaemy przykadowy kod z odpowiednim rysunkiem. Rozszerzamy tu prost implementacj interfejsu ScaleGestureDetector.SimpleOnScaleGesture
Listener dla obiektu nasuchujcego. Na listingu 25.16 zamiecilimy ukad graficzny
oraz kod Java aktywnoci MainActivity.
Listing 25.16. Ukad graficzny oraz kod Java odpowiedzialne za wykrywanie gestu ciskania za
pomoc klasy ScaleGestureDetector
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout" android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="fill_parent" >
<TextView android:text="Uyj gestu ciskania do zmiany rozmiaru obrazu."
android:layout_width="fill_parent" android:layout_height="wrap_content" />
<ImageView android:id="@+id/image" android:src="@drawable/icon"
android:layout_width="match_parent" android:layout_height="match_parent"
android:scaleType="matrix" />
</LinearLayout>

// Jest to plik MainActivity.java


import
import
import
import
import
import
import

android.app.Activity;
android.graphics.Matrix;
android.os.Bundle;
android.util.Log;
android.view.MotionEvent;
android.view.ScaleGestureDetector;
android.widget.ImageView;

public class MainActivity extends Activity {


private static final String TAG = "ScaleDetector";
private ImageView image;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1f;
private Matrix mMatrix = new Matrix();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
image = (ImageView)findViewById(R.id.image);
mScaleDetector = new ScaleGestureDetector(this,
new ScaleListener());

Rozdzia 25 Ekrany dotykowe

897

}
@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.v(TAG, "w metodzie onTouchEvent");

// Przekazuje wszystkie zdarzenia klasie ScaleGestureDetector


mScaleDetector.onTouchEvent(ev);
return true;
}
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();

// Upewniamy si, e obraz nie bdzie za may lub za duy


mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
Log.v(TAG, "w metodzie onScale, wspolczynnik skali = " + mScaleFactor);
mMatrix.setScale(mScaleFactor, mScaleFactor);
image.setImageMatrix(mMatrix);
image.invalidate();
return true;
}
}
}

Ukad graficzny jest bardzo prosty. Zawarta w nim zostaa kontrolka TextView wywietlajca
komunikat o wykorzystaniu gestu ciskania oraz widok ImageView ze standardow ikon Androida. Wykonujc gest ciskania, bdziemy zmienia rozmiar tej ikony. Oczywicie, w jej miejsce moemy wstawi dowolny obraz. Wystarczy umieci odpowiedni plik obrazu w folderze
drawable oraz zmieni atrybut android:src w pliku ukadu graficznego. Zwrmy uwag na
atrybut android:scaleType naszego obrazu. Za jego pomoc informujemy system, e do przeprowadzania operacji skalowania obrazu bdziemy uywa macierzy grafiki. Chocia suy
ona rwnie do przesuwania obrazu, skupimy si teraz wycznie na procesie skalowania.
Zauwamy take, e zdefiniowalimy najwikszy dopuszczalny widok ImageView. Nie
chcemy, aby w trakcie skalowania obrazu by on obcinany przez granice widoku ImageView.
Rwnie sam kod nie jest skomplikowany. Wewntrz metody onCreate() uzyskujemy odniesienie do obrazu i tworzymy klas ScaleGestureDetector. Jedynymi czynnociami w metodzie
zwrotnej onTouchEvent() s odbieranie wszelkich obiektw zdarze oraz przekazywanie wartoci true w celu cigego otrzymywania nowych zdarze. W ten sposb klasa ScaleGesture
Detector obserwuje wszystkie zdarzenia i moe zadecydowa, kiedy poinformowa system
o wykryciu gestu.
W obiekcie ScaleListener jest przeprowadzany proces skalowania. W klasie obiektu nasuchujcego znajdziemy trzy metody zwrotne: onScaleBegin(), onScale() oraz onScaleEnd().
Pierwsza i trzecia metoda nie s nam potrzebne, wic ich nie implementujemy.

898 Android 3. Tworzenie aplikacji


We wntrzu metody onScale() moemy wykorzysta przekazany wykrywacz do uzyskania
wielu informacji na temat operacji skalowania. Wspczynnik skalowania oscyluje przewanie
w granicach wartoci 1. Oznacza to, e w przypadku ciskania palcw warto tego wspczynnika wynosi nieco mniej ni 1, w czasie ich rozsuwania staje si ona nieznacznie wiksza
od 1. Domylna warto zmiennej mScaleFactor wynosi 1, wic stopniowo, w zalenoci od
ruchu palcw, jest ona zmniejszana lub zwikszana. Warto 1 wspczynnika skalowania definiuje normalny rozmiar obrazu. W przeciwnym wypadku obraz zostaje pomniejszony lub
powikszony, zalenie od tego, czy warto tego wspczynnika maleje, czy ronie. Za pomoc
eleganckiej kombinacji funkcji min i max nakadamy na ten wspczynnik pewne ograniczenia.
Zapobiegamy w ten sposb zbytniemu powikszeniu lub zmniejszeniu obrazu. Nastpnie
wykorzystujemy zmienn mScaleFactor do przeskalowania macierzy grafiki i stosujemy tak zaktualizowan macierz wobec obrazu. Wywoanie metody invalidate() wymusza proces ponownego rysowania obrazu na ekranie.
Jak wida, wkadamy tu o wiele mniej wysiku ni w poprzednim przykadzie, w ktrym musielimy wasnorcznie zajmowa si obiektami zdarze. Moemy teraz zaj si implementacj
odpowiedniej logiki uruchamianej standardowym gestem. Praca z interfejsem OnGesture
Listener bardzo przypomina czynnoci przeprowadzane na interfejsie ScaleListener; jedyna rnica polega na obecnoci metod zwrotnych odpowiedzialnych za inne gesty.
Standardowe gesty s uyteczne, ale niekiedy programista chce mc obsuy wasne gesty wewntrz aplikacji. By moe naley umoliwi uytkownikami narysowanie gestu zaznaczenia,
dziki czemu aplikacja wykona jak operacj. Potrzebne nam bd do tego niestandardowe gesty,
ktrymi si teraz zajmiemy.

Niestandardowe gesty
W ostatniej czci tego rozdziau przyjrzymy si formalnym klasom typu Gesture. Zgodnie
z definicj gestem nazywamy uprzednio zarejestrowany ruch po ekranie dotykowym, ktrego
aplikacja oczekuje od uytkownika. Jeeli uytkownik wykona taki gest w aplikacji, zacznie ona
wykonywa operacje zdefiniowane dla tego ruchu. Potrzebne s nakadki wykrywajce dany ruch,
ktre przekazuj informacje o wykryciu ruchu do gwnej aktywnoci. Stosowanie gestw pozwala uproci interfejs uytkownika, gdy przyciski oraz inne kontrolki staj si niepotrzebne
i s wypierane przez szybkie ruchy palcami lub inne gesty. Mog one stanowi rwnie interesujce interfejsy gier. W tym punkcie pokaemy, w jaki sposb mona rejestrowa wasne gesty
i programowa ich obsug w aplikacji. Zwrmy uwag, e wszelkie klasy zwizane z gestami
nie s w ogle wykorzystywane w tym przykadzie; prezentujemy tutaj zupenie inny zestaw klas.

Aplikacja Gestures Builder


Zanim zaprezentujemy kod odpowiedzialny za implementacj gestw, warto zapozna si z dostpn w zestawie Android SDK aplikacj Gestures Builder, ktra pomoe w zrozumieniu
koncepcji gestw. Aplikacja Gestures Builder tworzy plik gestw, zawierajcy bibliotek gestw,
i pozwala na zarzdzanie nim. Wczmy emulator z poziomu rodowiska Eclipse, przejdmy do
menu aplikacji i kliknijmy ikon Gestures Builder. Ikona ta zostaa pokazana na rysunku 25.4.
Jeeli emulator nie zawiera aplikacji Gestures Builder, musimy utworzy nowy projekt w rodowisku Eclipse. Jest ona dostpna jako przykadowa aplikacja w katalogu zestawu Android SDK,
mianowicie w folderze platforms/<wersja>/samples/GestureBuilder lub w katalogu samples.

Rozdzia 25 Ekrany dotykowe

899

Rysunek 25.4. Ikona aplikacji Gestures Builder


Tworzymy nowy projekt za pomoc opcji Create project from existing sample. Wybieramy odpowiedni wersj Androida, aby odblokowa rozwijalne menu z dostpnymi przykadowymi
aplikacjami, i wybieramy stamtd aplikacj GestureBuilder. Moemy nastpnie zainstalowa t
aplikacj w emulatorze.
Zobaczymy niemal puste okno. Kliknijmy przycisk Add. Aplikacja wywietli monit o wprowadzenie nazwy; nazwa ta zostanie powizana z gestem, ktry za chwil zarejestrujemy. Bdzie
ona uywana w kodzie w odniesieniu do tego gestu i w pewnym sensie posuy nam jako nazwa
polecenia. Gdy uytkownik wykona zakodowany gest, jego nazwa zostanie przekazana do metod, aby aplikacja potrafia przetworzy danie uytkownika. Nazwa powinna by rzeczownikiem, na przykad spiral albo checkmark, ewentualnie moe brzmie jak polecenie, na
przykad fetch albo stop. Naszym pierwszym gestem bdzie zaznaczenie, zatem wpiszmy
w polu Name nazw checkmark. Narysujmy teraz w tym pustym oknie duy znak zaznaczenia,
w emulatorze za pomoc myszy, a w urzdzeniu za pomoc palca. Jeeli jestemy niezadowoleni z rezultatu, moemy sprbowa ponownie; stary gest zniknie w momencie rozpoczcia
rysowania nowego. Gdy ju bdziemy zadowoleni z wyniku, kliknijmy przycisk Done. Powinien pojawi si ekran podobny do zaprezentowanego na rysunku 25.5.

Rysunek 25.5. Gest zaznaczenia zapisany na karcie pamici

Zwrmy uwag, e moemy narysowa rne rodzaje gestw zaznaczania i wszystkie nazwa
checkmark. Zarejestrujmy przynajmniej jeszcze jeden taki gest i nazwijmy go rwnie
checkmark; powinien w jaki sposb rni si od pierwotnego gestu, chociaby rozmiarem.
Dodajmy rwnie za pomoc przycisku Add gesture inne gesty, nadajc im odmienne nazwy.
Kadorazowe wcinicie przycisku Done powoduje dodanie kolejnego gestu do biblioteki.

900 Android 3. Tworzenie aplikacji


Moemy sprbowa utworzy gest wielodotykowy poprzez narysowanie dwoma palcami rwnolegych linii imitujcych znak rwnoci. Funkcja ta nie dziaa i zostanie narysowana tylko
jedna linia. By moe w nastpnych wersjach systemu bd obsugiwane gesty wielodotykowe
to znaczy gesty wymagajce co najmniej dwch palcw.
Kady gest posiada nazw i skada si z gestw waciwych (ang. stroke). Gestem waciwym
jest sekwencja dotyku rozpoczynajca si od dotknicia palcem ekranu, a koczca na jego
oderwaniu od wywietlacza. Jak ju wiemy, sekwencja dotyku jest tworzona przez zdarzenia
MotionEvent. W analogiczny sposb gest waciwy jest tworzony przez punkty gestu. Gesty s
przechowywane w magazynie gestw. Biblioteka gestw zawiera jeden taki magazyn. W Androidzie wszystkie te klasy mona wykorzysta w kodzie. Na rysunku 25.6 zosta przedstawiony
schemat powiza pomidzy poszczeglnymi klasami gestw.

Rysunek 25.6. Struktura klas gestw

Chocia do utworzenia gestu nie moemy uywa funkcji wielodotykowoci, istnieje sposb
uwzgldnienia wielu gestw waciwych w obrbie jednego gestu. Aby na przykad zdefiniowa
gest oznaczajcy liter E, potrzebujemy co najmniej dwch gestw waciwych; jeden gest
waciwy moe definiowa grny, boczny i dolny odcinek litery, a drugi gest waciwy posuy do narysowania jej rodkowego odcinka. Moemy take najpierw narysowa za pomoc jednego gestu waciwego pionow lini w literze E, a nastpne trzy gesty waciwe
wykorzysta do narysowania trzech linii poziomych. Istniej rne metody narysowania litery E i na szczcie mamy moliwo zapisania ich wszystkich w bibliotece gestw. Zarejestrujmy gest odpowiedzialny za liter E na kilka rnych sposobw, poniewa uytkownicy
mog korzysta z odmiennych technik rysowania tej litery, a chcemy, eby aplikacja rozpoznaa ten gest bez wzgldu na sposb jego wykonania. Rysunek 25.7 przedstawia rne sposoby rejestrowania litery E.
Utworzenie gestu skadajcego si z wielu gestw waciwych moe stanowi nie lada wyzwanie
na emulatorze. Jak ju wspomnielimy, moemy ponownie narysowa nowy gest na wczeniejszym gecie, ktry zostanie usunity. Skd wic Android wie, kiedy rysujemy gest od nowa,
a kiedy dodajemy jedynie nowy gest waciwy do istniejcego gestu? Android stosuje w tym celu

Rozdzia 25 Ekrany dotykowe

901

Rysunek 25.7. Rne sposoby rejestrowania litery E

warto atrybutu FadeOffset, ktra jest podawana w milisekundach. Jeli po jej przekroczeniu zaczniemy rysowa gest, Android uzna, e cay proces naley przeprowadzi od pocztku. Domylna warto tego atrybutu wynosi 420 milisekund. Oznacza to, e jeli podczas rysowania gestu uniesiemy palec na ponad 420 milisekund, system uzna, e skoczylimy
rysowanie tego gestu, i zostanie on zapamitany w takim stanie. W rzeczywistym urzdzeniu taki czas moe wystarczy do rozpoczcia rysowania kolejnego gestu waciwego. W przypadku emulatora moe nie by tak dobrze. Wszystko zaley od szybkoci stacji roboczej.
Jeeli aplikacja Gestures Builder stwarza problemy z akceptacj gestu skadajcego si z wielu
gestw waciwych, moemy utworzy wasn wersj tej aplikacji i zmodyfikowa domyln
warto atrybutu fadeOffset. Opisalimy wczeniej sposb utworzenia projektu Gestures Builder w rodowisku Eclipse. Postpujmy zgodnie z instrukcjami, a nastpnie przejdmy do pliku
/res/layout/create_gesture.xml i dodajmy atrybut android:fadeOffset="1000" do elementu
GestureOverlayView. Warto atrybutu fadeOffset zostanie powikszona do 1 sekundy
(1000 milisekund). Moemy jednak wstawi tu dowoln warto.
Poszukajmy miejsca, w ktrym s przechowywane gesty. Wiadomo typu Toast w aplikacji
Gestures Builder informuje nas, e gesty s zapisywane w katalogu /sdcard/gestures (lub
/mnt/sdcard/gestures od wersji 2.2 Androida). Skorzystajmy z perspektywy File Explorer w rodowisku Eclipse lub z powoki adb, aby odnale folder /sdcard na emulatorze. Znajdziemy
w nim plik gestures. Zwrmy uwag na jego niewielki rozmiar. Jest to plik binarny, zatem
nie ma moliwoci, aby go rcznie edytowa. Jeli chcemy modyfikowa jego zawarto, musimy otworzy aplikacj Gestures Builder. W trakcie tworzenia aplikacji obsugujcej gesty
musimy skopiowa plik gestures do jej katalogu /res/raw. Dokonujemy tego za pomoc
funkcji File Copy, znajdujcej si w perspektywie File Explorer, lub za pomoc polecenia adb
pull, co pozwala na skopiowanie pliku na dysk twardy, a stamtd do projektu.
Mamy nie tylko moliwo dodawania nowych gestw za pomoc aplikacji Gestures Builder
poprzez dugie kliknicie moemy wywoa menu istniejcego gestu. Dziki znajdujcym
si tu opcjom moemy zmieni nazw gestu lub go usun. Nie mona ponownie rejestrowa
zachowanego gestu, wic jeli nam si nie podoba, musimy go usun i utworzy nowy. Jak ju

902 Android 3. Tworzenie aplikacji


wspomnielimy, czasami pojawia si potrzeba zarejestrowania rnych odmian danego gestu
i nadania im takiej samej nazwy. Nazwa gestu nie musi by niepowtarzalna, chocia zalecane jest, aby identycznie nazwane gesty byy do siebie podobne.
Teraz utworzymy przykadow aplikacj obsugujc nasz nowy plik gestures. Utwrzmy
nowy projekt Androida w rodowisku Eclipse. Listing 25.17 zawiera zarwno plik XML ukadu
graficznego, jak i kod Java aktywnoci.
Listing 25.17. Plik ukadu graficznego oraz kod Java aplikacji wykrywajcej gesty
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Rysuj gesty, a ja odgadn ich przeznaczenie."
/>
<android.gesture.GestureOverlayView
android:id="@+id/gestureOverlay"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gestureStrokeType="multiple"
android:fadeOffset="1000" />
</LinearLayout>

import
import
import
import
import
import
import
import
import
import
import

java.util.ArrayList;
android.app.Activity;
android.gesture.Gesture;
android.gesture.GestureLibraries;
android.gesture.GestureLibrary;
android.gesture.GestureOverlayView;
android.gesture.Prediction;
android.gesture.GestureOverlayView.OnGesturePerformedListener;
android.os.Bundle;
android.util.Log;
android.widget.Toast;

public class MainActivity extends Activity implements OnGesturePerformedListener {


private static final String TAG = "Wykrywanie gestw";
GestureLibrary gestureLib = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

//

gestureLib = GestureLibraries.fromRawResource(this, R.raw.gestures);


gestureLib = GestureLibraries.fromFile("/sdcard/gestures");
if (!gestureLib.load()) {

Rozdzia 25 Ekrany dotykowe

903

Toast.makeText(this, "Nie mona wczyta pliku /sdcard/gestures",


Toast.LENGTH_SHORT).show();
finish();
}

// Przyjrzyjmy si bibliotece gestw, z ktr bdziemy pracowa


Log.v(TAG, "Funkcje biblioteki:");
Log.v(TAG, "Styl ulozenia: " + gestureLib.getOrientationStyle());
Log.v(TAG, "Typ sekwencji: " + gestureLib.getSequenceType());
for( String gestureName : gestureLib.getGestureEntries() ) {
Log.v(TAG, "Dla gestu " + gestureName);
int i = 1;
for( Gesture gesture : gestureLib.getGestures(gestureName) ) {
Log.v(TAG, " " + i + ": ID: " + gesture.getID());
Log.v(TAG, " " + i + ": Ilosc gestow wlasciwych: " +
gesture.getStrokesCount());
Log.v(TAG, " " + i + ": Dlugosc gestu wlasciwego: " + gesture.getLength());
i++;
}
}
GestureOverlayView gestureView =
(GestureOverlayView) findViewById(R.id.gestureOverlay);
gestureView.addOnGesturePerformedListener(this);
}
@Override
public void onGesturePerformed(GestureOverlayView view, Gesture gesture) {
ArrayList<Prediction> predictions = gestureLib.recognize(gesture);
if (predictions.size() > 0) {
Prediction prediction = (Prediction) predictions.get(0);
if (prediction.score > 1.0) {
Toast.makeText(this, prediction.name, Toast.LENGTH_SHORT).show();
for(int i=0;i<predictions.size();i++)
Log.v(TAG, "prognoza " + predictions.get(i).name +
" - wynik = " + predictions.get(i).score);
}
}
}
}

W tym przykadzie bdziemy korzysta z tego samego pliku, ktry zosta zapisany przez aplikacj Gestures Builder. Stosujemy w tym celu metod GestureLibraries.fromFile() wewntrz
metody onCreate(). Pokazujemy jednak rwnie w komentarzach, w jaki sposb mona
uzyska dostp do pliku gestw stanowicego cz naszej aplikacji. Gdybymy wprowadzili
metod fromRawResource(), uylibymy argumentu przyjmujcego posta zwykego identyfikatora zasobw, natomiast plik gestw wstawilibymy do katalogu /res/raw.
Nasza aplikacja nie jest bardzo rozbudowana, lecz jej uruchomienie da nam wiksze pojcie
na temat zjawisk zachodzcych w systemie podczas przetwarzania gestw. W momencie uruchamiania aplikacja wczytuje plik gestw i zapisuje informacje o znalezionych gestach.
Wywietla take wyniki prb dopasowania wzorcowego gestu do gestu narysowanego na ekranie. Wczmy aplikacj wykrywajc gesty, przy oczywistym zaoeniu, e ju uruchomilimy

904 Android 3. Tworzenie aplikacji


aplikacj Gestures Builder oraz zarejestrowalimy gesty w katalogu /sdcard/gestures. Popatrzmy na opis kadego gestu za pomoc identyfikatora, liczb gestw waciwych oraz dugoci poszczeglnych gestw waciwych.
Narysujmy gesty istniejce w bibliotece gestw. Nastpnie narysujmy takie, o ktrych wiemy, e nie s obecne w tej bibliotece. Zobaczmy, co si dzieje w oknie LogCat. Zauwaymy,
e czasami nie s rozpoznawane gesty, ktre powinny zosta zidentyfikowane, oraz e czasami Android rozpoznaje opacznie gesty, przewanie jednak waciwie odgaduje rysowane
symbole. Zauwaymy take, e w przypadku rozpoznania wprowadzonego gestu system
wywietla wyniki wszystkich gestw znajdujcych si w bibliotece, a jeeli nie rozpozna symbolu,
nie uzyskujemy adnego rezultatu.
Zobaczmy take, co si stanie, jeeli bdziemy zbytnio zwleka z rysowaniem gestw waciwych, na przykad wolno rysujc wczeniej opisan liter E. Aplikacja pobierze dotychczas narysowane gesty waciwe i porwna je z bibliotek gestw, wskutek czego otrzymamy najprawdopodobniej bdny wynik, ewentualnie nie zostanie wywietlony aden rezultat. Warto
czasu przerwy pomidzy rysowaniem poszczeglnych gestw waciwych jest kontrolowana
przez atrybut FadeOffset. W tym miejscu warto si chwil zastanowi. Chcemy, aby Android
zacz analizowa gesty tu po zakoczeniu procesu rysowania, jednak nie moemy sprawdzi,
czy uytkownik skoczy wprowadza gest inaczej, ni czekajc pewien czas. Zatem atrybut
FadeOffset spenia dwa zadania: pierwsza rola polega na kontrolowaniu czasu oczekiwania
na wprowadzenie nowego gestu waciwego bdcego czci oglnego gestu, a drug funkcj tego atrybutu jest kontrola czasu oczekiwania na rozpoczcie procesu porwnywania
wprowadzonych gestw z gestami umieszczonymi w bibliotece. Nadanie duej wartoci atrybutowi FadeOffset oznacza dugi czas oczekiwania na rozpoczcie procesu rozpoznawania gestw. Zbyt maa warto tego atrybutu uniemoliwia rysowanie gestu skadajcego si
z wielu gestw waciwych, poniewa Android uzna rysowanie za zakoczone przed wprowadzeniem wszystkich gestw waciwych. Czytelnik musi sam zadecydowa, czy czas 420
milisekund jest wystarczajcy. Mona rwnie wprowadzi ustawienie preferencji, dziki
czemu uytkownicy sami dostosuj czas oczekiwania do wasnych potrzeb.
Skoro omawiamy temat gestw tworzonych przez wiele gestw waciwych, zwrmy uwag, e
klasa GestureOverlayView posiada ustawienie pozwalajce na ustalenie, czy aplikacja ma oczekiwa tego typu gestw. W pliku XML tym atrybutem jest android:gestureStrokeType, a jego
wartoci to single (domylna) lub multiple. Jeeli chcemy wprowadzi moliwo stosowania
gestw tworzonych przez wiksz liczb gestw waciwych, musimy ustanowi ten atrybut. Moemy rwnie skonfigurowa go programowo za pomoc metody setGestureStrokeType(int
type), dla ktrej argumentami s GestureOverlayView.GESTURE_STROKE_TYPE_SINGLE lub
GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE. Klasa GestureOverlayView zawiera
rwnie atrybuty XML i metody pozwalajce na konfigurowanie kolorw i gruboci linii.
Aby utworzy wasn aplikacj obsugujc przetwarzanie gestw, musimy zadecydowa, na
ktre gesty bdzie reagowaa ta aplikacja, nastpnie musimy utworzy bibliotek tych gestw
i zaimplementowa prawdopodobnie w klasie Activity interfejs onGesturePerformed
Listener rozpoznajcy je i podejmujcy odpowiednie dziaania.
Czy mona sprawi, aby uytkownicy rejestrowali wasne gesty? Na przykad mog zechcie
stosowa dla okrelonej operacji inny gest ni udostpniony w aplikacji. Jest to moliwe, jednak
musimy sprawi, aby plik biblioteki gestw mg by przez innych modyfikowany, natomiast rozsdn lokalizacj dla tego pliku jest karta SD. Cakiem atwym procesem jest utworzenie nowego pliku biblioteki gestw, odczytywanie domylnych gestw z pliku biblioteki

Rozdzia 25 Ekrany dotykowe

905

umieszczonego w aplikacji i zamiana tych domylnych gestw na gesty dostosowane przez


uytkownika. Moemy przejrze wspomnian ju implementacj aplikacji Gestures Builder,
aby pozna kod rejestratora gestw. Mona rwnie napisa aplikacj Gestures Builder odpowiadajc na intencje, dziki czemu w prosty sposb bdzie wywoywana aktywno dodajca nowe gesty. Ewentualnie istnieje moliwo zarejestrowania wycznie gestw uytkownika
w nowym, modyfikowalnym pliku biblioteki gestw, a nastpnie wczytania obydwu bibliotek do
aplikacji naszej i uytkownika. Wewntrz metody onGesturePerformed() zostaaby najpierw
zaimplementowana metoda recognize(), prbujca rozpoznawa gesty z biblioteki uytkownika, pniej z naszej. Aplikacja porwnywaaby najblisze przewidywania z obydwu bibliotek
i na tej podstawie podejmowaaby decyzj co do wyboru gestu.

Odnoniki
Poniej prezentujemy odnoniki do materiaw, ktre pomog Czytelnikowi zrozumie zagadnienia omawiane w niniejszym rozdziale.
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu zbir projektw bezporednio
zwizanych z ksik. Prezentowane w tym rozdziale projekty zostay umieszczone
w katalogu ProAndroid3_R25_EkranyDotykowe. W katalogu znajduje si rwnie plik
Czytaj.TXT, zawierajcy instrukcj importowania projektw do rodowiska Eclipse.
http://www.ted.com/talks/jeff_han_demos_his_breakthrough_touchscreen.html Jeff
Han demonstruje swj interfejs uytkownika obsugujcy wielodotykowo na konferencji
TED w 2006 roku wietny materia.
http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
wpis dotyczcy wielodotykowoci, w ktrym ukazany jest inny sposb implementacji
klasy GestureDetector wewntrz rozszerzenia widoku.

Podsumowanie
W tym rozdziale zademonstrowalimy sposb obsugi ekranw dotykowych, poczwszy od
aplikacji rozpoznajcej dotykanie jednym palcem, nastpnie za zajlimy si kwesti wielodotykowoci. Wyjanilimy zasad dziaania funkcji obsugi dotyku w pracy z mapami, a take
omwilimy niektre pomocnicze klasy i metody uatwiajce przetwarzanie zdarzenia dotyku w aplikacji obsugujcej mapy. W ostatnim podrozdziale przeanalizowalimy mechanizm
gestw, umoliwiajcy wprowadzanie danych w nowy i prawdopodobnie prostszy sposb ni
za pomoc klawiatury i innych kontrolek interfejsu uytkownika.

906 Android 3. Tworzenie aplikacji

R OZDZIA

26
Czujniki

Urzdzenia obsugujce Androida czsto zawieraj wbudowane czujniki sprztowe,


a Android posiada mechanizmy pozwalajce na korzystanie z tych podzespow.
Praca z czujnikami moe by ciekawa. Moliwo dokonywania pomiarw w wiecie
zewntrznym oraz wykorzystywanie otrzymywanych danych w oprogramowaniu
s niezmiernie emocjonujce. Mamy tu do czynienia z tym rodzajem dowiadczenia programistycznego, ktrego nie zaznamy, pracujc na standardowym komputerze, stojcym na biurku lub w serwerowni. Potencja aplikacji korzystajcych z czujnikw jest olbrzymi i mamy nadziej, e uda nam si zachci Czytelnikw do jego
penej eksploatacji.
W niniejszym rozdziale zajmiemy si dostpn w Androidzie struktur czujnikw.
Wytumaczymy, czym s czujniki, w jaki sposb uzyskujemy ich odczyty, a take przeanalizujemy rodzaje i zastosowania otrzymywanych danych. Android posiada ju
kilka zdefiniowanych rodzajw czujnikw, naley jednak oczekiwa pojawienia si
zupenie nowych kategorii tych elementw elektronicznych, ktre zostan doczone
do architektury systemu.

Czym jest czujnik?


W przypadku Androida czujnik jest wchodzcym w skad urzdzenia podzespoem
elektronicznym, pobierajcym dane pochodzce ze rodowiska zewntrznego. Dane
te s nastpnie przekazywane do aplikacji. Z kolei aplikacje wykorzystuj odczyty
czujnikw do informowania uytkownika o warunkach otoczenia, sterowania w grach,
pracy w rzeczywistoci rozszerzonej1 lub do dostarczania uytecznych danych
roboczych. Czujniki s jednokierunkowe ich wyniki mona wycznie odczytywa (wyjtkiem jest czujnik NCF, ktrym zajmiemy si w dalszej czci rozdziau).
To nieco upraszcza ich wykorzystywanie na potrzeby oprogramowania: trzeba skonfigurowa obiekt nasuchujcy, odczyta dane pochodzce z czujnikw i nastpnie

Rzeczywisto rozszerzona (ang. Augmented Reality) jest systemem umoliwiajcym


czenie wiata rzeczywistego z danymi generowanymi za pomoc komputerw.
Najbardziej znanym przykadem AR jest wykorzystanie obrazu z kamery, na ktry
nakada si grafik 3D generowan w czasie rzeczywistym przyp. red.

908 Android 3. Tworzenie aplikacji


na bieco je przetwarza. Podobne dziaanie do czujnikw posiadaj odbiorniki systemu GPS.
W rozdziale 17. pokazalimy, jak skonfigurowa obiekty nasuchujce wobec aktualizacji pooenia uzyskiwanych z systemu GPS, tak aby aktualizacje pooenia od razu byy przetwarzane na
informacje zrozumiae dla uytkownika. Mimo e system GPS przypomina czujniki, nie stanowi
on jednak czci omawianej w tym rozdziale architektury.
Wrd dostpnych rodzajw czujnikw bdziemy mieli do czynienia z takimi, jak:
czujnik owietlenia,
czujnik zblieniowy,
termometr,
czujnik cinienia,
yroskop,
akcelerometr,
magnetometr,
czujnik orientacji pooenia,
czujnik grawitacji (Android 2.3),
czujnik przypieszenia liniowego (Android 2.3),
czujnik wektora rotacji (Android 2.3),
czujnik NFC (ang. Near Field Communication komunikacja bliskiego pola;
Android 2.3).
Czujnik NFC rni si od pozostaych. Zajmiemy si nim oddzielnie w dalszej czci rozdziau,
poniewa uzyskujemy do niego dostp w zupenie inny sposb ni w przypadku pozostaych
rodzajw czujnikw.

Wykrywanie czujnikw
Nie powinnimy jednak zakada, e kade urzdzenie bdzie wyposaone we wszystkie wymienione rodzaje czujnikw. W rzeczywistoci wiele urzdze zostao wyposaonych tylko
w cz tych ukadw. Na przykad emulator Androida posiada wycznie akcelerometr. W jaki
sposb moemy wic dowiedzie si, ktre czujniki s dostpne w urzdzeniu? Istniej dwa
sposoby jeden bezporedni, drugi poredni.
Pierwszy sposb polega na zadaniu od klasy SensorManager listy dostpnych czujnikw.
W efekcie otrzymamy list obiektw, dla ktrych moemy ustanowi obiekty nasuchujce,
a nastpnie pobra z nich dane. Rozwizanie to zostanie omwione w dalszej czci rozdziau.
Zakadamy tutaj, e aplikacja zostaa ju zainstalowana w urzdzeniu; co jednak naley zrobi,
w przypadku gdy urzdzenie nie ma czujnika wymaganego przez nasz program?
W tym miejscu moemy wykorzysta drugie rozwizanie. Moemy zdefiniowa w pliku
AndroidManifest.xml funkcje urzdzenia niezbdne do dziaania aplikacji. Jeeli nasz program
wymaga obecnoci czujnika zblieniowego, moemy to okreli w pliku manifecie za pomoc
nastpujcego wiersza:
<uses-feature android:name="android.hardware.sensor.proximity" />

W efekcie dana aplikacja zostanie zainstalowana wycznie w urzdzeniu posiadajcym czujnik


zblieniowy, wic jej zainstalowanie bdzie jednoznaczne z obecnoci tego podzespou.

Rozdzia 26 Czujniki

909

Jakie informacje moemy uzyska na temat czujnika?


Chocia stosowanie znacznikw uses-feature w pliku manifecie gwarantuje nam wiedz
o obecnoci wymaganego czujnika w danym urzdzeniu, to nie uzyskujemy za ich pomoc penych informacji na temat danego ukadu elektronicznego. Napiszemy wic prost aplikacj
uzyskujc dane na temat czujnika. Na listingu 26.1 zostay zamieszczone ukad graficzny
aplikacji oraz kod Java klasy MainActivity.
Moemy pobra wszystkie projekty omawiane w tym rozdziale. Na kocu rozdziau
znajduje si adres URL witryny z utworzonymi projektami. W ten sposb bdzie je
mona zaimportowa bezporednio do rodowiska Eclipse.
Listing 26.1. Ukad graficzny i kod Java aplikacji Lista czujnikw
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ScrollView android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1" >
<TextView android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</ScrollView>
</LinearLayout>

// Jest to plik MainActivity.java


import
import
import
import
import
import
import

java.util.HashMap;
java.util.List;
android.app.Activity;
android.hardware.Sensor;
android.hardware.SensorManager;
android.os.Bundle;
android.widget.TextView;

public class MainActivity extends Activity {


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView text = (TextView)findViewById(R.id.text);
SensorManager mgr =
(SensorManager) this.getSystemService(SENSOR_SERVICE);
List<Sensor> sensors = mgr.getSensorList(Sensor.TYPE_ALL);
StringBuilder message = new StringBuilder(2048);
message.append("Czujniki znalezione w urzdzeniu:\n");

910 Android 3. Tworzenie aplikacji

for(Sensor sensor : sensors) {


message.append(sensor.getName() + "\n");
message.append(" Typ: " +
sensorTypes.get(sensor.getType()) + "\n");
message.append(" Producent: " +
sensor.getVendor() + "\n");
message.append(" Wersja: " +
sensor.getVersion() + "\n");
message.append(" Rozdzielczo: " +
sensor.getResolution() + "\n");
message.append(" Maksymalny zasig: " +
sensor.getMaximumRange() + "\n");
message.append(" Zasilanie: " +
sensor.getPower() + " mA\n");
}
text.setText(message);
}
private HashMap<Integer, String> sensorTypes =
new HashMap<Integer, String>();
{
sensorTypes.put(Sensor.TYPE_ACCELEROMETER, "TYP_AKCELEROMETR");
sensorTypes.put(Sensor.TYPE_GYROSCOPE, "TYP_YROSKOP");
sensorTypes.put(Sensor.TYPE_LIGHT, "TYP_OWIETLENIE");
sensorTypes.put(Sensor.TYPE_MAGNETIC_FIELD, "TYP_POLE_MAGNETYCZNE");
sensorTypes.put(Sensor.TYPE_ORIENTATION, "TYP_ORIENTACJA");
sensorTypes.put(Sensor.TYPE_PRESSURE, "TYP_CINIENIE");
sensorTypes.put(Sensor.TYPE_PROXIMITY, "TYP_ODLEGO");
sensorTypes.put(Sensor.TYPE_TEMPERATURE, "TYP_TEMPERATURA");
sensorTypes.put(Sensor.TYPE_GRAVITY, "TYP_GRAWITACJA");
sensorTypes.put(Sensor.TYPE_LINEAR_ACCELERATION,
"TYP_PRZYPIESZENIE_LINIOWE");
sensorTypes.put(Sensor.TYPE_ROTATION_VECTOR,
"TYP_WEKTOR_OBROTU");
}
}

Warto zwrci uwag, e w tej przykadowej aplikacji wprowadzilimy kontrolk ScrollView,


poniewa z duym prawdopodobiestwem uzyskamy wicej wierszy, ni mona by pomieci
na ekranie. Rozpoczynamy metod onCreate() od uzyskania odniesienia do klasy Sensor
Manager. Moe istnie tylko jedno takie odniesienie, wic odczytujemy je jako usug systemow.
Nastpnie wywoujemy metod getSensorList(), aby uzyska list czujnikw. Zostaj wywietlone informacje dotyczce kadego dostpnego rodzaju czujnika. Na rysunku 26.1 widzimy rezultaty.
Powinnimy wyjani kilka spraw na temat uzyskanych informacji o czujnikach. Dziki wartoci
Typ poznajemy podstawow kategori czujnika, lecz nie uzyskujemy szczegw na jego temat.
Czujnik owietlenia pozostaje czujnikiem owietlenia, ale istniej jego rne odmiany, w zalenoci od urzdzenia. Na przykad rozdzielczo tego czujnika w jednym urzdzeniu moe by
wiksza ni w innym. Gdy okrelamy potrzeb obecnoci czujnika w znaczniku <uses-feature>,
nie potrafimy z gry okreli, na jak odmian podzespou natrafimy. Musimy wic wysa do
samego urzdzenia zapytanie o dane czujnika i dostosowa kod do otrzymanych wynikw.

Rozdzia 26 Czujniki

911

Rysunek 26.1. Wyniki uzyskane za pomoc aplikacji Lista czujnikw

Wartoci uzyskiwane dla rozdzielczoci i maksymalnego zasigu bd podawane w jednostkach


mierzonych przez dany czujnik. Warto zasilania podawana jest w miliamperach (mA) i reprezentuje natenie prdu pobieranego przez czujnik z baterii; im mniejsza warto, tym lepiej.
Wiemy ju teraz, jakie czujniki mamy do dyspozycji, zatem w jaki sposb moemy pobiera od
nich dane? Jak ju wczeniej wyjanilimy, konfigurujemy obiekt nasuchujcy wobec przesyanych danych. Przeanalizujmy teraz tak sytuacj.

Pobieranie zdarze generowanych przez czujniki


Czujniki zaczn przesya dane do aplikacji po zarejestrowaniu odczytujcego je obiektu nasuchujcego. Gdy obiekt ten nie nasuchuje, czujnik moe zosta wyczony, dziki czemu oszczdzamy energi baterii, musimy wic si upewni, e bdziemy nasuchiwa zdarze czujnikw
jedynie wtedy, gdy bd potrzebne. Skonfigurowanie obiektu nasuchujcego nie jest w tym przypadku skomplikowan czynnoci. Przykadowo naley mierzy poziom natenia wiata za
pomoc czujnika owietlenia. Na listingu 26.2 widzimy kod Java sucy do obsugi tego zadania.
Wykorzystamy w tym przykadzie ukad graficzny zdefiniowany na listingu 26.1.
Listing 26.2. Kod Java aplikacji Monitor czujnika owietlenia
// Jest to plik MainActivity.java
import
import
import
import
import
import
import

android.app.Activity;
android.hardware.Sensor;
android.hardware.SensorEvent;
android.hardware.SensorEventListener;
android.hardware.SensorManager;
android.os.Bundle;
android.widget.TextView;

912 Android 3. Tworzenie aplikacji


public class MainActivity extends Activity implements SensorEventListener {
private SensorManager mgr;
private Sensor light;
private TextView text;
private StringBuilder msg = new StringBuilder(2048);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mgr = (SensorManager) this.getSystemService(SENSOR_SERVICE);
light = mgr.getDefaultSensor(Sensor.TYPE_LIGHT);
text = (TextView) findViewById(R.id.text);
}
@Override
protected void onResume() {
mgr.registerListener(this, light,
SensorManager.SENSOR_DELAY_NORMAL);
super.onResume();
}
@Override
protected void onPause() {
mgr.unregisterListener(this, light);
super.onPause();
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
msg.insert(0, sensor.getName() + " dokadno zmieniona: " +
accuracy + (accuracy==1?" (NISKA)":(accuracy==2?" (REDNIA)":
" (WYSOKA)")) + "\n");
text.setText(msg);
text.invalidate();
}
public void onSensorChanged(SensorEvent event) {
msg.insert(0, "Otrzymano zdarzenie czujnika: " + event.values[0] +
" jednostek SI: luks\n");
text.setText(msg);
text.invalidate();
}
}

W tej przykadowej aplikacji ponownie uzyskujemy odniesienie do klasy SensorManager, jednak tym razem otrzymujemy informacje wycznie na temat czujnika owietlenia. Nastpnie
w metodzie onResume() naszej aktywnoci konfigurujemy obiekt nasuchujcy, a w metodzie
onPause() nastpuje jego wyrejestrowanie. Nie chcemy zajmowa si poziomami owietlenia,
gdy aplikacja nie znajduje si na pierwszym planie.

Rozdzia 26 Czujniki

913

Wewntrz metody registerListener() przekazywana jest warto parametru okrelajcego


czsto odwieania odczytw czujnika. Parametr ten moe przyjmowa nastpujce wartoci:
SENSOR_DELAY_NORMAL,
SENSOR_DELAY_UI,
SENSOR_DELAY_GAME,
SENSOR_DELAY_FASTEST.
Bardzo wany jest wybr odpowiedniej wartoci odwieania. Niektre czujniki s bardzo czue
i bd generowa du liczb wynikw w krtkim czasie. Jeeli wybierzemy warto SENSOR_
DELAY_FASTEST, moemy nawet przekroczy moliwoci ich przetwarzania przez aplikacj.
W zalenoci od operacji przeprowadzanych na odczytach czujnika istnieje moliwo, e pami
urzdzenia zostanie zapeniona w takim stopniu, i proces jej oczyszczania okae si zbyt mao
wydajny, co bdzie skutkowao widocznym zwolnieniem, a nawet przestojami w dziaaniu urzdzenia. Z drugiej strony niektre czujniki wymagaj maksymalnej czstoci odczytw; dotyczy
to w szczeglnoci czujnika wektora obrotu.
Poniewa w naszej aktywnoci zaimplementowalimy interfejs SensorEventListener, posiadamy dwie metody zwrotne zwizane ze zdarzeniami czujnika: onAccuracyChanged() oraz
onSensorChanged(). Pierwsza metoda bdzie nas informowaa o zmianie dokadnoci czujnika
(lub czujnikw, gdy mona j wywoa wobec wikszej liczby tych ukadw elektronicznych).
Warto tego parametru moe wynosi 0, 1, 2 lub 3, co oznacza dokadno, odpowiednio: niemiarodajn, nisk, redni lub wysok. Niemiarodajna dokadno nie musi wcale oznacza,
e urzdzenie jest popsute; w ten sposb zazwyczaj wskazuje si potrzeb kalibracji czujnika.
Druga metoda okrela moment zmiany poziomu natenia wiata, dziki czemu otrzymujemy
zdarzenie zawierajce informacje o nowej wartoci (wartociach) pochodzcej z czujnika.
Obiekt SensorEvent zawiera kilka elementw, z ktrych jeden stanowi tablic wartoci zmiennoprzecinkowych. W przypadku czujnika owietlenia tylko pierwsza warto zmiennoprzecinkowa ma znaczenie. Jest to wyraona w luksach warto natenia owietlenia zarejestrowana
przez urzdzenie. W naszej przykadowej aplikacji tworzymy wiadomoci poprzez nakadanie
nowych komunikatw na stare, a nastpnie ich wywietlanie w kontrolce TextView. Najnowsze
odczyty bd zawsze wywietlane na grze ekranu.
Po uruchomieniu aplikacji (oczywicie w fizycznym urzdzeniu, gdy emulator nie posiada
czujnika owietlenia) zauwaymy, e na pocztku nie bdzie wywietlany aden wynik. Wystarczy jednak zmieni rdo wiata padajce na lew grn cz urzdzenia. Najprawdopodobniej wanie tam jest umieszczony czujnik owietlenia. Jeeli przyjrzymy si temu miejscu
bardzo uwanie, zauwaymy ciemniejszy punkt pod wywietlaczem. To jest wanie czujnik
owietlenia. Jeeli zakryjemy go palcem, warto natenia wiata prawdopodobnie bardzo si
zmniejszy (chocia moe nie osign wartoci 0). Powinny zacz si pojawia komunikaty
informujce o zmianie poziomu owietlenia.
W momencie zakrycia czujnika owietlenia moemy zaobserwowa rwnie wczenie
podwietlenia klawiatury (jeeli urzdzenie jest wyposaone w tak funkcj). Wynika to
z faktu, e system wykry zmniejszon ekspozycj na wiato i podwietla klawisze, aby
uatwi korzystanie z telefonu.

914 Android 3. Tworzenie aplikacji

Problemy pojawiajce si
podczas uzyskiwania danych z czujnikw
Architektura czujnikw w Androidzie wie si z pewnymi problemami, ktrych powinnimy
by wiadomi. Ten aspekt obsugi czujnikw wcale nie jest prosty. W pewnych przypadkach
problemy s bardzo atwe do rozwizania, w innych jest to niemoliwe lub bardzo trudne do
osignicia.

Metoda onAccuracyChanged() zawsze zwraca t sam warto


A do wersji 2.2 Androida metoda zwrotna onAccuracyChanged() za kadym razem bya wywoywana w momencie pojawienia si nowego odczytu i parametr dokadnoci przybiera
warto 3 (wysoka dokadno). Dobrze jest dostosowa zmiany dokadnoci danych czujnika, jednak niekiedy metoda ta bdzie wywoywana za kadym razem, nawet jeeli dokadno odczytu nie ulega zmianie.

Brak bezporedniego dostpu do wartoci czujnika


Prawdopodobnie Czytelnik zdy ju zauway, e nie ma bezporedniego sposobu na uzyskiwanie biecej wartoci odczytu z czujnika. Jedynym rozwizaniem jest implementacja obiektu
nasuchujcego. Oznacza to, e nawet po ustawieniu tego obiektu nie mamy pewnoci, czy uzyskamy okrelone dane w interesujcym nas przedziale czasowym. Dobr stron sytuacji jest to,
e metoda zwrotna jest asynchroniczna i nie bdzie blokowaa nam gwnego wtku interfejsu
uytkownika w oczekiwaniu na dane z czujnika. Mimo to projektujc aplikacj, naley j przygotowa na to, i dane z czujnika nie bd dostpne w zakadanym przez nas momencie.
Moliwe jest uzyskanie bezporedniego dostpu do tych danych za pomoc natywnego kodu,
a take funkcji JNI2 Androida. W tym celu trzeba zna niskopoziomowe, natywne wywoania
interfejsu sterownika interesujcego nas czujnika, a ponadto wiedzie, w jaki sposb skierowa
interfejs ponownie do systemu. Jest to wic moliwe do zrealizowania, ale nieatwe zadanie.

Wartoci czujnika s zbyt wolno wysyane


Nawet po wstawieniu wartoci SENSOR_DELAY_FASTEST nie bdziemy otrzymywa nowych wartoci czciej ni co 20 ms (w zalenoci od urzdzenia). Jeeli potrzebujemy wikszej czstotliwoci
odwieania danych, ni moe nam zapewni rzadko stosowane ustawienie SENSOR_DELAY_
FASTEST, moemy podobnie jak w poprzednim przypadku wykorzysta natywny kod
oraz interfejs JNI, bdzie to jednak skomplikowane rozwizanie.

W Androidzie 2.1 czujniki wyczaj si wraz z ekranem


W wersji 2.1 Androida stwierdzono problemy z aktualizacjami odczytw czujnikw, spowodowane ich wyczaniem w momencie wyczenia wywietlacza. Najwidoczniej kto uzna, e
wietnym rozwizaniem bdzie uniemoliwienie wysyania odczytw czujnika przy wyczonym
ekranie, nawet w przypadku ustawienia blokady przechodzenia urzdzenia w stan upienia przez
aplikacj (ktra najprawdopodobniej korzysta z usugi). Gwn przyczyn problemu jest wyrejestrowanie obiektu nasuchujcego w momencie wyczenia ekranu. Istnieje kilka rozwiza
2

Technologia JNI (ang. Java Native Interface) stanowi mechanizm pozwalajcy na uruchamianie
kodu napisanego w jzyku natywnym (np. C/C++) wewntrz rodowiska Java przyp. tum.

Rozdzia 26 Czujniki

915

tego problemu. Przede wszystkim moemy skonfigurowa czas wyganicia wywietlacza, dziki
czemu nie zostanie on wyczony podczas otrzymywania aktualizacji odczytw. Zasadnicz
wad tego rozwizania jest wysoki koszt energetyczny, co wie si z szybszym rozadowywaniem baterii. Aby ustanowi czas wygaszania wywietlacza, musimy wprowadzi fragment
podobny do ukazanego poniej, gdzie myDelay oznacza czas wyraony w milisekundach:
Settings.System.putInt(getContentResolver(),
Settings.System.SCREEN_OFF_TIMEOUT, myDelay);

Wprowadzenie wartoci -1 sprawi, e ekran nigdy nie zostanie wyczony. Aplikacja bdzie wymagaa rwnie przydzielenia odpowiedniego uprawnienia (android.permission.WRITE_SETTINGS)
w pliku AndroidManifest.xml. Drug wad tego rozwizania jest to, e ustawienie czasu wygasania wywietlacza ma charakter globalny. Jeli jaka aplikacja zmieni t warto, Android
zmodyfikuje j wszdzie. W rzeczywistoci wic aplikacja powinna zapamita poprzedni warto tego ustawienia i przywrci j w momencie koczenia pracy. Jednak nawet po wdroeniu
takiego rozwizania moemy natrafi na problemy, poniewa uytkownik po uruchomieniu
aplikacji moe si zastanawia, dlaczego wywietlacz nie zostaje wyczony po pewnym czasie,
przej do panelu ustawie i wprowadzi zupenie inn warto wygaszania ekranu, nastpnie
powrci do aplikacji i dopiero wtedy j zamkn. Nie musimy wspomina, e po takich modyfikacjach ustawie ekran moe zosta wyczony w czasie pracy aplikacji, w efekcie czego
przestanie ona otrzymywa odczyty czujnika.
Technika wyrejestrowania i zarejestrowania
dotyczca cigych aktualizacji odczytw czujnika
Jedna z metod zapewnienia aplikacji otrzymywania nieprzerwanego strumienia odczytw polega
na zarejestrowaniu odbiorcy powiadomie zwizanych z wyczaniem ekranu, nastpnie wyrejestrowaniu obiektu nasuchujcego zdarzenia czujnika i jego ponownym zarejestrowaniu
w metodzie onReceive() klasy BroadcastReceiver. Rozwizanie to okazywao si skuteczne
w niektrych urzdzeniach pracujcych pod kontrol Androida w wersji 2.1, ale nie we wszystkich.
Poniewa nasza aplikacja w normalnych warunkach zostaje zatrzymana w momencie wyczenia
wywietlacza, najpierw musimy ustanowi czciow blokad przechodzenia urzdzenia w stan
wstrzymania (ang. wake lock), aby program pozostawa aktywny po wygaszeniu ekranu. W naszym przykadzie wykorzystujemy aktywno, jednak w rzeczywistej aplikacji najprawdopodobniej umiecilibymy kod obiektu nasuchujcego wewntrz usugi. Na listingu 26.3 pokazalimy przykadow implementacj tego pomysu w postaci aktywnoci.
Listing 26.3. Rozwizywanie problemw za pomoc wyczania obiektw SensorListener
package com.androidbook.sensor.accel;

// Jest to plik MainActivity.java


import
import
import
import
import
import
import
import
import
import

java.io.BufferedWriter;
java.io.FileWriter;
java.io.IOException;
java.text.SimpleDateFormat;
java.util.Date;
android.app.Activity;
android.content.BroadcastReceiver;
android.content.Context;
android.content.Intent;
android.content.IntentFilter;

916 Android 3. Tworzenie aplikacji


import
import
import
import
import
import
import
import
import
import

android.hardware.Sensor;
android.hardware.SensorEvent;
android.hardware.SensorEventListener;
android.hardware.SensorManager;
android.os.Bundle;
android.os.Environment;
android.os.PowerManager;
android.os.PowerManager.WakeLock;
android.provider.Settings;
android.util.Log;

public class MainActivity extends Activity implements SensorEventListener {


private static final String TAG = "AccelerometerRecordToFile";
private WakeLock mWakelock = null;
private SensorManager mMgr;
private Sensor mAccel;
private BufferedWriter mLog;
final private SimpleDateFormat mTimeFormat =
new SimpleDateFormat("HH:mm:ss - ");
private int mSavedTimeout;
*
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mMgr = (SensorManager) this.getSystemService(SENSOR_SERVICE);
mAccel = mMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

// Konfiguruje plik dziennika, w ktrym bd zapisywane informacje. Dodamy go,


// w przypadku gdy nasza aktywno zostanie uruchomiona ponownie w rodku
// eksperymentu.
try {
String filename =
Environment.getExternalStorageDirectory().getAbsolutePath() +
"/accel.log";
mLog = new BufferedWriter(new FileWriter(filename, true));
}
catch(Exception e) {
Log.e(TAG, "Nie mozna zainicjalizowac pliku dziennika");
e.printStackTrace();
finish();
}
PowerManager pwrMgr =
(PowerManager) this.getSystemService(POWER_SERVICE);
mWakelock = pwrMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"Accel");
mWakelock.acquire();

// Zapisuje biec warto czasu wygaszenia ekranu, a nastpnie


// zmniejsza j
try {
mSavedTimeout = Settings.System.getInt(getContentResolver(),
Settings.System.SCREEN_OFF_TIMEOUT);

Rozdzia 26 Czujniki

}
catch(Exception e) {
mSavedTimeout = 120000;

// Domylna warto wynosi 2 minuty,


// jeli nie moemy odczyta wartoci biecej

}
Settings.System.putInt(getContentResolver(),
Settings.System.SCREEN_OFF_TIMEOUT, 5000);

// 5 sekund

}
public BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
writeLog("Ekran zostal wylaczony");

// Wyrejestruje obiekt nasuchujcy i zarejestruje go ponownie.


// Powinno by to konieczne wycznie w wersji 2.1 Androida, chocia
// nie zaszkodzi wstawi to rozwizanie rwnie dla innych wersji systemu.
mMgr.unregisterListener(MainActivity.this);
mMgr.registerListener(MainActivity.this, mAccel,
SensorManager.SENSOR_DELAY_NORMAL);
}
}
};
@Override
protected void onStart() {
writeLog("rozpoczynanie...");
mMgr.registerListener(this, mAccel,
SensorManager.SENSOR_DELAY_NORMAL);
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(mReceiver, filter);
super.onStart();
}
@Override
protected void onStop() {
writeLog("zatrzymywanie...");
mMgr.unregisterListener(this, mAccel);
unregisterReceiver(mReceiver);
try {
mLog.flush();
} catch (IOException e) {

// Ignoruje wszelkie bdy wystpujce w pliku dziennika


}
super.onStop();
}
@Override
protected void onDestroy() {
writeLog("zamykanie...");
try {
mLog.flush();

917

918 Android 3. Tworzenie aplikacji


mLog.close();
}
catch(Exception e) {

// Ignoruje wszelkie bdy wystpujce w pliku dziennika


}

// Odczytuje pierwotn warto czasu wyganicia ekranu


Settings.System.putInt(getContentResolver(),
Settings.System.SCREEN_OFF_TIMEOUT, mSavedTimeout);
mWakelock.release();
super.onDestroy();
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {

// Ignoruje
}
public void onSensorChanged(SensorEvent event) {
writeLog("Uzyskano zdarzenie czujnika: " + event.values[0] + ", " +
event.values[1] + ", " + event.values[2]);
}
private void writeLog(String str) {
try {
Date now = new Date();
mLog.write(mTimeFormat.format(now));
mLog.write(str);
mLog.write("\n");
}
catch(IOException ioe) {
ioe.printStackTrace();
}
}
}

Nie musimy si przejmowa ukadem graficznym, gdy ta przykadowa aplikacja bdzie wywietlaa jedynie swoj nazw. Nie interesuj nas rwnie uprawnienia, zatem na listingu
26.4 prezentujemy plik AndroidManifest.xml.
Listing 26.4. Plik AndroidManifest.xml aplikacji Monitor akcelerometru
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0" package="com.androidbook.sensor.accel">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

Rozdzia 26 Czujniki

919

</activity>
</application>
<uses-sdk android:minSdkVersion="3" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>

Gwnym zadaniem tego przykadowego kodu jest zapisywanie zdarze akcelerometru w pliku
dziennika. Musimy umieci czciow blokad przechodzenia urzdzenia w stan upienia
wewntrz metody onCreate(), dziki czemu aplikacja nie bdzie wstrzymywana w momencie
wyczenia ekranu (blokady przechodzenia urzdzenia w stan upienia zostay omwione w rozdziale 14.). Ustanawiamy rwnie czas wyganicia ekranu na 5 sekund, czyli wywietlacz zostanie wyczony do szybko, jednak jego poprzednia warto zostaje zapamitana i bdzie
przywrcona w momencie wywoania metody onDestroy().
Modyfikujemy czas wyganicia ekranu jedynie w celu sprawdzenia, co si stanie
z obiektem nasuchujcym zdarzenia czujnika. W rzeczywistej aplikacji uytkowej
nie stosowalibymy tego rozwizania.

Konfigurujemy rwnie odbiorc BroadcastReceiver, ktry zostaje powiadomiony o wyczeniu wywietlacza. Zapisujemy t informacj w metodzie onReceive(), a nastpnie wprowadzamy omawiane obejcie dla wersji 2.1 Androida, pozwalajce na dalsze otrzymywanie aktualizacji odczytw z czujnika. W metodzie onStart() zostaje zarejestrowany obiekt nasuchujcy
zdarzenia czujnika, a take odbiorca komunikatw. Obydwa te obiekty zostaj wyrejestrowane w metodzie onStop(). Korzystamy z metod onStart() i onStop() zamiast onResume()
i onPause(), poniewa chcemy nasuchiwa zdarzenia czujnika nawet po przejciu uytkownika do innej aktywnoci w trakcie dziaania programu.
Metoda onDestroy() zapewni oczyszczenie, oprnienie i zamknicie pliku dziennika. W przeciwiestwie do poprzedniego przykadu, metoda onAccuracyChanged() nie wykonuje adnej
czynnoci. Dane zdarze s zapisywane do pliku dziennika w metodzie onSensorChanged().
Taki wzr pracy z obiektem nasuchujcym zdarzenia czujnika powinnimy stosowa rwnie
w zwykej, uytkowej aplikacji. Inaczej ni w przypadku poprzedniego przykadu, gdzie nie przejmowalimy si stanem wstrzymania urzdzenia, najprawdopodobniej bdziemy musieli wprowadzi blokad przechodzenia urzdzenia w stan wstrzymania, gwarantujc pobieranie zdarze przez aplikacj nawet po wyczeniu ekranu. Pamitajmy, e jeeli naszym systemem
docelowym bdzie Android 2.2 lub nowszy, nie powinnimy si przejmowa procesami rejestrowania i wyrejestrowywania obiektu nasuchujcego w odbiorcy BroadcastReceiver. Rwnie Android w wersji 2.0 i starszych nie powinien sprawia problemw.
Aplikacja, ktr pokazalimy, jest bardzo ciekawa. Proponujemy, aby przetestowa j w nastpujcy sposb:
1. Zainstaluj aplikacj, nastpnie odcz urzdzenie od stacji roboczej, aby nie byo ono
poczone z kablem USB (czasami z tego powodu ekran pozostaje cay czas wczony
pomimo wprowadzonych ustawie).
Kiedy aplikacja si uruchomi, moesz porusza w przestrzeni urzdzeniem, a po piciu
sekundach wywietlacz zostanie wyczony.

920 Android 3. Tworzenie aplikacji


2. Poruszaj dalej urzdzeniem, aby uruchamia zdarzenia akcelerometru, a nastpnie
po kilku sekundach odblokuj urzdzenie i wcinij przycisk cofania, aby zakoczy
dziaanie aplikacji.
W gwnym katalogu karty SD urzdzenia znajdziemy plik dziennika, nazwany
accel.log.
3. Podcz ponownie urzdzenie do stacji roboczej, nastpnie skopiuj plik accel.log
i przejrzyj go.
Powinien by widoczny pocztkowy komunikat, wiele komunikatw o zdarzeniach,
a nastpnie informacja Ekran zosta wyczony. W zalenoci od urzdzenia oraz
wersji systemu tu po nim mog si pojawi kolejne komunikaty lub przerwa w napywie
komunikatw do momentu odblokowania telefonu i zamknicia aplikacji.
Technika pozostawiania wczonego ekranu
przy cigych aktualizacjach odczytw czujnika
Istnieje jeszcze jedno obejcie problemu dotyczcego wersji 2.1 Androida. Przypomnijmy, e
podstawowy problem polega na tym, e w niektrych urzdzeniach czujniki zostaj po prostu
wyczone w momencie wygaszenia ekranu. Rozwizaniem zatem moe by pozostawienie wczonego wywietlacza. Na listingu 26.5 widzimy alternatywn wersj utworzonego w poprzednim przykadzie odbiorcy BroadcastReceiver. Rnica polega na tym, e tym razem w chwili
wyczenia wywietlacza urzdzenia ekran pozostanie wczony (chocia przyciemniony).
Dokonalimy take kilka pomniejszych modyfikacji kodu. Czujnik bdzie wysya odczyty do
aplikacji nawet wtedy, gdy ekran bdzie pozostawa przyciemniony. Jeeli Czytelnik zamierza
zaimportowa gotowy projekt, nosi on nazw AccelerometerRecordToFileAlwaysOn.
Listing 26.5. Pozostawienie wczonego wywietlacza, nawet jeli zostanie wyczony przez uytkownika
// Dodajmy te obiekty do aktywnoci
private PowerManager mPwrMgr;
private WakeLock mTurnBackOn = null;
private Handler handler = new Handler();

// Dodajmy ponisze trzy wiersze do metody onCreate()


mPwrMgr = (PowerManager) this.getSystemService(POWER_SERVICE);
mWakelock = mPwrMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Accel");
mWakelock.acquire();

// Poniszym fragmentem zastpujemy klas BroadcastReceiver w pliku MainActivity.java


public BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
writeLog("Ekran zostal wylaczony");

// Z poziomu gwnego wtku uruchamiamy ponownie ekran


handler.post(new Runnable() {
public void run() {
if(mTurnBackOn != null)
mTurnBackOn.release();
mTurnBackOn = mPwrMgr.newWakeLock(
PowerManager.SCREEN_DIM_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP,
"AccelOn");

Rozdzia 26 Czujniki

921

mTurnBackOn.acquire();
}});
}
}
};

// Nie zapomnijmy doda poniszego fragmentu w metodzie onDestroy()


if(mTurnBackOn != null)
mTurnBackOn.release();

Jeeli teraz po uruchomieniu aplikacji wciniemy przycisk zasilania urzdzenia w celu wyczenia ekranu, wykryje ona to zdarzenie i za pomoc blokady przechodzenia w stan upienia ustawi
wywietlacz w trybie oszczdzania energii. W trakcie przeprowadzania tej czynnoci moe si
pojawi krtka przerwa pomidzy kolejnymi odczytami czujnika, jednak jest to lepsze rozwizanie od braku aktualizacji odczytw w czasie, gdy wywietlacz pozostaje wyczony. Zwrmy
uwag, e wykorzystalimy procedur obsugi do wystawienia obiektu Runnable z poziomu odbiorcy komunikatw. Dziki temu nasz kod bdzie przetwarzany w gwnym wtku, co staje si
istotne w momencie zwalniania blokady przechodzenia urzdzenia w stan upienia w metodzie
onDestroy(). Zakadanie i zwalnianie blokady musz by przeprowadzane w tym samym wtku.
Zauwamy rwnie, e w metodzie onReceive() odbiorcy komunikatw zwalniamy blokad
przechodzenia urzdzenia w stan upienia, zanim pojawi si kolejna. Dokonujemy tego na wypadek kilkukrotnego wcinicia przycisku zasilania w trakcie rejestrowania odczytw czujnika.
Liczba zwalnianych blokad musi odpowiada liczbie zakadanych blokad, wic zwalniamy t
jedn jeszcze przed wystpieniem nastpnej.
Skoro ju wyjanilimy, w jaki sposb naley pobiera dane z czujnikw, naley powiedzie, co
moemy z nimi zrobi. Jak ju wczeniej stwierdzilimy, w zalenoci od rodzaju czujnika wartoci
przekazywane w tablicy posiadaj rnorodne znaczenie. W nastpnym podrozdziale zajmiemy si
poszczeglnymi typami czujnikw oraz znaczeniem generowanych przez nie odczytw.

Interpretowanie danych czujnika


Potrafimy ju uzyskiwa dane z czujnikw, czas wic zrobi z nimi co sensownego. Musimy
jednak pamita, e rodzaj otrzymywanych odczytw zaley od rodzaju czujnika. Niektre czujniki s mniej skomplikowane od innych. W nastpnych punktach zajmiemy si opisem odczytw otrzymywanych z obecnie znanych nam rodzajw czujnikw. Wraz z nowymi generacjami urzdze bd si z pewnoci pojawia rwnie niespotykane do tej pory rodzaje
czujnikw. Najprawdopodobniej sama architektura czujnikw w Androidzie pozostanie
niezmieniona, wic omawiane w tym podrozdziale techniki powinny znale zastosowanie
take w przyszych typach urzdze.

Czujniki owietlenia
Czujnik owietlenia stanowi jeden z najprostszych rodzajw tego typu ukadw elektronicznych pokazalimy, jak go wykorzysta, w pierwszej przykadowej aplikacji omwionej w tym
rozdziale. Czujnik generuje odczyty zwizane z wykrywanym poziomem natenia wiata.
Wraz ze zmian natenia poziomu wiata czujnik aktualizuje rwnie swoje odczyty. Dane s
wyraane w luksach luks jest jednostk natenia wiata w ukadzie SI. Aby pozna dokadn

922 Android 3. Tworzenie aplikacji


definicj tej jednostki, moemy zajrze do podrozdziau Odnoniki, gdzie zostay umieszczone
adresy URL zasobw zawierajcych bardziej szczegowe informacje.
W przypadku tablicy wartoci przechowywanej w obiekcie SensorEvent czujnik owietlenia wykorzystuje tylko pierwszy element values[0]. Warto ta jest zmiennoprzecinkowa i z technicznego punktu widzenia jej zakres siga od 0 do maksymalnej wartoci odbieranej przez dany
czujnik. Piszemy z technicznego punktu widzenia, poniewa w przypadku pomiarw prowadzonych w ciemnoci czujnik moe wysya bardzo niewielkie wartoci odczytw i w rzeczywistoci nigdy nie przekazuje wartoci rwnej 0.
Pamitajmy rwnie, e czujnik moe przekazywa maksymaln warto natenia wiata, ktra
rni si dla poszczeglnych rodzajw urzdze. Z tego powodu definiowanie staych zwizanych
z owietleniem w klasie SensorManager moe si okaza nieprzydatne. Na przykad klasa ta
zawiera sta LIGHT_SUNLIGHT_MAX, ktrej warto zmiennoprzecinkowa wynosi 120 000. Kiedy
jednak wczeniej wysyalimy zapytanie do urzdzenia, maksymalna warto odczytu wynosia
10 240, czyli zdecydowanie mniej od wartoci tej staej. Istnieje rwnie inna staa, LIGHT_SHADE,
o wartoci 20 000, ktra przekracza maksymaln warto osigan przez testowany czujnik. Musimy wic o tym pamita w trakcie pisania kodu wykorzystujcego odczyty czujnika owietlenia.

Czujniki zblieniowe
Czujnik zblieniowy mierzy odlego dzielc dany obiekt od urzdzenia (w centymetrach) lub
definiuje flag okrelajc, czy dany obiekt jest blisko, czy daleko. Niektre czujniki zblieniowe
posiadaj zakres wartoci od 0 do maksimum wraz z wartociami porednimi, inne natomiast
definiuj wycznie wartoci minimaln i maksymaln. Jeeli maksymalny zakres czujnika jest
rwny jego rozdzielczoci, to znaczy, e odczytuje on wycznie kracowe wartoci. Maksymalna
warto pewnych czujnikw wynosi 1.0, a innych 6.0. Niestety, dopki nie zainstalujemy
i nie uruchomimy aplikacji, nie dowiemy si, jaki rodzaj czujnika zosta umieszczony w urzdzeniu. Nawet jeli umiecimy znacznik <uses-feature> w pliku AndroidManifest.xml, niewiele
nam to pomoe. Nasza aplikacja powinna w elegancki sposb obsugiwa obydwa typy czujnikw zblieniowych, chyba e dokadniejsza wiedza o typie czujnika okae si niezbdna.
Warto pozna interesujcy szczeg dotyczcy czujnikw zblieniowych: czasami czujnik ten
jest czci ukadu elektronicznego wsplnego z czujnikiem owietlenia. Android jednak traktuje je jako logicznie oddzielne czujniki, jeli wic bdziemy potrzebowa danych z obydwu rodzajw czujnikw, musimy utworzy dla nich osobne obiekty nasuchujce. Istnieje jeszcze jeden
ciekawy fakt: w aplikacjach telefonicznych czujnik zblieniowy czsto jest stosowany do okrelania odlegoci gowy uytkownika od urzdzenia. Jeeli gowa znajdzie si wystarczajco blisko
ekranu dotykowego, zostaje on zablokowany, aby aden przycisk nie zosta przypadkowo wcinity przez ucho lub policzek podczas rozmowy.
Wrd kodw rdowych utworzonych na potrzeby tego rozdziau znajdziemy prost aplikacj
monitorujc czujnik zblieniowy, ktra w rzeczywistoci jest zmodyfikowan wersj monitora
czujnika owietlenia. W samej ksice nie umiecimy tego kodu, jednak nic nie stoi na przeszkodzie, eby samodzielnie go pobra i z nim poeksperymentowa.

Termometry
Termometr przekazuje wyniki odczytu temperatury i rwnie s to pojedyncze elementy w tablicy
values[0]. Wartoci s reprezentowane w stopniach Celsjusza. Wartoci w skali Fahrenheita
uzyskamy, mnoc warto wyraon w stopniach Celsjusza przez 9/5 i dodajc do wyniku 32.

Rozdzia 26 Czujniki

923

Na przykad 0 stopni Celsjusza (punkt zamarzania wody) w skali Fahrenheita przyjmuje warto
32, natomiast 100 stopni Celsjusza (punkt wrzenia wody) to 212 stopni Fahrenheita.
W zalenoci od urzdzenia termometr moe zosta umieszczony w rnych miejscach, istnieje
wic moliwo, e wynik mierzonej temperatury zakca ciepo generowane przez telefon.
Przykadowo odczyty temperatury w pewnych urzdzeniach s zakcane przez ciepo powstajce podczas pracy baterii. Powinnimy o tym pamita podczas pisania aplikacji wykorzystujcych termometry. Nie naley oczekiwa, e termometr wbudowany w telefon bdzie mierzy
wycznie temperatur powietrza otaczajcego aparat.
Wrd projektw utworzonych na potrzeby tego rozdziau Czytelnik znajdzie jeden ukazujcy
sposb korzystania z termometru, zatytuowany TemperatureSensor.

Czujniki cinienia
Co ciekawe, w czasie, gdy pisalimy niniejsz ksik, ten rodzaj czujnikw nie zosta jeszcze
umieszczony w adnym urzdzeniu. Uwaamy jednak, e kolejne generacje urzdze mog zosta wyposaone w barometryczne czujniki cinienia, pozwalajce na przykad na pomiar wysokoci. Nie naley myli tego czujnika z funkcj ekranu dotykowego, ktry reaguje na nacisk
palca, moe okreli jego si i generuje obiekt MotionEvent. Wykrywanie tego rodzaju oddziaywa mechanicznych zostao omwione w rozdziale 25., a suca do tego architektura nie
jest czci omawianej w tym rozdziale struktury czujnikw.
Chocia utworzenie aplikacji obsugujcych czujniki cinienia przez proste skopiowanie i zmodyfikowanie zaprezentowanych do tej pory aplikacji monitorujcych nie byoby trudnym zadaniem, to jednak bez wiedzy, jakie jednostki bd stosowane w przypadku tych czujnikw, napisanie takiej aplikacji nie zdaoby si na wiele. Najwidoczniej programici z firmy Google planuj
z wyprzedzeniem.

yroskopy
yroskopy stanowi bardzo ciekaw kategori urzdze, mierzc skrt urzdzenia w paszczynie odniesienia. Inaczej mwic, za ich pomoc mierzymy prdko obrotu telefonu w danej osi. Jeli urzdzenie nie bdzie obracane, wartoci odczytywane przez czujnik bd wynosi 0.
W momencie obrotu smartfonu w dowolnym kierunku pojawi si niezerowe odczyty. Sam yroskop nie poda nam wszystkich wymaganych danych. Niestety, podczas pracy z yroskopami
zawsze wkradn si jakie bdy. Jednak w sprzeniu z akcelerometrami moemy okreli
ciek ruchu urzdzenia. Do powizania odczytw pochodzcych z obydwu czujnikw mog
suy filtry Kalmana. Akcelerometry nie s zbyt dokadne w krtszych odcinkach czasowych,
z kolei yroskopy trac j wraz z upywem czasu, wic ich powizanie ze sob moe nam zagwarantowa cakiem niez dokadno przez cay czas. Filtry Kalmana s bardzo skomplikowane, ale istnieje alternatywa zwana filtrami komplementarnymi, ktre s atwiejsze do implementacji i generuj cakiem poprawne wyniki. Wspomniane tu koncepcje wykraczaj poza
zakres ksiki.
yroskop przekazuje trzy wartoci w tablicy wartoci, opisujce kolejno punkty na osiach x,
y i z. Jednostk przekazywanych wartoci s radiany na sekund, reprezentuj one szybko
obrotu urzdzenia wok danej osi. Jednym ze sposobw ich wykorzystania jest ich cakowanie
po czasie w celu obliczenia zmiany kta. W podobny sposb jest cakowana warto prdkoci
liniowej po czasie w celu obliczenia odlegoci.

924 Android 3. Tworzenie aplikacji

Akcelerometry
Akcelerometry stanowi chyba najciekawsze z obecnie dostpnych rodzajw czujnikw. Za
ich pomoc aplikacja moe okreli fizyczne uoenie urzdzenia w zalenoci od siy cienia,
a dodatkowo wykrywa siy przesuwajce to urzdzenie. Dziki takim informacjom programici zyskuj niespotykane dotd moliwoci, poczwszy od nowej jakoci sterowania w grach,
a skoczywszy na pracy w rzeczywistoci rozszerzonej. Oczywicie, podstawowym zadaniem
akcelerometru jest przekazanie do urzdzenia informacji o zmianie jego uoenia z orientacji
pionowej na poziom, i odwrotnie.
System wsprzdnych w akcelerometrze dziaa nastpujco: o x czujnika ma swj pocztek
w lewym dolnym rogu urzdzenia i jest skierowana w praw stron (patrzc od przodu urzdzenia). O y rwnie ma pocztek w lewym dolnym rogu urzdzenia i jest skierowana w gr
telefonu. Take punkt 0 osi z znajduje si w lewym dolnym rogu urzdzenia i jest skierowany
na zewntrz, w taki sposb, e oddala si od urzdzenia. Zostao to zobrazowane na rysunku 26.2.

Rysunek 26.2. System wsprzdnych akcelerometru

System wsprzdnych rni si od wykorzystywanego w ukadach graficznych i grafice dwuwymiarowej. W przypadku tamtych ukadw wsprzdnych ich pocztek (0, 0) znajduje si
w lewym grnym rogu ekranu, a wartoci dodatnie osi y rosn w kierunku dolnym. atwo si
pomyli podczas pracy z systemami wsprzdnych w rnych ukadach odniesienia, naley
wic zachowa ostrono.
Jeszcze nic nie wspomnielimy o znaczeniu wartoci przekazywanych przez akcelerometr, a wic
co one oznaczaj? Przypieszenie jest mierzone w metrach na sekund do kwadratu (m/s2).
Przypieszenie powodowane ziemsk grawitacj wynosi 9,81 m/s2 i jest skierowane w d,
w stron rodka planety. Z punktu widzenia akcelerometru warto siy cienia wynosi 9,81.
Jeeli urzdzenie znajduje si w stanie spoczynku (nie porusza si) i jest uoone na doskonale
paskiej, poziomej powierzchni, odczyty na osiach x i y przyjm wartoci 0, natomiast w osi
z +9,81. W rzeczywistoci, zalenie od czuoci i dokadnoci akcelerometru, wartoci te nie
bd doskonale odwzorowywa faktycznego stanu rzeczy, jednak bd stanowi wystarczajco
dobre przyblienie. W stanie spoczynku jedynie grawitacja bdzie wpywa na urzdzenie, a poniewa jej wektor jest skierowany w d (nasze urzdzenie za ley pasko), nie bdzie miaa
wpywu na osie x i y. W przypadku osi z bdzie mierzona warto siy dziaajcej na urzdzenie

Rozdzia 26 Czujniki

925

oraz zostanie odjta warto siy cienia, wic 0 minus 9,81 daje nam ostatecznie warto +9,81
i tyle wanie wynosi warto siy przyoonej do tej osi (element values[2] w obiekcie
SensorEvent).
Wartoci przesyane do aplikacji przez akcelerometr zawsze stanowi sum si dziaajcych na
urzdzenie minus warto przypieszenia ziemskiego. Gdybymy unieli w gr nasze uoone
doskonale pasko urzdzenie, pocztkowo warto mierzona dla osi z wzrosaby, poniewa zwikszylibymy oddziaywanie wbrew sile grawitacji. Gdy tylko przestaniemy unosi urzdzenie,
warto sumaryczna dziaajcych si powrci do wartoci grawitacji. Gdyby urzdzenie zostao
upuszczone (czysto hipotetycznie nie sprawdzajmy tego), zaczoby uzyskiwa przypieszenie
w kierunku ziemi, a tym samym warto odczytu w nastpnych momentach zmalaaby do zera.
Wyobramy sobie, e urzdzenie z rysunku 26.2 obrcimy w taki sposb, aby byo uoone
w trybie portretowym, pionowo. O x pozostaje bez zmian i wskazuje z lewej strony na praw.
Z kolei o y zostaje ustawiona prostopadle do ziemi, a o z zostaje skierowana w nasz stron.
Warto osi y wynosi teraz +9,81, a osi x i z po 0.
Co si stanie, jeli obrcimy urzdzenie do uoenia poziomego, w trybie krajobrazowym,
i w dalszym cigu bdziemy trzyma je pionowo, tj. ekran bdzie si znajdowa na wprost twarzy?
Nietrudno zgadn, e osie y i z przybior wartoci 0, a w osi x bdzie dziaa sia rwna +9,81.
Taka sytuacja zostaa zilustrowana na rysunku 26.3.

Rysunek 26.3. Wartoci akcelerometru w trybie krajobrazowym, urzdzenie ustawione w pionie

Gdy urzdzenie znajduje si w stanie spoczynku lub porusza si z jednostajn prdkoci, akcelerometry mierz wycznie warto grawitacji. Dla kadej osi odczyty akcelerometru stanowi skadowe grawitacje w tej osi. Zatem za pomoc oblicze trygonometrycznych moemy
okreli kty oraz uoenie urzdzenia wzgldem kierunku dziaania siy cienia. Oznacza
to, e moemy si dowiedzie, czy urzdzenie jest uoone w trybie portretowym, krajobrazowym, czy jakim porednim. W rzeczywistoci system korzysta wanie z tego rozwizania
w czasie rozpoznawania orientacji uoenia (tryb krajobrazowy lub portretowy). Zwrmy jednak uwag, e akcelerometry nie okrelaj uoenia urzdzenia w odniesieniu do pnocy
magnetycznej. Do tego wanie suy magnetometr, ktry zostanie omwiony w dalszej czci rozdziau.

926 Android 3. Tworzenie aplikacji

Akcelerometry a tryb wywietlania


Akcelerometry jako takie s ukadami elektronicznymi, trwale przymocowanymi do obudowy
urzdzenia i z tego powodu maj okrelone uoenie wzgldem reszty architektury telefonu,
niezmieniajce si w trakcie jego obracania. Odczyty wysyane w wyniku ruchu bd oczywicie ulegay zmianom, jednak ukad wsprzdnych akcelerometrw jest oparty na urzdzeniu i nie bdzie w aden sposb modyfikowany. Z kolei ukad wsprzdny ekranu zmienia si
w zalenoci od trybu wywietlania. Faktycznie, w zalenoci od uoenia ekranu tryb portretowy moe by obrcony nawet o 180 stopni. Analogiczna sytuacja dotyczy rwnie trybu
krajobrazowego.
Gdy nasza aplikacja odczytuje dane akcelerometru i ma waciwie modyfikowa interfejs uytkownika, musi zna warto obrotu urzdzenia, aby mc wprowadzi niezbdne poprawki.
W trakcie zmiany trybu z portretowego na krajobrazowy ukad wsprzdnych ekranu zostaje
obrcony zgodnie z ukadem wsprzdnych akcelerometrw. Aby tak si stao, aplikacja musi
wykorzysta metod Display.getRotation(), ktra zostaa wprowadzona w wersji 2.2 Androida. Przekazywana warto jest zwyk liczb cakowit, nie symbolizuje jednak rzeczywistej
wartoci kta obrotu. Mamy tu do czynienia z jedn z czterech nastpujcych staych: Surface.
ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180 lub Surface.ROTATION_270.
Przyjmuj one wartoci kolejno: 0, 1, 2, 3. Wartoci te informuj aplikacj, jak bardzo urzdzenie zostao obrcone w stosunku do standardowego uoenia wywietlacza. Poniewa nie
wszystkie urzdzenia obsugujce system Android w domyle s uoone w trybie portretowym,
nie moemy z gry zakada, e staa odpowiedzialna za ten tryb to ROTATION_0.
Nie wszystkie urzdzenia przekazuj wszystkie cztery wartoci. W telefonie HTC Droid Eris
obsugujcym wersj 2.1 Androida metoda Display.getOrientation() (poprzedniczka metody Display.getRotation(), obecnie uznanej za przestarza) wywietla tylko wartoci 0 i 1,
i to tyle. W zwykym trybie portretowym przekazywana warto wynosi 0. Jeeli obrcimy
urzdzenie o 90 stopni w stron przeciwn do kierunku ruchu wskazwek zegara, ukad graficzny zostanie zmieniony i metoda Display.getOrientation() powrci z wartoci 1. Jeeli
w trybie portretowym obrcimy urzdzenie o 90 stopni zgodnie z kierunkiem ruchu wskazwek zegara, ekran pozostanie w trybie portretowym, a my otrzymamy warto 0 z metody
Display.getOrientation().
W telefonie Motorola Droid pracujcym pod kontrol Androida 2.2 metoda Display.get
powraca z wartociami 0, 1 lub 3. Warto 2 nie jest przekazywana, co oznacza,
e urzdzenie nie pracuje w odwrconym trybie portretowym. Jest jednak pewien rozczarowujcy wynik: jeeli odwrcimy urzdzenie o 270 stopni w stron przeciwn do kierunku ruchu
wskazwek zegara (z pozycji domylnej), metoda Display.getRotation() powrci z wartoci 1
przy 90 stopniach i urzdzenie przejdzie w tryb krajobrazowy, przy 180 stopniach cay czas
pozostaje warto 1 i tryb wywietlania nie ulega zmianie, przy 270 stopniach ukad graficzny
zostaje odwrcony na odwrotny krajobrazowy, jednak metoda Display.getRotation() wci
przekazuje warto 1. Jeeli obrcimy urzdzenie z pozycji domylnej o 90 stopni zgodnie z kierunkiem ruchu wskazwek zegara, metoda ta powrci z wartoci 3. Ta pozycja wyglda dokadnie tak samo jak odwrcona pozycja z 270 stopni, zostaje jednak przekazana inna warto
w metodzie Display.getRotation(), w zalenoci od tego, jak drog do niej dotarlimy.

Rotation()

Akcelerometry i grawitacja
Do tej pory zajmowalimy si bardzo pobienie kwesti zachowania odczytw akcelerometru w trakcie przemieszczania si urzdzenia. Zastanwmy si teraz nad tym dokadniej.

Rozdzia 26 Czujniki

927

Wszystkie siy dziaajce na urzdzenie zostan wykryte przez akcelerometry. Jeeli uniesiemy
telefon, sia dziaajca w osi z bdzie pocztkowo dodatnia, a jej warto bdzie wiksza ni +9,81.
Jeeli przesuniemy urzdzenie w lewo, pocztkowa warto wektora siy w osi x bdzie ujemna.
Zaleaoby nam teraz na oddzieleniu wartoci wynikajcych z oddziaywania grawitacyjnego
od pozostaych si dziaajcych na urzdzenie. Rozwizanie jest cakiem proste i nosi nazw filtru
dolnoprzepustowego. Siy niebdce oddziaywaniem grawitacyjnym zazwyczaj niestopniowo
wpywaj na urzdzenie. Inaczej mwic, jeeli uytkownik potrzsa urzdzeniem, pojawiajce
si siy s bardzo szybko rejestrowane przez akcelerometry. W zwizku z tym filtr dolnoprzepustowy usunie skadow odpowiedzialn za same wstrzsy i pozostawi wycznie niezmienn
skadow, w tym przypadku przypieszenie ziemskie. Zilustrujmy t koncepcj na przykadzie.
Interesujcy nas projekt nosi nazw GravityDemo. Na listingu 26.6 zosta umieszczony ukad
graficzny oraz kod Java.
Listing 26.6. Pomiar grawitacji za pomoc akcelerometrw
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView android:id="@+id/text" android:textSize="20sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>

// Jest to plik MainActivity.java.


import
import
import
import
import
import
import

android.app.Activity;
android.hardware.Sensor;
android.hardware.SensorEvent;
android.hardware.SensorEventListener;
android.hardware.SensorManager;
android.os.Bundle;
android.widget.TextView;

public class MainActivity extends Activity implements SensorEventListener {


private SensorManager mgr;
private Sensor accelerometer;
private TextView text;
private float[] gravity = new float[3];
private float[] motion = new float[3];
private double ratio;
private double mAngle;
private int counter = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mgr = (SensorManager) this.getSystemService(SENSOR_SERVICE);

928 Android 3. Tworzenie aplikacji


accelerometer = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
text = (TextView) findViewById(R.id.text);
}
@Override
protected void onResume() {
mgr.registerListener(this, accelerometer,
SensorManager.SENSOR_DELAY_UI);
super.onResume();
}
@Override
protected void onPause() {
mgr.unregisterListener(this, accelerometer);
super.onPause();
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {

// Ignorujemy.
}
public void onSensorChanged(SensorEvent event) {

// Wprowadzamy filtr dolnoprzepustowy, sucy do uzyskania skadowej grawitacji.


// Pozostaoci s skadowe ruchu.
for(int i=0; i<3; i++) {
gravity [i] = (float) (0.1 * event.values[i] +
0.9 * gravity[i]);
motion[i] = event.values[i] - gravity[i];
}

// Zmienn ratio jest stosunek grawitacji przyoonej w osi Y do standardowej wartoci grawitacji.
// Warto ta powinna mieci si w zakresie pomidzy 1 i 1.
ratio = gravity[1]/SensorManager.GRAVITY_EARTH;
if(ratio > 1.0) ratio = 1.0;
if(ratio < -1.0) ratio = -1.0;

// Konwertuje radiany na stopnie, w trakcie kierowania si


// w gr warto zostaje przetworzona na ujemn.
mAngle = Math.toDegrees(Math.acos(ratio));
if(gravity[2] < 0) {
mAngle = -mAngle;
}

// Wywietla co dziesit warto.


if(counter++ % 10 == 0) {
String msg = String.format(
"Nieprzetworzone wartoci\nX: %8.4f\nY: %8.4f\nZ: %8.4f\n" +
"Skadowa grawitacji\nX: %8.4f\nY: %8.4f\nZ: %8.4f\n" +
"Skadowa ruchu\nX: %8.4f\nY: %8.4f\nZ: %8.4f\nKt: %8.1f",
event.values[0], event.values[1], event.values[2],
gravity[0], gravity[1], gravity[2],
motion[0], motion[1], motion[2],
mAngle);
text.setText(msg);

Rozdzia 26 Czujniki

929

text.invalidate();
counter=1;
}
}
}

W wyniku uruchomienia powyszego kodu ujrzymy ekran zaprezentowany na rysunku 26.4.


Poniszy zrzut ekranu zosta wykonany, kiedy urzdzenie spoczywao pasko na stole.

Rysunek 26.4. Wartoci grawitacji, ruchu i kta

Aplikacja ta w wikszoci przypomina wczeniejszy przykadowy program Monitor akcelerometru. Rnice pojawiaj si w metodzie onSensorChanged(). Zamiast standardowego wywietlania otrzymywanych wartoci z tablicy prbujemy pozna skadowe grawitacji i ruchu.
Skadow grawitacji uzyskujemy poprzez zsumowanie aktualnej i poprzedzajcej wartoci
z tablicy grawitacji, przemnoonych przez wspczynniki. Wspczynniki te musz po zsumowaniu da warto 1.0, a dobiera si je tak, e aktualn warto z tablicy wartoci grawitacji
mnoy si przez mniejszy wspczynnik, a poprzedni przez wikszy (byle suma tych wspczynnikw bya rwna wartoci 1.0). W naszym przykadzie wykorzystalimy wspczynniki
0,9 i 0,1. Moemy rwnie wyprbowywa inne wartoci wspczynnikw, na przykad 0,8 i 0,2.
Tablica wartoci grawitacji prawdopodobnie nie bdzie zmieniaa si tak szybko jak rzeczywiste
odczyty czujnika. W ten sposb jednak zbliamy si do rzeczywistych wartoci. Do tego wanie suy filtr dolnoprzepustowy. Wartoci tabeli zdarze ulegaj zmianom jedynie w przypadku si poruszajcych urzdzeniem, a my nie chcemy, aby byy one mierzone jako cz oddziaywania grawitacyjnego. W tabeli wartoci grawitacji chcemy jedynie zarejestrowa faktyczne
odczyty siy cienia. Zastosowane tutaj obliczenia matematyczne nie sprawiaj, e w magiczny
sposb jest rejestrowane wycznie przypieszenie grawitacyjne, ale uzyskiwane wartoci bd
znacznie blisze rzeczywistoci ni nieprzetworzone dane.

930 Android 3. Tworzenie aplikacji


Analizujc kod, zwrmy rwnie uwag na tablic wartoci ruchu. Poprzez ledzenie rnicy
pomidzy nieprzetworzonymi wartociami zdarze a obliczonymi wartociami grawitacji mierzymy po prostu aktywne siy (niebdce przypieszeniem ziemskim) dziaajce na urzdzenie.
Jeeli wartoci w tablicy ruchu wynosz zero lub s bliskie zeru, oznacza to, e urzdzenie
prawdopodobnie znajduje si w stanie spoczynku. Jest to bardzo przydatna informacja. W idealnym przypadku urzdzenie poruszajce si ze sta prdkoci rwnie powinno generowa
wartoci tabeli ruchu bliskie zeru, w rzeczywistoci jednak s one wiksze.

Uywanie akcelerometrw do mierzenia kta uoenia urzdzenia


Zanim przejdziemy dalej, chcielibymy zaprezentowa jeszcze jedn cech akcelerometrw.
Jeeli przypomnimy sobie lekcje trygonometrii ze szkoy redniej, zauwaymy, e cosinus kta
jest stosunkiem dugoci przyprostoktnej bliszej do tego kta i przeciwprostoktnej. Jeli wemiemy pod uwag kt pomidzy osi y a wektorem dziaania siy grawitacji, moglibymy zmierzy warto grawitacji dziaajcej w kierunku y, a za pomoc funkcji arcus cosinus obliczy kt.
Ta metoda znalaza zastosowanie w naszym kodzie. Musimy jednak w tym przypadku zmierzy si z pewnym baaganem zwizanym z architektur czujnikw w Androidzie. W klasie
SensorManager istniej rne stae definiujce grawitacj, w tym rwnie ziemsk. Prawdopodobnie jednak mierzone wartoci mog przekracza wartoci okrelane przez te stae. Za
chwil wyjanimy, co mamy na myli.
Nasze urzdzenie, pozostajc w stanie spoczynku, powinno teoretycznie mierzy warto grawitacji rwn wspominanej ju wielokrotnie staej, tak si jednak nieczsto dzieje. W takim
przypadku akcelerometr bdzie prawdopodobnie dawa mniejsze lub wiksze odczyty od zaoonych. Zatem wspczynnik grawitacji moe by wikszy od 1 lub mniejszy od 1. Nasza
funkcja acos() moe zacz w takim przypadku dawa niestabilne wyniki, zatem zamykamy
otrzymywane wartoci w zbiorze liczb z przedziau od 1 do 1. Jest to zakres ktowy rwnowany wartociom od 0 do 180 stopni. Wyglda zachcajco, ale w ten sposb nie uzyskamy
ktw ujemnych, od 0 do 180 stopni. eby uzyska takie ujemne wartoci, korzystamy z kolejnego elementu tablicy wartoci grawitacji, ktrym jest warto z. Jeeli warto z grawitacji
jest ujemna, oznacza to, e urzdzenie jest skierowane wywietlaczem w d. W przypadku
wszystkich wartoci, w ktrych urzdzenie jest skierowane w d, wprowadzamy znak minus,
dziki czemu zakres ktowy wynosi od 180 do +180 stopni, dokadnie tak, jak powinno by.
Nie bjmy si poeksperymentowa z t przykadow aplikacj. Zwrmy uwag, e warto kta
nachylenia urzdzenia wynosi 90 stopni, gdy ley ono na stole, a 0 stopni (lub blisko tej wartoci), gdy trzymamy je naprzeciwko twarzy. Jeeli bdziemy przechylali urzdzenie jeszcze bardziej z pozycji paskiej, warto tego kta zacznie przekracza 90 stopni. Jeeli bdziemy je
pochyla coraz bardziej z pozycji 0 stopni, warto kta bdzie przybiera wartoci ujemne, a
w kocu urzdzenie bdzie uoone wywietlaczem do dou i warto tego kta osignie warto
90 stopni. Na koniec pewnie zauwaylimy w kodzie licznik kontrolujcy czstotliwo
aktualizacji ekranu. Poniewa odczyty czujnika mog by do szybko odwieane, postanowilimy pokazywa na wywietlaczu zaledwie co dziesity wynik.

Magnetometry
Magnetometr mierzy indukcj pola magnetycznego w otoczeniu i okrela jej rozkad w osiach
x, y i z. Ukad wsprzdnych jest taki sam jak w przypadku akcelerometrw, wic moemy
zastosowa tu taki, jaki wida na rysunku 26.2. Jednostkami stosowanymi w magnetometrach
s mikrotesle (T). Czujnik ten wykrywa ziemskie pole magnetyczne, dlatego te pozwala nam

Rozdzia 26 Czujniki

931

okrela kierunek pnocny. Magnetometr jest czsto nazywany rwnie kompasem, nawet
w znaczniku <uses-feature> jest stosowana nazwa android.hardware.sensor.compass.
Magnetometr jest bardzo niewielkim i czuym urzdzeniem, dlatego na jego odczyty wpywa
pole magnetyczne generowane przez inne urzdzenia znajdujce si w pobliu, a w pewnym
stopniu nawet ukady znajdujce si w samym telefonie. Zatem wskazania magnetometru
mog by czasami niewiarygodne.
Wrd projektw przygotowanych specjalnie na potrzeby tego rozdziau umiecilimy prost
aplikacj CompassSensor, warto wic zaimportowa j i troch z ni poeksperymentowa. Jeeli
zbliymy metalowe przedmioty do urzdzenia w trakcie dziaania aplikacji, moemy zauway
zmian odczytw. Oczywicie, jeeli blisko czujnika ustawimy magnes, odczyty na pewno
ulegn zmianie, nie radzimy jednak tego robi, gdy magnetometr moe zosta rozkalibrowany.
Moemy si zastanawia, czy istnieje moliwo wykorzystania magnetometru jako kompasu
do wskazywania kierunku pnocnego. Odpowied brzmi: niebezporednio. Chocia magnetometr wykrywa strumienie magnetyczne otaczajce urzdzenie, jeeli urzdzenie nie bdzie
uoone doskonale poziomo, jego odczyty jako kompasu bd niemiarodajne. Mamy jednak do
dyspozycji akcelerometry, ktre informuj nas o uoeniu urzdzenia wzgldem osi stanowicej
kierunek dziaania siy grawitacji! Moemy wic wykorzystywa magnetometr jako kompas,
bdzie nam jednak do tego potrzebna pomoc akcelerometrw. Zobaczmy, jak si to robi.

Wsppraca akcelerometrw z magnetometrami


Klasa SensorManager zawiera pewne metody umoliwiajce czenie odczytw magnetometru
z wartociami mierzonymi przez akcelerometr w celu okrelenia orientacji w przestrzeni.
Jak niedawno wspomnielimy, nie moemy w tym celu wykorzysta samego magnetometru.
W klasie SensorManager znajdziemy wic metod getRotationMatrix(), pobierajc wartoci
akcelerometru i kompasu, a nastpnie przekazujc macierz danych pozwalajcych na okrelenie orientacji w przestrzeni.
Inna metoda tej klasy, getOrientation(), pobiera wspomnian wczeniej tablic obrotw
i przetwarza j na tablic kierunkowoci. Wartoci podane w tej macierzy informuj nas o obrocie
urzdzenia wzgldem pnocy magnetycznej oraz o nachyleniu bocznym i wzdunym w stosunku do poziomu. Byoby wspaniale, gdyby wszystkie te obliczenia byy dokonywane automatycznie. Niestety, mechanizm ten stanowi wielkie wyzwanie, przynajmniej do wersji 2.2 Androida, gdzie jednym z problemw, i to wcale nie najwikszym, jest brak cigoci, w przypadku
gdy trzymamy urzdzenie naprzeciwko siebie, a nastpnie unosimy je nieznacznie w taki sposb,
e spogldamy nieco do gry na ekran. Owa niecigo polega na tym, e gdy tylko urzdzenie
przekroczy punkt 0 stopni (zgodnie z ktrym jeszcze znajdujemy si naprzeciwko urzdzenia),
magnetometr odwraca kierunki, co jest zupenie nieintuicyjne. Na szczcie w wersji 2.3 Androida
umieszczono kilka dodatkowych metod korygujcych ten bd (wicej informacji znajdziemy
w punkcie Czujniki wektora obrotu). Jednak w midzyczasie powinnimy rwnie obsuy
odczyty czujnikw w urzdzeniach wyposaonych w wersje systemu starsze od 2.3.

Czujniki orientacji w przestrzeni


Nadszed czas na omwienie czujnikw orientacji. W poprzednim punkcie stwierdzilimy, e
mona powiza dziaanie akcelerometrw i magnetometrw w celu uzyskania odczytw dotyczcych orientacji urzdzenia, dziki ktrym moemy dowiedzie si, w jakim kierunku jest
skierowany jego wywietlacz. Takie samo zadanie wykonuje czujnik orientacji. W rzeczywistoci

932 Android 3. Tworzenie aplikacji


stanowi on poczenie akcelerometru i magnetometru na poziomie sterownikw. Inaczej mwic, czujnik orientacji nie jest oddzielnym urzdzeniem, ale oprogramowanie systemowe wie
dwa wspomniane czujniki w taki sposb, e dziaaj jak jeden ukad elektroniczny.
Nie wspominalimy o czujnikach orientacji a do teraz, poniewa zostay uznane
za przestarzae w wersji 2.2 Androida i nie zaleca si ich stosowania. Jednak s one
bardzo przydatne, a do tego o wiele prostsze w uyciu od preferowanego rozwizania,
o czym si wkrtce przekonamy.

Niedawno stwierdzilimy, e stosowanie preferowanego mechanizmu obliczania orientacji urzdzenia w przestrzeni jest trudnym zadaniem. W nastpnym przykadzie porwnamy wartoci
orientacji uzyskiwane z preferowanego rozwizania z odczytami czujnika orientacji i przyjrzymy
si rnicom.
Urozmaicimy nieco przykadow aplikacj. Moglibymy po prostu wywietli wartoci przekazywane przez czujniki, ale moemy je jeszcze wykorzysta w interesujcy sposb. Wyobramy
sobie, e stoimy na ulicy w Jacksonville na Florydzie. Nasza aplikacja bdzie nam pokazywaa
zdjcia w trybie StreetView tego miasta, tak jakbymy tam byli, a czujnik orientacji posuy
nam do okrelenia kierunku, w jakim spogldamy. Wraz ze zmian kierunku orientacji telefonu bd si odpowiednio zmieniay widoki w trybie StreetView. Na listingu 26.7 widzimy
ukad graficzny i kod Java naszej przykadowej aplikacji, nazwanej VirtualJax.
Listing 26.7. Uzyskiwanie informacji o pooeniu za pomoc czujnikw
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout/main.xml -->


<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<Button android:id="@+id/update" android:text="Aktualizuj wartoci"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doUpdate" />
<Button android:id="@+id/show" android:text="Wywietl moj pozycj!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doShow" android:layout_toRightOf="@id/update" />
<TextView android:id="@+id/preferred" android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/update" />
<TextView android:id="@+id/orientation" android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/preferred" />
</RelativeLayout>

// Jest to plik MainActivity.java


import android.app.Activity;
import android.content.Intent;
import android.hardware.Sensor;

Rozdzia 26 Czujniki

import
import
import
import
import
import
import
import
import

android.hardware.SensorEvent;
android.hardware.SensorEventListener;
android.hardware.SensorManager;
android.net.Uri;
android.os.Build;
android.os.Bundle;
android.view.View;
android.view.WindowManager;
android.widget.TextView;

public class MainActivity extends Activity implements SensorEventListener {


private static final String TAG = "VirtualJax";
private SensorManager mgr;
private Sensor accel;
private Sensor compass;
private Sensor orient;
private TextView preferred;
private TextView orientation;
private boolean ready = false;
private float[] accelValues = new float[3];
private float[] compassValues = new float[3];
private float[] inR = new float[9];
private float[] inclineMatrix = new float[9];
private float[] orientationValues = new float[3];
private float[] prefValues = new float[3];
private float mAzimuth;
private double mInclination;
private int counter;
private int mRotation;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
preferred = (TextView)findViewById(R.id.preferred);
orientation = (TextView)findViewById(R.id.orientation);
mgr = (SensorManager) this.getSystemService(SENSOR_SERVICE);
accel = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
compass = mgr.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
orient = mgr.getDefaultSensor(Sensor.TYPE_ORIENTATION);
WindowManager window = (WindowManager)
this.getSystemService(WINDOW_SERVICE);
int apiLevel = Integer.parseInt(Build.VERSION.SDK);
if(apiLevel < 8) {
mRotation = window.getDefaultDisplay().getOrientation();
}
else {
mRotation = window.getDefaultDisplay().getRotation();
}
}
@Override
protected void onResume() {

933

934 Android 3. Tworzenie aplikacji


mgr.registerListener(this, accel,
SensorManager.SENSOR_DELAY_GAME);
mgr.registerListener(this, compass,
SensorManager.SENSOR_DELAY_GAME);
mgr.registerListener(this, orient,
SensorManager.SENSOR_DELAY_GAME);
super.onResume();
}
@Override
protected void onPause() {
mgr.unregisterListener(this, accel);
mgr.unregisterListener(this, compass);
mgr.unregisterListener(this, orient);
super.onPause();
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {

// Ignorujemy
}
public void onSensorChanged(SensorEvent event) {

// Musimy uzyska dostp do akcelerometru i kompasu,


// zanim okrelimy wartoci tablicy orientationValues
switch(event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
for(int i=0; i<3; i++) {
accelValues[i] = event.values[i];
}
if(compassValues[0] != 0)
ready = true;
break;
case Sensor.TYPE_MAGNETIC_FIELD:
for(int i=0; i<3; i++) {
compassValues[i] = event.values[i];
}
if(accelValues[2] != 0)
ready = true;
break;
case Sensor.TYPE_ORIENTATION:
for(int i=0; i<3; i++) {
orientationValues[i] = event.values[i];
}
break;
}
if(!ready)
return;
if(SensorManager.getRotationMatrix(
inR, inclineMatrix, accelValues, compassValues)) {

// Uzyskalimy dobr macierz obrotw


SensorManager.getOrientation(inR, prefValues);
mInclination = SensorManager.getInclination(inclineMatrix);

Rozdzia 26 Czujniki

935

// Wywietla co dziesit warto


if(counter++ % 10 == 0) {
doUpdate(null);
counter = 1;
}
}
}
public void doUpdate(View view) {
if(!ready)
return;
mAzimuth = (float) Math.toDegrees(prefValues[0]);
if(mAzimuth < 0) {
mAzimuth += 360.0f;
}
String msg = String.format(
"Preferowana:\nazymut (Z): %7.3f \nprzechy wzduny (X): %7.3f\nprzechy
boczny (Y): %7.3f",
mAzimuth, Math.toDegrees(prefValues[1]),
Math.toDegrees(prefValues[2]));
preferred.setText(msg);
msg = String.format(
"Czujnik orientacji:\nazymut (Z): %7.3f\nprzechy wzduny (X):
%7.3f\nprzechy
boczny (Y): %7.3f",
orientationValues[0],
orientationValues[1],
orientationValues[2]);
orientation.setText(msg);
preferred.invalidate();
orientation.invalidate();
}
public void doShow(View view) {

// google.streetview:cbll=30.32454,81.6584&cbp=1,yaw,,pitch,1.0
// yaw = warto w stopniach, zgodnie ze wskazwkami zegara od bieguna pnocnego
// W przypadku odchylenia (ang. yaw) moemy wykorzysta wartoci mAzimuth
// lub orientationValues[0].
//
// pitch = warto w stopniach, przechy w gr lub d. 90 oznacza spogldanie w gr,
// +90 to spogldanie w d,
// nie biorc pod uwag faktu, e przechy wzduny (ang. pitch) nie jest poprawnie obliczany.
Intent intent=new Intent(Intent.ACTION_VIEW, Uri.parse(
"google.streetview:cbll=30.32454,-81.6584&cbp=1," +
Math.round(orientationValues[0]) + ",,0,1.0"
));
startActivity(intent);
return;
}
}

936 Android 3. Tworzenie aplikacji


Interfejs uytkownika stanowi dwa przyciski i para listingw zawierajcych odczyty czujnikw,
z ktrych na jednym s ukazane wartoci generowane przez preferowan metod, a na drugim
wyniki z czujnika orientacji. Po uruchomieniu tej aplikacji powinnimy ujrze ekran podobny
do przedstawionego na rysunku 26.5.

Rysunek 26.5. Dwa sposoby okrelania orientacji w przestrzeni

Zanim przyjrzymy si wynikom, wyjanijmy, jakie jest zadanie tej aplikacji. W metodzie
onCreate() przeprowadzamy takie same czynnoci jak w poprzednich przykadach: tworzymy
odniesienia do widokw tekstowych, klasy SensorManager oraz trzech typw czujnikw, jakie
chcemy wykorzysta: akcelerometru, kompasu i czujnika orientacji. Definiujemy rwnie
zmienn przechowujc warto obrotu. Za chwil dowiemy si po co.
W metodzie onResume() uruchamiamy czujniki, a w metodzie onPause() wyczamy je.
Podczas otrzymywania aktualizacji odczytw czujnika okrelamy, do ktrej kategorii one nale,
i rejestrujemy te wartoci w lokalnych czonkach: accelValues, compassValues lub orientation
Values. Zauwamy, e moglibymy skopiowa tablic zdarze w celu zachowania lokalnych
kopii odczytw; oznaczaoby to jednak cige tworzenie obiektw, a to nie jest dobry pomys.
Tworzenie nowych obiektw i pniejsze ich usuwanie moe by naprawd kosztowne, jeli chodzi
o zuycie zasobw, dlatego ograniczamy si wycznie do aktualizowania ju istniejcych tablic.
Zwrmy uwag, e zanim zajmiemy si przetwarzaniem dalszej czci kodu, sprawdzamy za pomoc operacji logicznej, czy posiadamy wartoci zarwno w tablicy accelValues, jak i compass
Values. Widzimy nastpnie wywoanie metody getRotationMatrix(), po ktrej nastpuje
wywoanie metody getOrientation(). Wprowadzilimy rwnie metod getInclination().
Nie bdziemy z niej korzysta, warto jednak wiedzie, e reprezentuje ona kt pomidzy strumieniem magnetycznym a powierzchni Ziemi. Im bliej znajdujemy si ktrego z biegunw,
tym wiksza jest przekazywana warto kta. Nastpnie tworzymy licznik, za pomoc ktrego
bdzie wywietlana co dziesita aktualizacja wartoci. Podobnie jak we wczeniejszych przykadach, mechanizm ten suy do zminimalizowania obcienia interfejsu uytkownika, dziki
czemu aplikacja zyskuje na wydajnoci.

Rozdzia 26 Czujniki

937

Wewntrz metody doUpdate(), ktra moe by wywoywana rwnie za pomoc przycisku


w interfejsie uytkownika, przeprowadzamy kilka oblicze i wywietlamy wyniki. W przypadku
zalecanej metody pierwsza warto, azymut, jest wyznaczana w radianach, w zakresie od ujemnej
wartoci pi do dodatniej wartoci pi (czyli od 180 do 180 stopni). Wartoci czujnika orientacji
mieszcz si w zakresie od 0 do 360 stopni. Aby odczyty z obydwu rodzajw czujnikw byy
porwnywalne, wzilimy pierwsz warto z tablicy prefValues, przeksztacilimy radiany na
stopnie i dodalimy 360, jeli warto bya ujemna. Teraz wartoci pozyskiwane z akcelerometru
i magnetometru s porwnywalne z odczytami czujnika orientacji. Pozostaa cz tej metody
zapewnia obsug wywietlania tych wynikw w interfejsie uytkownika.
Ostatni metod w naszej aplikacji jest doShow(). Uwaamy, e jest naprawd pasjonujca.
W rozdziale 25. pokazalimy, w jaki sposb mona przywoa aplikacj StreetView za pomoc
intencji. W tamtym rozdziale pominlimy cz zwizan z konfigurowaniem wartoci odchylenia, ktra definiuje kierunek spogldania uytkownika w przypadku wywietlania obrazu. Przeanalizujemy teraz sposb przekazania wartoci odchylenia, a take przechyu wzdunego.
Jako dugo i szeroko geograficzn wybralimy wsprzdne miasta Jacksonville na Florydzie.
Moemy wstawi tu oczywicie wasne koordynaty. W przypadku odchylenia musimy przekaza warto w stopniach liczon od kierunku pnocnego (0 360), zatem moemy skorzysta
z wartoci zmiennej mAzimuth lub tablicy orientationValues[0], przeksztaconej w liczb
cakowit. Jeli chodzi o przechy wzduny, moglibymy teoretycznie wykorzysta drug warto z ktrej z tablic, a nastpnie doda warto 90. Jednak wydaje si, e aplikacja StreetView
ma problemy z obsug wartoci przechyu wzdunego innymi ni 0, przynajmniej w tej lokacji.
Zatem na razie ustanawiamy warto 0 przechyu wzdunego. Jeeli klikniemy przycisk Wywietl
moj pozycj!, zostanie uruchomiona aplikacja StreetView, a take wywietlony obraz znajdujcy
si w kierunku, w ktrym spogldamy. Jeeli wciniemy przycisk cofania, obrcimy si i ponownie klikniemy przycisk Wywietl moj pozycj!, zostanie wywietlony nowy obraz. Przyjrzyjmy si teraz rzeczywistym wartociom czujnikw.
Wartoci generowane przez kod zgodny z zalecanym rozwizaniem oraz przez czujnik orientacji
wydaj si identyczne albo bardzo do siebie zblione. Wartoci pochodzce z tego drugiego mechanizmu wygldaj bardziej stabilnie, mamy tu rwnie do czynienia z liczbami cakowitymi.
Wydaje si, e jest naprawd niele, ale to troch pochopny wniosek. Jeeli uniesiemy urzdzenie nieco nad gow i nachylimy je tak, eby mc spoglda na wywietlacz, odczyty generowane
obydwiema metodami zaczn si od siebie znaczco rni. Obrmy teraz urzdzenie, aby
znalazo si w trybie krajobrazowym. Powinnimy otrzyma wyniki przypominajce widoczne
na rysunku 26.6.
Co si stao? Wartoci przechyu bocznego uzyskane w metodzie zalecanej i z odczytu czujnika
orientacji uzyskay przeciwne znaki. Problem polega na tym, e w obydwu mechanizmach
stosowane s inne punkty odniesienia.
Nie wyjanilimy jeszcze, co si dzieje, gdy urzdzenie dziaa w trybie krajobrazowym, a nie
portretowym. Jeeli urzdzenie jest ustawione naprzeciwko twarzy uytkownika w trybie krajobrazowym, akcelerometry nie zamieniaj si miejscami, wic o x zastpuje o y i odwrotnie. W normalnych warunkach musielibymy w tym wypadku wprowadzi nieco przeksztace matematycznych, na szczcie klasa SensorManager zawiera pewn przydatn metod
remapCoordinateSystem(). Moe by ona wywoana pomidzy momentem uzyskania macierzy obrotw a wywoaniem metody getOrientation(). Podstawow funkcj tej metody jest
modyfikacja macierzy obrotw poprzez zamienienie osi ukadu wsprzdnych. Sygnatura tej
metody wyglda nastpujco:
public static boolean remapCoordinateSystem (float[] inR, int X, int Y, float[] outR)

938 Android 3. Tworzenie aplikacji

Rysunek 26.6. Dane o orientacji urzdzenia w przestrzeni otrzymane dwoma sposobami,


urzdzenie ustawione w trybie krajobrazowym

Przekazujemy w niej macierz obrotw oraz wartoci okrelajce sposb zamiany osi x i y, w wyniku czego otrzymujemy now tablic obrotw (outR), a take warto logiczn, ktra wskazuje,
czy proces przeksztacania zosta zakoczony powodzeniem. Wartoci x i y s staymi klasy
SensorManager, takimi jak AXIS_Z lub AXIS_MINUS_Y.
Omawiane tu rozwizanie zademonstrowalimy w przykadowej aplikacji VirtualJaxWithRemap,
ktr moemy pobra wraz z innymi projektami.

Deklinacja magnetyczna i klasa GeomagneticField


Chcielibymy poruszy jeszcze jeden temat zwizany z orientacj w przestrzeni i okrelajcymi
j urzdzeniami. Dziki kompasowi dowiemy si, gdzie znajduje si pnoc magnetyczna, nie
wskae nam on jednak rzeczywistej pnocy (geograficznej). Wyobramy sobie, e znajdujemy
si na linii pomidzy biegunem magnetycznym pnocnym a biegunem geograficznym pnocnym. Tworzyyby one wtedy kt 180 stopni. Im bardziej oddalilibymy si od obydwu biegunw, tym mniejszy kt wystpowaby pomidzy nimi. Rnica ktowa pomidzy obydwoma
typami biegunw zwana jest deklinacj magnetyczn. Warto ta moe by obliczona jedynie
w odniesieniu do okrelonego punktu na powierzchni Ziemi. Oznacza to, e aby okreli kierunek pnocy geograficznej, jeli znamy kierunek pnocy magnetycznej, musimy jeszcze zna
swoje pooenie geograficzne. Na szczcie Android jak zwykle suy pomoc, tym razem przy
uyciu klasy GeomagneticField.
Aby utworzy obiekt klasy GeomagneticField, musimy przekaza jej wsprzdne geograficzne.
Zatem w celu okrelenia kta deklinacji magnetycznej musimy zna pooenie punktu odniesienia. Wymagana jest rwnie znajomo godziny, w ktrej bdzie obliczana warto deklinacji.
Magnetyczny biegun pnocny zmienia swoje pooenie w czasie. Po utworzeniu tego obiektu
wywoujemy po prostu ponisz metod, aby uzyska kt deklinacji (wyraony w stopniach):
float declinationAngle = geoMagField.getDeclination();

Warto zmiennej declinationAngle bdzie dodatnia, jeeli pnoc magnetyczna bdzie si


znajdowaa na wschd od pnocy geograficznej.

Rozdzia 26 Czujniki

939

Czujniki grawitacji
Wraz z wersj 2.3 Androida wprowadzono czujniki grawitacji. W rzeczywistoci nie jest to
osobny ukad elektroniczny. Mamy tu do czynienia z wirtualnym czujnikiem opartym na odczytach akcelerometrw. Tak naprawd wykorzystywany jest tutaj opisany wczeniej mechanizm okrelania skadowej grawitacji przez akcelerometry, w ktrym jest ona oddzielana od
pozostaych si dziaajcych na urzdzenie. Nie moemy jednak modyfikowa tego mechanizmu i musimy zaakceptowa wszelkie wspczynniki i przeksztacenia dostpne w klasie tego
czujnika. By moe w przyszoci ten wirtualny czujnik bdzie wykorzystywa rwnie inne
sensory, na przykad yroskop, do dokadniejszego pomiaru wartoci grawitacji. Macierz wartoci w przypadku tego czujnika przekazuje odczyty grawitacji w taki sam sposb, jak miao to
miejsce w przypadku akcelerometrw.

Czujniki przypieszenia liniowego


Podobnie jak miao to miejsce w przypadku czujnika grawitacji, czujniki przypieszenia liniowego s wirtualnymi sensorami reprezentujcymi siy dziaajce na urzdzenie z wyczon
skadow grawitacji. Take i w tym przypadku ukazalimy wczeniej mechanizm pozwalajcy
na uzyskanie tych wartoci oraz usuwajcy z wyniku skadow przypieszenia ziemskiego.
Omawiany czujnik jeszcze bardziej uatwia nam to zadanie. By moe w przyszoci rwnie
i ten sensor wykorzysta inne czujniki, na przykad yroskop, do dokadniejszego mierzenia
przypiesze liniowych wpywajcych na urzdzenie. Tablica wartoci przedstawia wyniki
w taki sam sposb jak w przypadku wskaza akcelerometrw.

Czujniki wektora obrotu


Czujnik wektora obrotu przypomina wycofany ju czujnik orientacji pod tym wzgldem, e odczytuje orientacj urzdzenia w przestrzeni oraz podaje kty zalene od punktu odniesienia
wobec sprztowego akcelerometru (rysunek 26.2). W trakcie pisania ksiki nie byo jeszcze
adnych konkretnych informacji na temat tego czujnika. Prosimy zaglda na nasz stron
(www.androidbook.com), gdy bdziemy zamieszcza tam informacje na jego temat.

Czujniki komunikacji bliskiego pola


Wraz z wprowadzeniem wersji 2.3 Androida uzyskalimy moliwo stosowania specjalnych
terminali wykorzystujcych pole NFC (ang. Near Field Communication). Przypominaj one
nieco terminale RFID (ang. Radio Frequency Identification identyfikacja na czstotliwoci
radiowej), gwna rnica polega na tym, e zasig terminalu NFC wynosi 4 cale3. Oznacza to,
e czujnik NFC musi bardzo zbliy si do terminalu (ang. tag), eby mc go przeskanowa.
Terminale NFC mog zosta zaprogramowane w taki sposb, aby przesyay dane tekstowe, identyfikatory URI oraz metadane, na przykad jzyk, w jakim dana informacja jest przesyana.
Zwrmy uwag, e technologia NFC nie jest nowoci i w wielu krajach jest od lat znana i uywana. W rzeczywistoci w kilku pastwach terminale kasowe wyposaone w terminale NFC s
do powszechne. Gdy terminal wykryje czujnik NFC, klient moe dokoczy transakcj za
pomoc konta skojarzonego z identyfikatorem NFC. W internecie mona znale wiele filmw pokazujcych uytkownikw, ktrzy w ten sposb przeprowadzaj transakcje patnicze.
3

Okoo 10 cm przyp. tum.

940 Android 3. Tworzenie aplikacji


Reprezentanci firmy Google obiecuj, e pewnego dnia nasze portfele zostan zastpione przez telefony. Jest to istotnie kuszca wizja. Android pozwala przeksztaci telefon w taki terminal
wobec innego czytnika lub w czytnik wykrywajcy oraz skanujcy terminale NFC.
W rzeczywistoci istniej trzy tryby dziaania mechanizmu NFC. Pierwszy tryb polega na odczytywaniu i zapisywaniu bezkontaktowych terminali. Drugim jest tryb emulacji karty. W tym
trybie telefon pracujcy pod kontrol systemu Android moe sam zachowywa si jak terminal
NFC. Oczywist zalet tego rozwizania jest moliwo zmiany zachowania urzdzenia jako
terminalu po wciniciu jednego przycisku. To wanie dziki temu trybowi nasz telefon moe
przeksztaci si w portfel. Bez wzgldu na rodzaj posiadanej karty kredytowej lub biletu nasze
urzdzenie moe naladowa taki obiekt (oczywicie przy zastosowaniu wszelkich niezbdnych
zabezpiecze), dziki czemu czytnik dziaa tak, jakby obsugiwa kart kredytow, chocia w rzeczywistoci ma do czynienia z telefonem. Trzecim trybem mechanizmu NFC jest bezporednia,
rwnorzdna komunikacja. W tym przypadku kade urzdzenie jest rwnorzdne i nie s potrzebne terminale.
Wraz z wydaniem wersji 2.3.3 Androida moemy odczytywa terminale za pomoc urzdzenia
obsugujcego ten system, podobnie jak ma to miejsce w terminalu kasowym z wczeniejszego
przykadu, a take moemy zachowywa informacje w zapisywalnych terminalach NFC. Jeeli
urzdzenie uytkownika zostao poprawnie skonfigurowane, moe przesya dane do innego
urzdzenia wyposaonego w czujnik NFC za pomoc protokou P2P, zdefiniowanego przez firm
Google. W trakcie pisania ksiki nie bya jeszcze dostpna funkcja emulacji karty lub, dokadniej, terminalu NFC. W istocie jest to bardzo trudne zadanie do wykonania, czciowo z powodu
rnorodnoci architektur czujnikw NFC wprowadzanych do urzdze. Nie wiadomo, kiedy
tryb emulacji karty zostanie wprowadzony do pakietu SDK, wierzymy jednak, e kiedy to nastpi. W midzyczasie istnieje moliwo emulacji karty w pewnym zakresie na poziomie sterownikw za pomoc zestawu Android NDK (ang. Native Development Kit). Zagadnienie to
wykracza jednak poza zakres ksiki.
Oprcz przeprowadzania transakcji pieninych terminale NFC mog spenia rwnie wiele
innych zada. Na przykad mog by umieszczane w muzeum w pobliu eksponatw i wysya
adres URL do strony zawierajcej multimedialne informacje na temat danego przedmiotu. Na
przystankach autobusowych terminale mog przechowywa rozkad jazdy interesujcej nas linii.
Przedsibiorstwa mog umieszcza w rnych miejscach terminale NFC umoliwiajce atw
rejestracj do swoich usug mobilnych. By moe zapomnimy o kluczach w hotelach, gdy bdziemy mogli otwiera drzwi urzdzeniami wyposaonymi w NFC. Nawet produkty w sklepach
mog zosta zaopatrzone w terminale NFC, zawierajce dokadniejsze informacje na ich temat,
na przykad skadniki i wartoci odywcze, parametry techniczne lub multimedialne reklamy.

Aktywacja czujnika NFC


Obsuga czujnika NFC w Androidzie rni si od korzystania z pozostaych rodzajw sensorw.
Nie stosujemy klasy SensorManager, tylko NfcAdapter. Zazwyczaj w urzdzeniu dostpny jest
tylko jeden czujnik NFC, zarzdzajcy zapisywaniem informacji na terminalach i odczytywaniem ich treci, a take rozdzielaniem terminali pomidzy aktywnoci. Adapter moe by wczony lub wyczony, natomiast w widoku Ustawienia znajdziemy opcje pozwalajce na jego
uaktywnianie lub wyczanie. Opcje adaptera NFC mona zazwyczaj znale w zakadce Sieci
bezprzewodowe. Jeeli adapter jest wczony, po wykryciu terminalu nastpi do skomplikowany proces okrelajcy, ktra aktywno powinna otrzyma intencj informujc o obecnoci
tego terminalu. Wszystko zaley od rodzaju danych przechowywanych przez terminal NFC, a tak-

Rozdzia 26 Czujniki

941

e od obecnoci filtrw intencji dla zainstalowanych aplikacji w urzdzeniu. Uwzgldniana jest


take jeszcze jedna informacja, mianowicie czy aktualnie pierwszoplanowa aktywno moe
otrzymywa terminale NFC. Niedugo zajmiemy si dokadniej tym zagadnieniem.
Aby uzyska dostp do adaptera, tworzymy najpierw za pomoc metody getSystemService()
wystpienie obiektu NfcAdapter. Nastpnie wywoujemy metod getDefaultAdapter() w sposb
zaprezentowany poniej:
NfcManager manager = (NfcManager)
context.getSystemService(Context.NFC_SERVICE);
NfcAdapter adapter = manager.getDefaultAdapter();

Otrzymamy w ten sposb singletonowy obiekt klasy NfcAdapter. Aby sprawdzi, czy klasa
NfcAdapter jest aktywna, wprowadzamy metod isEnabled(), ktra powraca z wartoci
logiczn okrelajc, czy technologia NFC zostaa wczona w panelu Ustawienia. Nigdzie nie
znalelimy udokumentowanego sposobu na programowe wczanie i wyczanie adaptera NFC.
Jeeli jest wyczony, a dana aplikacja wymaga jego uruchomienia, musimy powiadomi uytkownika o koniecznoci rcznego wczenia czujnika. Aby wywietli uytkownikowi waciwy
widok ustawie, moemy skorzysta z nastpujcego fragmentu kodu:
startActivityForResult(new Intent(
android.provider.Settings.ACTION_WIRELESS_SETTINGS), 0);

Po przetworzeniu tego kodu Android otworzy odpowiedni widok ustawie i uytkownik bdzie
mg wczy adapter NFC. Metoda zwrotna onActivityResult() zostanie wywoana w chwili
zamknicia okna ustawie przez uytkownika. Pamitajmy, e uytkownik moe nie wczy
adaptera pomimo powiadomienia. Nasza aplikacja powinna by przygotowana rwnie na ten
scenariusz.

Trasowanie terminali NFC


Nadszed odpowiedni moment na omwienie rnych rodzajw technologii oraz terminali
NFC. Mechanizm NFC nie jest ograniczony do jednego standardu. W rzeczywistoci uytkownik
moe natrafi na kilka odmian terminali rnicych si pomidzy sob. Terminale te nie posiadaj takiej samej architektury, co oznacza, e Android musi zawiera dla kadego z nich oddzieln klas. Jeli zajrzymy do wntrza pakietu android.nfc.tech.package, znajdziemy w nim
kilka klas dotyczcych rnych technologii terminali NFC, poczwszy od klasy MifareClassic,
poprzez NfcV, a skoczywszy na ISO-DEP. Kady rodzaj terminalu rni si od innych struktur
wewntrzn, natomiast uzyskanie dostpu do zawartych w nich danych i manipulowanie nimi
wymaga stosowania oddzielnych metod. Na szczcie Android zosta zaopatrzony w klas Tag
uatwiajc komunikacj NFC i za jej pomoc moemy utworzy dowolny rodzaj terminalu.
Po utworzeniu wystpienia okrelonego terminalu NFC moemy przeprowadza na nim dopuszczalne operacje. Oznacza to take, e naley wzi pod uwag kilka czynnikw przed wysaniem terminalu do aktywnoci. Wyjanimy najpierw, w jaki sposb jest tworzona intencja
terminalu NFC, dziki czemu Czytelnik zrozumie mechanizm tworzenia odpowiednich filtrw intencji.
W trakcie przesyania intencji zawierajcej dane terminalu obiekt klasy Tag zawsze jest rozkadany do pakietu dodatkowych danych intencji, a jego kluczem jest EXTRA_TAG. Jeeli terminal zawiera informacje typu NDEF, zostaje dodana kolejna warto z kluczem EXTRA_NDEF_
MESSAGES. Ostatnim elementem dodatkowym moe by identyfikator terminalu, ktrego
klucz to EXTRA_ID. Dwie ostatnie wartoci s opcjonalne i zale od obecnoci danych w terminalu. Wszystkie intencje NFC s wysyane za pomoc metody startActivity(). Zauwamy,

942 Android 3. Tworzenie aplikacji


e tak naprawd nie musimy nigdy uzyskiwa dostpu do adaptera, aby otrzymywa komunikaty NFC. Wiadomoci z intencji bd przychodziy do aplikacji, tak samo jak wszelkie inne
aplikacje przesyane z rnych rde, tak dugo, jak odpowiadaj one filtrowi (filtrom) intencji.
Naley zwrci uwag, e technologia NFC dotyczy tylko urzdze wyposaonych
w czujnik NFC. Mechanizmy wymagane do tworzenia odpowiednich intencji zawieraj
funkcje nieobsugiwane przez pakiet SDK. Oznacza to, e samodzielne tworzenie
testowej aktywnoci nadawczej jest bardzo trudne. W tym podrozdziale staramy si
wyjani mechanizmy rzdzce tym systemem, dla ktrych nie da si wasnorcznie
napisa kodu. Oznacza to rwnie, e aby rzeczywicie przetestowa aplikacj
wykorzystujc mechanizm NFC, trzeba wykorzysta fizyczne urzdzenie oraz terminale
NFC. By moe kiedy firma Google zaimplementuje odpowiednie funkcje w emulatorze
lub w narzdziu DDMS.

W przypadku intencji terminalu warto dziaania zaley od rodzaju informacji wykrytych


w terminalu. Dla danej intencji istniej trzy moliwe wartoci dziaania:
1. ACTION_NDEF_DISCOVERED stanowi dziaanie w przypadku wykrycia bloku danych
NDEF w terminalu. W takim przypadku Android szuka nastpnie obecnoci elementu
NdefRecord w pierwszym obiekcie NdefMessage. Jeeli elementem NdefRecord jest
identyfikator URI lub rejestr SmartPoster, w polu danych intencji zostanie umieszczony
ten identyfikator. Jeeli z kolei zostanie wykryty rekord MIME, pole typu intencji
zostanie zmodyfikowane do odpowiedniego typu MIME terminalu. System zacznie
nastpnie szuka odpowiedniej aktywnoci dla tej intencji oraz waciwego algorytmu
dopasowania intencji. Jeeli nie zostanie znaleziona adna aktywno, bieca
intencja zostanie porzucona i Android sprbuje utworzy nastpn intencj NFC.
2. ACTION_TECH_DISCOVERED jest dziaaniem podejmowanym, w przypadku gdy nie
zostan wykryte dane NDEF lub jeli nie znajdziemy adnej aktywnoci obsugujcej
ten format danych, lecz dostpna bdzie technologia terminali. W tym przypadku
Android dodaje metadane do intencji, za pomoc ktrych zostanie uruchomiona
odpowiednia technologia terminali. W terminalu NFC moe zosta zaimplementowanych
kilka rnych technologii, zwaszcza e format Ndef bardziej przypomina wirtualny
mechanizm. Android wyszukuje aktywno pasujc do intencji. Jeeli zostanie
znaleziona, przelemy do niej intencj, w przeciwnym wypadku intencja ta zostanie
porzucona i system wyprbuje trzeci rodzaj intencji NFC.
3. ACTION_TAG_DISCOVERED jest ostatnim dziaaniem definiowanym dla terminalu NFC.
Jest ono podejmowane, gdy wszystkie pozostae dziaania oka si niedopasowane
do aktywnoci. Intencja tego typu nie przenosi rwnie danych ani typu MIME.
Jeeli ta intencja nie zostanie dopasowana do adnej aktywnoci w urzdzeniu, system
NFC zaprzestaje prb i informacje o terminalu zostan usunite.

Odbieranie terminali NFC


Bez wzgldu na to, czy zdecydujemy si na utworzenie filtrw intencji za pomoc kodu, czy
w pliku AndroidManifest.xml, musimy bardzo dobrze wiedzie, czego szukamy, a filtry intencji
przygotowa z du ostronoci. Jeeli zdefiniujemy je zbyt rygorystycznie, aplikacja nie bdzie powiadamiana o istotnych terminalach. Z kolei jeli zdefiniujemy je niezbyt precyzyjnie,
aplikacja zacznie otrzymywa komunikaty o terminalach, ktre nie s dla niej przeznaczone.
W przypadku gdy nasza aplikacja otrzyma terminal dla niej nieprzeznaczony, by moe w urz-

Rozdzia 26 Czujniki

943

dzeniu istnieje inna aplikacja, dla ktrej tego typu terminale s przeznaczone, jednak ta waciwa
aplikacja nie otrzymaa terminalu. Taka sytuacja moe nastpi, w przypadku gdy filtr intencji
znalaz wicej ni jedn aplikacj pasujc do terminalu. Wtedy system wywietla monit, aby
uytkownik wybra waciw aplikacj. Moe si zdarzy, e uytkownik wybierze aplikacj, dla
ktrej dany terminal nie jest przeznaczony. Istnieje wic kolejny powd, dla ktrego naley
ostronie definiowa filtry intencji dla terminali NFC: jeeli uytkownik otrzyma monit o wybr
aplikacji, z duym prawdopodobiestwem przed podjciem decyzji wyjdzie z zasigu terminalu.
Jeeli moemy okreli, jakiego typu dane terminali bd przetwarzane przez nasz aplikacj,
oznacza to, e moemy je bardzo dokadnie sprecyzowa, na przykad za pomoc niestandardowego schematu identyfikatora URI lub wasnego typu MIME.
Wybr filtru intencji zaley od rodzaju dziaania umieszczonego wewntrz intencji terminalu
NFC (zostao to omwione powyej). Na listingu 26.8 zosta umieszczony przykadowy filtr
intencji dla terminalu NDEF, ktry moemy umieci w pliku AndroidManifest.xml.
Listing 26.8. Filtr intencji dla terminalu NDEF zawierajcego typ MIME
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<data android:mimeType="type/subtype" />
</intent-filter>

Zamiast wartoci type/subtype moglibymy oczywicie wskaza okrelony, poszukiwany przez


nas typ MIME lub wprowadzi symbole wieloznaczne w przypadku akceptowania kadego typu
lub podtypu. Moemy na przykad zdefiniowa atrybut mimeType jako text/*, dziki czemu
akceptowane byyby wszystkie formaty tekstu. Nie musimy jednak definiowa typu MIME dla
terminalu NDEF. Jeeli terminal ten posiada identyfikator URI, moemy utworzy filtr intencji
przypominajcy kod z listingu 26.9.
Listing 26.9. Filtr intencji dla terminalu NDEF zawierajcego identyfikator URI
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<data android:scheme="geo" />
</intent-filter>

W tym przykadzie definiujemy schemat geo, dziki czemu po wykryciu terminalu zawierajcego
identyfikator rozpoczynajcy si od czonu geo: zostanie uruchomiona nasza aktywno. Moemy
stosowa wszystkie atrybuty wza <data> do okrelania, ktre dane terminalu NFC s oczekiwane przez nasz aktywno.
Jeeli nasza aktywno wymaga terminali NFC utworzonych w okrelonej technologii, moemy
skorzysta z filtru intencji zaprezentowanego na listingu 26.10. Moe rwnie zaistnie sytuacja, w ktrej zostanie wykryty terminal NDEF, lecz adna aktywno nie bdzie dopasowana
do przetwarzania intencji NDEF_DISCOVERED. Rwnie w takim przypadku nasza aktywno
moe otrzyma t intencj, dopki jest ona zgodna z filtrem intencji. Inaczej mwic, jeeli
intencja terminalu zawierajca dziaanie NDEF_DISCOVERED nie zostanie dostarczona do aktywnoci wyszukujcej tego typu dziaania, zostanie wysana do aktywnoci oczekujcej terminalu utworzonego w okrelonej technologii.

944 Android 3. Tworzenie aplikacji


Listing 26.10. Filtr intencji dla terminalu NFC zawierajcego okrelon technologi
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />

Zwrmy uwag, e wstawilimy teraz dziaanie definiujce poszukiwan technologi, a zamiast wza <data> wprowadzilimy znacznik <meta-data>, ktry znajduje si poza znacznikiem <intent-filter>. Mamy rwnie do czynienia z innymi znacznikami w tym wle, ktre
znajduj si w oddzielnym pliku, umieszczonym w katalogu /res/xml. Na listingu 26.11 demonstrujemy przykadowy plik nfc_tech_filter.xml.
Listing 26.11. Przykadowy plik XML zawierajcy filtr technologii NFC
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
</resources>

Filtr ten definiuje dwa rodzaje terminali oczekiwanych przez nasz aplikacj. Terminal NFC
zazwyczaj zawiera list obsugiwanych przez niego technologii. Jeeli ktrykolwiek z elementw
tej listy zosta wymieniony w filtrze z listingu 26.11, nasza aktywno uzyska dostp do intencji
tego terminalu.
Na listingu 26.11 pierwszy rodzaj terminalu okrela technologie NfcA oraz MifareUltralight,
w drugim za zdefiniowano: NfcB i Ndef. Moemy dodawa kolejne wzy <resources> do
tego pliku w celu definiowania nastpnych terminali akceptowanych przez nasz aktywno. Uwzgldniane tutaj technologie bior swoje nazwy od nazw klas dostpnych w pakiecie
android.nfc.tech, ale powinnimy wpisywa jedynie te mechanizmy, ktre bd przydatne
aktywnoci. Wzy potomne znacznika <tech-list> zawieraj wszystkie technologie, ktre
powinien posiada terminal NFC, aby jego intencja pasowaa do aktywnoci. Wszystkie technologie z danej listy musz si znajdowa na licie technologii obsugiwanych przez terminal
NFC. Zatem lista technologii w filtrze moe zawiera mniej elementw ni analogiczna lista
w terminalu NFC, nie moe jednak wystpi odwrotna sytuacja. Kontynuujc powyszy przykad, jeeli w terminalu NFC znajdzie si wycznie wskazanie technologii Ndef, terminal ten nie
zostanie przepuszczony przez aden filtr i aktywno nie otrzyma jego intencji. adna z wymienionych list filtru intencji nie stanowi podzbioru na licie terminalu. Gdyby ten terminal
zawiera technologie NfcA, NfcB oraz Ndef, okazaby si zgodny z drug specyfikacj i zostaby
przesany do aktywnoci. Ta druga specyfikacja stanowi podzbir listy technologicznej terminalu
NFC. Terminal ten byby dopasowany, nawet gdyby zawiera dodatkowe technologie, niewymienione w filtrze intencji.

Rozdzia 26 Czujniki

945

Ostatni filtr intencji, ktry moe si przyda Czytelnikowi, zosta zaprezentowany na listingu
26.12. Charakteryzuje go uniwersalno. Oznacza to, e jeli po wykryciu terminalu NFC nie
znaleziono adnej aktywnoci odbierajcej terminale NDEF bd zgodnej z okrelonymi w nim
technologiami lub jeli mamy do czynienia z nieznanym typem terminalu, zostanie utworzona
intencja zawierajca dziaanie ACTION_TAG_DISCOVERED.
Listing 26.12. Filtr intencji dla nieznanego lub nieprzetwarzanego terminalu NFC
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>

Zwrmy uwag, e dla tego filtru intencji nie zdefiniowano adnego wza <data> ani
<meta-data>, poniewa nie s przenoszone adne dane w intencji oznaczonej dziaaniem
ACTION_TAG_DISCOVERED. W normalnej sytuacji oznaczaoby to konieczno wprowadzenia
znacznika <category>. Sprawa ma si jednak inaczej z intencjami terminali NFC. Stanowi
one specjalny przypadek, zatem w filtrach intencji nie s wymagane tego typu terminale w celu
dopasowania intencji. Jeeli intencja otrzymuje dziaania ACTION_TAG_DISCOVERED, oznacza to,
e system nie zdoa odnale aktywnoci dla terminali NFC. W tym momencie kada aktywno przyjmujca to dziaanie otrzyma intencj tego terminalu. W wikszoci standardowych
operacji nigdy nie natrafimy na intencj znacznika ACTION_TAG_DISCOVERED, poniewa wikszo terminali NFC bdzie dopasowanych do kryteriw NDEF lub TECH.
Istnieje jeszcze jeden sposb, w jaki aktywno moe otrzyma intencj terminalu NFC zastosowanie systemu dyspozycji pierwszoplanowej. Jeeli nasza aktywno znajduje si na pierwszym planie (co oznacza, e zostaa uruchomiona metoda onResume() i uytkownik korzysta
z tej aktywnoci), moemy utworzy intencj oczekujc, tablic filtrw intencji, tablic list
technologii, a nastpnie wprowadzi nastpujce wywoanie:
mAdapter.enableForegroundDispatch(this, pendingIntent,
intentFiltersArray, techListsArray);

gdzie mAdapter jest adapterem NFC, a this stanowi odniesienie do naszej aktywnoci. Za pomoc tego wywoania skutecznie wystawiamy nasz aktywno przed wszystkie pozostae aktywnoci i jeeli ktrykolwiek z jej filtrw jest dopasowany do wykrytego terminalu NFC, to
aktywno ta przetworzy terminal. Jeeli aktywno nie otrzyma intencji terminalu z powodu
niedopasowania, jej dziaanie bdzie sprawdzane wobec pozostaych aktywnoci. Musimy wywoa t metod z poziomu wtku interfejsu uytkownika, a najlepiej tego dokona w metodzie
onResume() naszej aktywnoci. Wymagane byoby rwnie wprowadzenie nastpujcego
wywoania:
mAdapter.disableForegroundDispatch(this);

z poziomu metody zwrotnej onPause(), dziki czemu nasza aktywno nie otrzyma intencji,
ktrej nie bdzie moga przetworzy. Gdy aktywno w taki sposb otrzyma intencj, przekae
j za pomoc metody zwrotnej onNewIntent().
Mamy tu do czynienia ze standardow intencj oczekujc. Tablica intentFiltersArray moe
stanowi zbir potrzebnych nam obiektw IntentFilter, z ktrych kady definiuje okrelone
dziaanie, a take, w razie potrzeby, dowolne dane lub typy MIME. Na listingu 26.13 widzimy
przykadowy kod generujcy filtr intencji dla obiektu Ndef, ktry nastpnie zostaje wstawiony
do tablicy.

946 Android 3. Tworzenie aplikacji


Listing 26.13. Kod filtru intencji dla technologii Ndef
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("text/*");
}
catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[] {
ndef,
};

Nie zapominajmy, e w tablicy filtrw intencji moe si znale wiele wystpie obiektw
IntentFilter, zawierajcych te same lub rne dziaania, a take posiadajcych dane lub
ich pozbawione. To samo dotyczy wartoci pl typw.
Obiekt techListsArray jest tablic, ktrej wartociami s inne tablice listy zawierajce
nazwy klas obsugiwanych przez znacznik NFC. Moemy okreli wiele list technologii. Zostao
to zaprezentowane na listingu 26.14, ktry jest odpowiednikiem pliku zasobw ukazanego na
listingu 26.11.
Listing 26.14. Kod tabeli zawierajcej listy technologii
techListsArray = new String[][] {
new String[] { NfcA.class.getName(),
MifareUltralight.class.getName() },
new String[] { NfcB.class.getName(),
Ndef.class.getName() }
};

Jeli po przeprowadzeniu omawianego procesu konfiguracji nasza aktywno uzyska dostp do


intencji terminalu NFC, spowoduje to uruchomienie metody zwrotnej onNewIntent() w celu
odebrania terminalu. Z tego miejsca moemy odczyta dodatkow zawarto intencji, ktr
stanowi przechowywane informacje terminalu, co bdzie tematem nastpnego podpunktu.
Owszem, aby w dynamiczny sposb uzyskiwa dostp do intencji terminalu NFC, trzeba woy
duo pracy, jednak z drugiej strony, jeeli po uruchomieniu przez uytkownika tylko ta aktywno
odbieraa terminale, warto wykorzysta pokazane tu rozwizanie. Zwrmy jeszcze uwag, e
prawdopodobnie nie ma sensu jednoczesne korzystanie z tej metody i umieszczanie filtrw
intencji w pliku manifecie, jednak z technicznego punktu widzenia jest to moliwe.

Odczytywanie terminali NFC


Jak ju wczeniej sugerowalimy, odczytywanie terminali NFC jest do skomplikowan czynnoci. Dokadniej mwic, sam proces dostarczania terminalu do aplikacji jest zoony. Wyjaniajc to na najbardziej podstawowym poziomie, w momencie wykrycia terminalu NFC system
sprbuje okreli aktywno, do ktrej naley wysa intencj tego terminalu. W przeciwiestwie do aktywnoci obsugujcych pozostae czujniki omawiane w tym rozdziale, aktywno
przetwarzajca terminale NFC nie musi by uruchomiona w momencie ich wykrycia i z pewnoci nie otrzyma informacji o terminalu za pomoc obiektu nasuchujcego. Powiadomiona
aktywno otrzyma intencj, a to z kolei moe oznacza jej uruchomienie w celu przetworzenia
danych terminalu.

Rozdzia 26 Czujniki

947

Jedn z pierwszych kwestii rozwaanych w procesie projektowania aplikacji otrzymujcej i przetwarzajcej intencje NFC jest konieczno obsugi fizycznego terminalu znajdujcego si w otoczeniu urzdzenia za pomoc interfejsu sprztowego. Interfejs API generuje wywoania blokujce, co oznacza, e nie bd one przekazywane tak szybko, jak bymy sobie tego yczyli,
w zwizku z czym bdziemy musieli uruchamia metody terminalu w osobnym wtku.
Informacje terminalu NFC s umieszczone w pakiecie dodatkowych danych otrzymywanej intencji. Po odebraniu intencji moemy uzyska dostp do tych informacji za pomoc nastpujcego fragmentu kodu:
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
String[] techlists = tag.getTechLists();

Jeeli zdefiniowany przez nas filtr intencji jest bardzo dokadny, bdziemy ju doskonale wiedzie, jaki rodzaj terminalu otrzymalimy. Jeeli jednak zdefiniowalimy wiksz liczb akceptowanych technologii terminalu, moemy teraz sprawdzi listy tych technologii, aby pozna
mechanizmy, z jakimi bdziemy mieli do czynienia w terminalu. Kady cig znakw na tej
licie stanowi nazw klasy przechowujcej technologi obsugiwan przez wykryty terminal.
Jeeli nasz terminal obsuguje klas android.nfc.tech.Ndef, moemy wykorzysta poniszy kod do uzyskania bardziej bezporedniego dostpu do danych NDEF:
NdefMessage[] ndefMsgs = intent.getParcelableArrayExtra
(NfcAdapter.EXTRA_NDEF_MESSAGES);

Teoretycznie moemy otrzyma warto null, w przypadku gdy intencja nie bdzie zawieraa
informacji NDEF. W przeciwnym wypadku nie powinno by problemu z analiz skadni przesyanych danych. Moemy odczytywa elementy klasy NdefMessage z poziomu intencji, zlicza je i w przypadku kadej z nich odbiera zawarte w nich obiekty NdefRecord.
Obiekty klasy NdefRecord s do interesujce. Nie zaszkodzi, jeeli Czytelnik zajrzy do specyfikacji technologii NFC, dostpnej pod adresem http://www.nfc-forum.org/specs/. Aby uzyska do
niej dostp, trzeba zaakceptowa postanowienia licencyjne NFC Forum. Jest to bezpatny proces,
naley jednak poda imi i nazwisko, adres, numer telefonu i adres e-mail. Alternatywnym rozwizaniem jest aplikacja NfcDemo dostpna w pakiecie SDK Androida 2.3.3, w folderze z przykadowymi programami. Kod rdowy tego przykadu zosta umieszczony na stronie http://developer.
android.com/resources/samples/NFCDemo/index.html. Aplikacja ta odbiera intencje NFC i wywietla zawarto klasy NdefRecord w widoku ListView. Sytuacja komplikuje si z powodu obecnoci kilku odmian klasy NdefRecord, ktre mog zosta przesane wraz z obiektami NdefMessage. Kada odmiana tej klasy ma inne zastosowanie. Na przykad odmiana Text przechowuje
tekst w okrelonym jzyku. W odmianie Uri znajdziemy identyfikator URI. Ze wszystkich
znanych rodzajw rekordw NDEF aplikacja NfcDemo obsuguje tylko trzy, z czego dwa przed
chwil omwilimy, a trzecim jest SmartPoster, ktremu wkrtce przyjrzymy si uwaniej.
Format klasy NdefRecord skada si z trzybitowego pola TNF (ang. Type Name Format format
nazwy typu), pola typu o zmiennej dugoci, pola identyfikatora o zmiennej dugoci oraz
pola adunku o zmiennej dugoci. A zatem mamy do czynienia z dwoma kategoriami pl.
Pole TNF stanowi podstawowy typ tego obiektu i definiuje zawarto caego rekordu. Moe
by to na przykad bezwzgldny rekord URI (TNF_ABSOLUTE_URI) lub oficjalny rekord RTD
(TNF_WELL_KNOWN). Nastpne pole typu przechowuje dokadniejsze informacje na temat rodzaju rekordu w oparciu o warto pola TNF. Jeeli w polu TNF zostaa zdefiniowana staa
TNF_WELL_KNOWN, pole to bdzie si skadao ze staych RTD_* klasy NdefRecord, takich jak
RTD_SMART_POSTER. Jeeli wartoci pola TNF jest TNF_ABSOLUTE_URI, kolejnym polem typu
bdzie konstrukt BNF niezalenego identyfikatora URI, zdefiniowany w specyfikacji RFC 3986.

948 Android 3. Tworzenie aplikacji


Typ rekordu TNF_UNCHANGED jest stosowany, w przypadku gdy adunek komunikatu
z powodu rozmiarw zostaje rozmieszczony w kilku obiektach NdefRecord. System
automatycznie obsuguje tego typu podzielone obiekty NdefRecord, wic nie powinnimy
nigdy mie do czynienia z wartoci TNF_UNCHANGED. Pakiet android.nfc czy
poszczeglne elementy adunku w jeden wielki obiekt NdefRecord.

Nastpnym polem obiektu NdefRecord jest jego identyfikator. Odczytywany rekord moe
posiada identyfikator, chocia nie jest to wymagane.
Na kocu mamy do czynienia z adunkiem. Moe to by do dua tablica bajtowa, ktra posiada jednak wewntrzn struktur, zalen od rodzaju obiektu NdefRecord. Trzeba zwrci
uwag na t struktur. W przypadku typu RTD URI pierwszy bajt tej tablicy reprezentuje pocztek identyfikatora URI. Na przykad warto 1 oznacza http://www. i od tego czonu
bdzie rozpoczyna si kady identyfikator URI znajdujcy si we wntrzu adunku. W przypadku typu rekordu Text pierwszy bajt tabeli adunku stanowi warto kodowania tekstu
(UTF-8 lub UTF-16), a take dugo tablicy jzyka, wystpujcej tu po polu stanu kodowania.
Za polem jzyka znajduje si waciwy tekst. W przypadku obiektw SmartPoster sprawa jest
bardziej skomplikowana, gdy kady obiekt NdefRecord przechowuje obiekty NdefMessage,
przechowujce z kolei wicej obiektw NdefRecord. Te ostatnie mog zawiera rekord Title
(zbudowany tak samo jak rekordy Text), rekord URI (niernicy si od omawianego wczeniej), rekord zalecanego dziaania, rekord rozmiaru, rekord ikony oraz rekord typu. Warto
zalecanego dziaania wskazuje na czynnoci, jakie aplikacja moe wykonywa w przypadku danych obiektu SmartPoster. Zwrmy uwag, e wartoci zalecanego dziaania nie s wymieniane w dokumentacji klasy NdefRecord. S one nastpujce:
-1
0
1
2

UNKNOWN
DO_ACTION
SAVE_FOR_LATER
OPEN_FOR_EDITING

Tylko od nas zaley, co z nimi zrobimy, chocia oczywicie prawdopodobnie bdziemy chcieli
sprbowa przeprowadzi na odczytywanym terminalu najwaciwsz operacj. Jeli na przykad
mamy do czynienia z formatem rekordu TNF_WELL_KNOWN, jego typem jest RTD_SMART_POSTER,
a zalecane dziaanie przybiera warto 0 (DO_ACTION) i jest poczone z adresem URL strony
WWW, najprawdopodobniej bdziemy chcieli uruchomi przegldark internetow i otworzy
stron dostpn pod tym adresem. Rekord rozmiaru pozwala zdefiniowa wielko obiektu
czekajcego po drugiej stronie cza. Jeeli terminal odnosi si do pobieralnego pliku, w rekordzie rozmiaru moe zosta okrelony jego rozmiar. W rekordzie ikony przechowywany jest obraz
ikony, wykorzystywany przez urzdzenie do jej wywietlania wraz z tytuem oraz adresem URL.
Rekord typu nie jest tym samym co warto formatu TNF czy typ klasy NdefRecord. Jest on
wykorzystywany w terminalach SmartPoster, a w tym przypadku reprezentuje on typ MIME
obiektu znajdujcego si po drugiej stronie adresu URL. Urzdzenie moe nie obsugiwa
danego typu MIME i w ten sposb zostanie uniemoliwione pobieranie tego obiektu.
Jedynym niezbdnym podrekordem terminalu SmartPoster jest rekord URI i tylko jedno
jego wystpienie moe si znajdowa w kadym terminalu. Moemy wypisa wiele rekordw
Title, kady przechowujcy tekst w innym jzyku. Moliwe jest rwnie zamieszczanie wielu
rekordw ikony, pod warunkiem e kady z nich posiada osobny typ MIME dla swojego formatu.

Rozdzia 26 Czujniki

949

W przypadku wszystkich rodzajw terminali NFC, w tym rwnie terminali NDEF, moemy
zastosowa poniszy fragment kodu, aby uzyska wystpienie danego typu terminali:
NfcA nfca = NfcA.get(tag);

Za pomoc tak utworzonego obiektu moemy uzyska dostp do okrelonych metod, najbardziej nadajcych si dla danego typu terminalu. W przypadku terminali Ndef i NdefFormatable
klasy NdefMessage oraz NdefRecord okazuj si bardzo przydatne do przetwarzania ich danych.
Klasy pozostaych rodzajw terminali posiadaj odpowiednie metody umoliwiajce obsug
tych terminali i zawartych w nich informacji. Mamy do dyspozycji odpowiednie metody do
odczytu i zapisu danych na terminalu. Zwrmy uwag, e zapisywanie danych w terminalu nie
jest tym samym co emulacja karty. Zapisywanie terminalu oznacza, e w pobliu urzdzenia
znajduje si jaki terminal, ktry mona zmodyfikowa (jeli mamy odpowiednie uprawnienia).
Emulacja karty jest oddzielnym procesem.

Emulacja karty NFC


Emulacja karty oznacza imitowanie zachowania urzdzenia wyposaonego w czujnik NFC jako
terminal NFC przed odpowiednim czytnikiem. Oznacza to, e w okrelonym miejscu lokalnego
urzdzenia s przechowywane dane. Jeeli w zasigu tego urzdzenia znajdzie si czytnik NFC,
ktry zada dostpu do tych informacji, zostan mu one udostpnione. Funkcja ta nie bya
jeszcze dostpna w czasie pisania tej ksiki, chocia oczekujemy, e w kocu bdzie mona
z niej korzysta. Jeeli emulacja karty jest Czytelnikowi naprawd potrzebna, zalecamy zapoznanie si z dokumentacj sieciow, omawiajc przeprowadzenie tego procesu na najbardziej
elementarnym poziomie urzdzenia, tj. na poziomie zestawu NDK.

Poczenia rwnorzdne (P2P) NFC


Zestaw Android SDK umoliwia w ograniczonym stopniu obsug komunikacji P2P (ang.
peer-to-peer komunikacja rwnorzdna) pomidzy dwoma urzdzeniami za pomoc technologii NFC.
Funkcja ta nie jest pozbawiona wad, mianowicie korzystajca z niej aplikacja musi by uruchomiona i dziaa na pierwszym planie, a ponadto obsugiwa format NDEF. By moe pozostae technologie bd w przyszoci obsugiwa komunikacj P2P, na razie jednak pozwala
na to jedynie format NDEF. Oznacza to take, e telefon musi by wczony, a aplikacja uruchomiona, aby mc w ten sposb nawiza komunikacj z innym urzdzeniem.
Aby zaimplementowa funkcj P2P, wykorzystamy metod klasy NfcAdapter zwan enable
Przyjmuje ona dwa parametry: aktywno oraz obiekt NdefMessage,
ktre zostan wysane w momencie dania danych przez inne urzdzenie NFC. Podobnie jak
w przypadku omwionego wczeniej systemu dyspozycji pierwszoplanowej, metoda ta powinna
by wywoywana we wntrzu metody onResume(), a wyczana w metodzie onPause(). Obiekt
NdefMessage moe by dowolny, ale nasza aktywno powinna si znajdowa na pierwszym
planie podczas prby uzyskania dostpu do danych urzdzenia przez czytnik. Firma Google
stwierdzia, e urzdzenie znajdujce si po drugiej stronie musi posiada implementacj
protokou wysyania NDEF zawartego w pakiecie com.android.npp, co umoliwi nawizywanie
komunikacji z telefonem, jednak w momencie pisania ksiki nie byo adnych konkretnych
informacji na ten temat. Bdziemy jednak zamieszcza aktualizacje na naszej stronie.
ForegroundNdefPush().

950 Android 3. Tworzenie aplikacji


Wyjanialimy wczeniej sposb wykorzystania wza uses-feature zawierajcego wpisy
o czujnikach, dziki czemu mona si byo upewni, e dane urzdzenie jest wyposaone
w czujniki niezbdne do dziaania danej aplikacji jeszcze przed jej instalacj. Czujnik NFC
nie stanowi w tym przypadku wyjtku. Powinnimy umieci nastpujcy fragment w pliku
AndroidManifest.xml, jeeli chcemy, aby nasza aplikacja bya instalowana jedynie na urzdzeniach wyposaonych w czujnik NFC:
<uses-feature android:name="android.hardware.nfc" />

Lepiej upewni si rwnie, e plik AndroidManifest.xml zawiera odpowiednie uprawnienia,


pozwalajce aplikacji na uzyskanie dostpu do technologii NFC:
<uses-permission android:name="android.permission.NFC" />

Testowanie technologii NFC za pomoc aplikacji NfcDemo


Omwilimy du cz interfejsu technologii NFC, teraz jednak warto zada pytanie: w jaki
sposb moemy przetestowa nasz aplikacj? Jeli chodzi o terminale NFC, by moe udaoby
si znale w pobliu jakie wyposaone w nie obiekty. Nie powinno by to zbyt trudne w krajach, w ktrych technologia ta zdya si ju zadomowi. W Polsce moe by o wiele trudniej.
Moemy zakupi wasne terminale NFC; na caym wiecie mona znale kilku producentw
sprzedajcych te podzespoy, a take odpowiednie oprogramowanie, umoliwiajce zapisywanie na nich informacji. Niestety, narzdzie DDMS nie zostao wyposaone w funkcj przesyania
intencji wykrywanych terminali do emulatora. Przykadowa aplikacja NfcDemo zostaa doczona
do wersji 2.3 Androida w czasach, gdy byo dostpne wycznie dziaanie intencji ACTION_TAG_
DISCOVERED. Wraz z wersj 2.3.3 Android si rozwin, czego nie mona powiedzie o aplikacji
NfcDemo. Mona w niej znale przydatne informacje na temat ukadw graficznych terminali
NFC oraz o znaczeniu poszczeglnych bajtw w tabelach danych terminali. Miejmy nadziej,
e wkrtce pojawi si zaktualizowana wersja tej aplikacji testowej, dostosowana do pracy z fizycznymi terminalami NFC oraz nowym systemem technologii NFC.
Jeeli Czytelnik zdecyduje si na wczytanie przykadowej aplikacji NfcDemo, potrzebna mu bdzie
dodatkowa, zewntrzna biblioteka. Plik tej biblioteki znajdziemy na stronie http://code.google.com/
p/guava-libraries/. Po rozpakowaniu pliku ZIP uzyskamy dostp do plikw JAR. Naley zapisa
w stacji roboczej plik guava (bez czonu gwt). Trzeba utworzy w rodowisku Eclipse odniesienie
do tego pliku; w tym celu naley klikn nazw projektu prawym przyciskiem myszy, wybra
opcj Build Path, nastpnie Configure Build Path i otworzy zakadk Libraries. Nastpnie trzeba klikn opcj Add External JARs, wyszuka plik JAR, wybra go i klikn przycisk Open.
Pozostaje jeszcze ponowne skompilowanie projektu NfcDemo, trzeba wic klikn jego nazw prawym przyciskiem myszy i wybra opcj Build Project.

Odnoniki
Znajdziemy tu cza do materiaw pomocnych w zrozumieniu koncepcji zawartych w tym
rozdziale:
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu list projektw utworzonych
specjalnie na potrzeby niniejszej ksiki. Projekty przeznaczone dla tego rozdziau
zostay umieszczone w katalogu ProAndroid3_R26_Czujniki. Kady z zawartych
w nim projektw znajduje si w osobnym katalogu. W katalogu umiecilimy rwnie
plik Czytaj.TXT, stanowicy dokadn instrukcj importowania projektw
do rodowiska Eclipse.

Rozdzia 26 Czujniki

951

http://en.wikipedia.org/wiki/Lux informacje o jednostce natenia wiata luksie.


http://android-developers.blogspot.com/2010/09/one-screen-turn-deserves-another.html
wpis dotyczcy zagadnienia obracania ekranu i poprawnego wywietlania jego
zawartoci.
www.ngdc.noaa.gov/geomag/faqgeom.shtml znajdziemy tu informacje
o geomagnetyzmie pochodzce od agencji NOOA.
www.youtube.com/watch?v=C7JQ7Rpwn2k prezentacja Google TechTalk autorstwa
Davida Sachsa, dotyczca akcelerometrw, yroskopw i kompasw w odniesieniu
do Androida.
http://stackoverflow.com/questions/1586658/combine-gyroscope-and-accelerometer-data
przyjemny artyku dotyczcy czenia odczytw pochodzcych z yroskopw
i akcelerometrw w celu wykorzystania tych danych w aplikacjach.
www.nfc-forum.org/specs oficjalna strona specyfikacji technologii NFC.
www.slideshare.net/tdelazzari/architecture-and-development-of-nfc-applications
bardzo szczegowa prezentacja autorstwa Thomasa de Lazzariego dotyczca
technologii NFC.

Podsumowanie
W niniejszym rozdziale przyjrzelimy si gwnej architekturze czujnikw w Androidzie, a take
funkcji komunikacji bliskiego pola NFC. Zademonstrowalimy mechanizm odczytywania
danych generowanych przez czujniki oraz sposoby ich przetwarzania. Teraz Czytelnik powinien
by w stanie tworzy wietne aplikacje wsppracujce z nowoczesnymi urzdzeniami w otaczajcym wiecie.

952 Android 3. Tworzenie aplikacji

R OZDZIA

27
Analiza interfejsu kontaktw

W rozdziale 4., dotyczcym dostawcw treci, wymienilimy zalety wynikajce z eksponowania danych za pomoc abstrakcji dostawcy treci. Udowodnilimy rwnie,
e takie wyodrbnione dane s dostpne w postaci zbioru adresw URL, ktre inne
obiekty mog odczytywa, wysya do nich zapytania, a take aktualizowa oraz wstawia lub usuwa w ich wntrzu wasne informacje. Wspomniane adresy URL oraz
ich kursory stanowi podstaw interfejsu API dostawcy treci.
Jednym z takich interfejsw API dostawcw treci jest interfejs kontaktw sucy
do pracy z danymi kontaktowymi. W Androidzie kontakty s przechowywane w bazie
danych i eksponujemy je za pomoc dostawcy treci, ktrego uprawnienie rozpoczyna si od segmentu:
content://com.android.contacts

Dokumentacja zestawu Android SDK wymienia wiele rnych kontraktw zawieranych przez tego dostawc kontaktw za pomoc rnorodnych interfejsw
i klas Java, ktre s przechowywane w pakiecie:
android.provider.ContactsContract

W czasie tworzenia aplikacji natrafimy na wiele klas, pomocnych w wysyaniu zapyta, odczytywaniu, aktualizowaniu oraz wstawianiu kontaktw do lub z bazy
kontaktw, ktrych nadrzdnym kontekstem jest ContactsContract. Podstawowa dokumentacja omawiajca zastosowanie interfejsu API kontaktw jest dostpna na stronie systemu Android:
http://developer.android.com/resources/articles/contacts.html
Gwny punkt wejciowy interfejsu, czyli ContactsContract, nosi waciw nazw,
poniewa klasa ta definiuje kontrakt pomidzy klientami kontaktw a dostawc
i zabezpieczeniem bazy kontaktw.
W tym rozdziale omwimy pojcie kontraktu do szczegowo, nie wymienimy
jednak kadego najdrobniejszego detalu. Interfejs kontaktw jest bardzo rozbudowany, a jego korzenie sigaj naprawd daleko. Gdy jednak powicimy mu uwag,
po kilku tygodniach zauwaymy, e jego zasadnicza struktura wcale nie jest taka
skomplikowana. Wanie na tej strukturze chcielibymy si skupi najbardziej
i wyjani jej podstawowe mechanizmy, co umoliwi Czytelnikowi jej zrozumienie w czasie potrzebnym na przeczytanie rozdziau.

954 Android 3. Tworzenie aplikacji

Koncepcja konta
Wszystkie kontakty w systemie Android dziaaj w kontekcie konta. Czym jest konto? Jeli na
przykad korzystamy ze skrzynki pocztowej umieszczonej na serwerze Google, to znaczy, e
posiadamy konto Google. Jeeli umiecilimy swoje dane w serwisie Facebook i jestemy jego
uytkownikami, stalimy si posiadaczami konta Facebook.
Nawet jeli korzystamy tylko z poczty e-mail na serwerze Google, te same nazwa uytkownika
i haso daj nam dostp do pozostaych usug tej firmy, zatem nasze konto pocztowe nie jest
ograniczone wycznie do skrzynki pocztowej. Jednak niektre rodzaje kont s ograniczone do
jednego rodzaju usugi, czego przykadem jest konto pocztowe typu POP (ang. Post Office Protocol
protok wzw pocztowych). W przypadku urzdzenia mobilnego moemy zarejestrowa
wiele rnych usug opartych na korzystaniu z konta.
Cz z takich kont, na przykad konta Google, Facebook lub firmowe konto Microsoft Exchange,
moemy ustanowi z poziomu widoku Konta i synchronizacja, dostpnego w aplikacji Ustawienia. Wicej informacji dotyczcych kont znajdziemy w instrukcji uytkownika systemu
Android. W podrozdziale Odnoniki zamiecilimy do niej adres URL.

Szybki przegld ekranw zwizanych z kontami


Aby uatwi zrozumienie natury kontaktw, przejrzyjmy najpierw kilka ekranw z nimi zwizanych, ktre znajdziemy w emulatorze. Rozpocznijmy od ekranu aplikacji Ustawienia, pokazanego na rysunku 27.1.

Rysunek 27.1. Wywoywanie widoku ustawie Konta i synchronizacja

Po zaznaczeniu elementu menu Konta i synchronizacja zostanie wywietlony ekran Ustawienia


kont i synchronizacji, ktry widzimy na rysunku 27.2. Znajdziemy tutaj, obok kilku opcji zwizanych z kontami, rwnie list kont powizanych z urzdzeniem.

Rozdzia 27 Analiza interfejsu kontaktw

955

Rysunek 27.2. Ustawienia kont i synchronizacji

Na rysunku 27.2 interesuje nas gwnie lista dostpnych kont. W celach wiczeniowych kliknijmy przycisk Dodaj konto, dziki czemu ujrzymy list dostpnych kont, ktre moemy skonfigurowa lub doda (rysunek 27.3).

Rysunek 27.3. Lista kont, ktre moemy skonfigurowa

Lista ta bdzie miaa rn zawarto w zalenoci od rodzaju urzdzenia oraz dostpnych elementw. Na rysunku 27.3 zaprezentowalimy list dostpnych kont dla wersji 2.3 Androida
zainstalowanej na emulatorze, gdzie docelowy jest 9. poziom interfejsw Google API. Jeeli pobrano sam podstawow wersj zestawu SDK, nie bdzie mona wybra interfejsw Google API

956 Android 3. Tworzenie aplikacji


dla emulatora, zatem nie bdzie mona rwnie skonfigurowa konta Google, ktre znajduje
si na licie widocznej na rysunku 27.3. Oznacza to rwnie, e lista dostpnych kont zaley
od wersji systemu Android, producenta urzdzenia oraz operatora sieci lub dostawcy usug.
Ponadto, w zalenoci od dostawcy, liczba kont i rodzaje pl wymaganych do ich konfiguracji
mog si rni. Jeli na przykad wybierzemy w emulatorze konto Google, otrzymamy moliwo utworzenia nowego konta lub zalogowania si do ju istniejcego (rysunek 27.4).

Rysunek 27.4. Dodawanie konta Google

Jeli klikniemy przycisk Utwrz, pojawi si pola wymagane do zaoenia nowego konta Google,
co zostao ukazane na rysunku 27.5.

Rysunek 27.5. Tworzenie konta Google

Rozdzia 27 Analiza interfejsu kontaktw

957

Na rysunku 27.5 widzimy pola wymagane do utworzenia nowego konta Google, jeli uytkownik jeszcze go nie posiada. Jak ju stwierdzilimy, liczba i tre pl mog si rni w zalenoci
od rodzaju konta. Teraz pokaemy, w jaki sposb wprowadzi ustawienia dla ju istniejcego
konta Google. W tym przypadku cay proces konfiguracji ogranicza si do zalogowania si na
swoje konto, tak jak wida na rysunku 27.6.

Rysunek 27.6. Logowanie si na istniejce konto Google

Skoro zademonstrowalimy ju podstawy dotyczce kont oraz sposb ich umieszczania w urzdzeniu, wyjanijmy, dlaczego konta peni tak wan rol dla kontaktw.

Zwizek pomidzy kontami a kontaktami


Kontakty zarzdzane przez uytkownika s powizane z okrelonym kontem. Inaczej mwic,
kade zarejestrowane na urzdzeniu konto moe przechowywa du liczb zwizanych z nim
kontaktw. Konto jest wacicielem zbioru kontaktw albo jest ono nadrzdne w stosunku do
danego kontaktu. Rwnie dobrze konto moe nie zawiera adnego kontaktu.
Konto jest definiowane za pomoc dwch cigw znakw: jego nazwy oraz typu. W przypadku
konta Google mamy do czynienia z nazw uytkownika skrzynki pocztowej Gmail, a typem
konta jest com.google. Oczywicie, typ konta musi by niepowtarzalny w obrbie caego urzdzenia. W zakresie danego typu konta jego nazwa rwnie musi by jedyna w swoim rodzaju.
Typ i nazwa tworz razem konto, a natychmiast po jego utworzeniu moemy zaj si wstawianiem do niego kontaktw.

Wyliczanie kont
Zasadniczo interfejs kontaktw obsuguje kontakty przechowywane wewntrz rnych kont.
Samo tworzenie kont zachodzi poza interfejsem kontaktw, zatem opis moliwoci zwizanych
z pisaniem wasnych dostawcw kont oraz synchronizowania z nimi kontaktw wykracza poza
zakres tego rozdziau. Sam proces konfiguracji kont jest nieistotny dla niniejszego rozdziau. Jeeli
jednak chcemy doda kontakt lub list kontaktw, musimy wiedzie, jakie konta s dostpne

958 Android 3. Tworzenie aplikacji


w urzdzeniu. Moemy zastosowa kod z listingu 27.1 do wywietlenia rodzajw kont oraz ich
wymaganych waciwoci (nazwa i typ konta). Kod z listingu 27.1 generuje nazwy i typy kont
w zalenoci od zmiennego kontekstu.
Listing 27.1. Kod umoliwiajcy wywietlenie listy kont
public void listAccounts(Context ctx)
{
AccountManager am = AccountManager.get(ctx);
Account[] accounts = am.getAccounts();
for(Account ac: accounts)
{
String acname=ac.name;
String actype = ac.type;
Log.d("accountInfo", acname + ":" + actype);
}
}

Oczywicie, aby uruchomi kod z listingu 27.1, w pliku manifecie musi zosta umieszczone
odpowiednie uprawnienie, widoczne na listingu 27.2.
Listing 27.2. Uprawnienie pozwalajce na odczytywanie zawartoci kont
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>

Kod z listingu 27.1 spowoduje wywietlenie mniej wicej nastpujcej informacji:


Adres-e-mail-serwisu-google:com.google

W tym przypadku przyjlimy, e posiadamy skonfigurowane tylko jedno konto (Google). Jeeli
jest ich wicej, wszystkie zostan wywietlone w podobny sposb.
Zanim zajmiemy si bardziej szczegowo kontaktami, zastanwmy si, w jaki sposb uytkownicy tworz kontakty za pomoc aplikacji fabrycznie umieszczonej w systemie Android.

Aplikacja Kontakty
Jeeli producent urzdzenia, na przykad Motorola, lub operator (przykadowo Verizon1) nie
przewidzieli wasnej aplikacji zarzdzajcej kontaktami, z pomoc przychodzi system Android
i jego domylna aplikacja. Znajdziemy j bez trudu na licie aplikacji dostpnych w urzdzeniu,
jej dokumentacja rwnie zostaa zamieszczona w instrukcji obsugi systemu Android.

Wywietlanie kontaktw
Po uruchomieniu aplikacji Kontakty pierwszym z wywietlonych ekranw bdzie lista kontaktw
(rysunek 27.7). Zasadniczo kontaktem s dane dotyczce osoby, ktr znamy w kontekcie
konta, np. Gmail. Jeeli posiadamy wiele kont, lista z rysunku 27.7 zostanie wypeniona pochodzcymi z nich kontaktami. Spogldajc na ten ekran, nie dowiemy si, z ktrym kontem jest
1

Verizon Communications, Inc. amerykaski dostawca usug telekomunikacyjnych


przyp. red.

Rozdzia 27 Analiza interfejsu kontaktw

959

Rysunek 27.7. Wywietlanie zebranych kontaktw

powizany dany kontakt. System postara si nie powiela takich samych kontaktw pochodzcych z rnych kont, chyba e zostanie to jawnie dozwolone. W nastpnym podrozdziale zajmiemy si t heurystyk podobnych kontaktw.
Odnoszc si do sytuacji z rysunku 27.7, zaoylimy, e s dostpne dwa kontakty, ktre s alfabetycznie uszeregowane.

Wywietlanie szczegw kontaktu


Jeeli klikniemy jeden z kontaktw widocznych na ekranie z rysunku 27.7, zostan wywietlone
jego szczegy, widoczne na rysunku 27.8.

Rysunek 27.8. Szczegy kontaktu

960 Android 3. Tworzenie aplikacji


Na rysunku 27.8 zaprezentowalimy rne rodzaje informacji, przechowywane przez kontakt.
Widzimy na nim rwnie, jakie dziaania moe przeprowadzi aplikacja zarzdzajca kontaktami na danym kontakcie w zalenoci od liczby wypenionych w nim pl. W przypadku niektrych pl moemy wykona poczenie telefoniczne i wysa wiadomo tekstow, a inne
pozwalaj na wysanie wiadomoci e-mail lub rozmow przez komunikator internetowy.

Edytowanie szczegw kontaktu


Przyjrzyjmy si teraz, w jaki sposb moemy edytowa (lub utworzy nowy) kontakt zaprezentowany na rysunku 27.8. Dokonujemy tego poprzez wcinicie przycisku Menu i wybranie opcji
Edytuj kontakt lub Nowy kontakt. Zostanie wywietlony ekran zilustrowany na rysunku 27.9.

Rysunek 27.9. Edycja kontaktu

W grnej czci zaprezentowanego na rysunku 27.9 ekranu Edytuj kontakt ujrzymy nazw
konta, w ramach ktrego dany kontakt jest modyfikowany lub tworzony. W przypadku omawianego kontaktu jedynym kontem jest telefon, co oznacza, e nie ma dla niego skonfigurowanego adnego konta serwerowego (np. Google) oraz e mamy do czynienia z kontem lokalnym.
Rzeczywicie, w bazie kontaktw nazwa i typ konta przyjmuj w tym przypadku wartoci null.
Firma Google usilnie zaleca utworzenie przynajmniej jednego konta na jej serwerze, zanim
urzdzenie pracujce pod kontrol systemu Android zostanie uaktywnione, bez wzgldu na to,
czy korzystamy z telefonu, czy tabletu.
Jak jednak wida, moemy tworzy kontakt bez koniecznoci czenia go z okrelonym kontem,
w takich za przypadkach ekran widoczny w momencie tworzenia takiego kontaktu wyglda tak
jak na rysunku 27.9.
Patrzc na rysunek 27.9, zauwaymy, e tu za wskanikiem typu konta (Tylko telefon itd.) znajduje si miejsce na zdjcie powizane z kontaktem, a nastpnie zestaw pl. Rysunek 27.10
prezentuje nastpne pola, widoczne po przewiniciu ekranu.

Rozdzia 27 Analiza interfejsu kontaktw

961

Rysunek 27.10. Wicej pl edycji

Jak wida na rysunku 27.10, istnieje moliwo przypisania do kontaktu rnych rodzajw numerw telefonw i adresw e-mail. Czytelnik zastanawia si pewnie rwnie, czy w kontaktach
moemy umieszcza wasne pola zawierajce niestandardowe dane (widoczne na rysunku
27.10 pola Telefon oraz E-mail stanowi znane, predefiniowane typy pl. By moe kto chciaby
wstawi mniej oczekiwane formaty danych. To wanie mamy na myli, piszc niestandardowe).
Interfejs API kontaktw pozwala na wprowadzenie tego typu pl, co zostao zaprezentowane
na rysunku 27.11, gdzie dodalimy dane adresowe do kontaktu.

Rysunek 27.11. Edytowanie niestandardowych danych kontaktowych

962 Android 3. Tworzenie aplikacji

Umieszczanie zdjcia powizanego z kontaktem


Moemy wprowadzi rwnie zdjcie dotyczce danego kontaktu. Na rysunku 27.12 widzimy
okno ustawie zdjcia, ktre zostanie otwarte po klikniciu ukazanego na rysunku 27.9 pola
zarezerwowanego na fotografi (pierwsza strona szczegowych danych kontaktu).

Rysunek 27.12. Edycja zdjcia kontaktu

Eksportowanie kontaktw
Zakoczmy przegld aplikacji zarzdzajcej kontaktami zapoznaniem si z mechanizmem eksportowania kontaktw na kart SD. Funkcja ta pozwala nam midzy innymi na przegldanie
rodzajw danych przechowywanych w kontakcie oraz sprawdzenie, w jaki sposb s prezentowane w formie tekstowej (rysunek 27.13).

Rysunek 27.13. Eksportowanie kontaktw

Rozdzia 27 Analiza interfejsu kontaktw

963

Po wyeksportowaniu kontaktw na kart SD moemy przejrze jej zawarto za pomoc wtyczki


ADT rodowiska Android. Na rysunku 27.14 widzimy jeden z eksportowanych plikw .vcf
w perspektywie File Explorer rodowiska Eclipse.

Rysunek 27.14. Informacje kontaktowe umieszczone na karcie SD

Moemy skopiowa widoczny na rysunku 27.14 plik .vcf z urzdzenia do stacji roboczej za pomoc ikon widocznych w prawym grnym rogu zakadki File Explorer. Zawarto pliku .vcf dla
dwch kontaktw widocznych na rysunku 27.8 bdzie wygldaa tak jak zaprezentowano na
listingu 27.3.
Listing 27.3. Eksportowane kontakty w formacie VCF
BEGIN:VCARD
VERSION:2.1
N:C1-Nazwisko;C1-Imi;;;
FN:C1-Imi C1-Nazwisko
TEL;TLX:55555
TEL;WORK:66666
EMAIL;HOME:test@dom.com
EMAIL;WORK:test@praca.com
ORG:PracaKomp
TITLE:Prezes
ORG:Inna praca
TITLE:Prezes
URL:www.com
NOTE:Uwaga1
X-AIM:aim
X-MSN:wlive
END:VCARD
BEGIN:VCARD
VERSION:2.1
N:C2-Nazwisko;C2-Imi;;;
FN:C2-Imi C2-Nazwisko
END:VCARD

964 Android 3. Tworzenie aplikacji

Rne typy danych kontaktowych


Za pomoc dotychczas prezentowanych rysunkw pokazalimy, w jaki sposb mona dodawa
rne rodzaje informacji do kontaktu. Na listingu 27.4 zaprezentowalimy list typw danych
zdefiniowanych w interfejsie kontaktw (w nowszych wersjach systemu moe si ona rozrasta;
prezentujemy wersj zgodn z Androidem 2.3).
Listing 27.4. Standardowe typy danych kontaktowych
email
event
groupmembership
im
nickname
note
organization
phone
photo
relation
SipAddress
structuredname
structuredpostal
website

Kady rodzaj danych, na przykad email czy structuredpostal (przechowujcy kod pocztowy),
posiada wasny zestaw pl. Skd moemy wiedzie, jaki ksztat przybieraj te pola? S one
zdefiniowane w pomocniczych klasach, znajdujcych si w pakiecie:
android.provider.ContactsContract.CommonDataKinds

Dokumentacja tego pakietu jest dostpna pod adresem:


http://developer.android.com/reference/android/provider/ContactsContract.
CommonDataKinds.html
Na przykad klasa CommonDataKinds.Email definiuje pola ukazane na listingu 27.5.
Listing 27.5. Rodzaje pl w adresie typu e-mail kontaktu
Adres e-mail
Typ poczty e-mail: type_home, type_work, type_other, type_mobile
Etykieta: do obsugi poczty type_other

Skoro znamy ju podstawowe pojcia i narzdzia wymagane do korzystania z kontaktw i kont,


przejdmy do waciwych szczegw interfejsu kontaktw.

Analiza kontaktw
Jak ju stwierdzilimy, kontakt jest przypisany do konta. Kade konto zawiera wasny zbir
kontaktw. Z kolei na kady kontakt przypada zestaw elementw danych (na przykad adres
e-mail, numer telefonu, imi i nazwisko czy kod pocztowy). Co wicej, Android przedstawia

Rozdzia 27 Analiza interfejsu kontaktw

965

zbiorczy widok nieprzetworzonych kontaktw, ktre umieszcza na licie kontaktw po sprawdzeniu, czy speniaj okrelone kryteria. Uytkownik widzi tak zbiorcz list kontaktw w momencie uruchomienia aplikacji Kontakty (rysunek 27.8).
Sprawdzimy teraz, w jaki sposb dane powizane z kontaktami s przechowywane w rnych
rodzajach tabel. Wyjanienie regu rzdzcych tymi tabelami oraz powizanymi z nimi widokami stanowi klucz do zrozumienia dziaania interfejsu kontaktw.

Badanie treci bazy danych SQLite


Jednym ze sposobw zrozumienia i analizy bazodanowych tablic kontaktw jest pobranie treci
bazy danych z urzdzenia czy emulatora i przejrzenie jej za pomoc eksploratora SQLite.
Aby pobra baz kontaktw, skorzystamy z widocznej na rysunku 27.14 perspektywy File
Explorer, gdzie przejdziemy do nastpujcego katalogu, znajdujcego si w emulatorze:
/data/data/com.android.providers.contacts/databases
W zalenoci od wersji systemu nazwa bazy danych moe si nieznacznie rni, powinna jednak
brzmie contacts.db, contacts2.db lub podobnie.
Teoretycznie wystarczy otworzy baz danych za pomoc odpowiedniego narzdzia. Wykrylimy jednak problem zwizany z jej otwieraniem wikszo narzdzi ulegao zawieszeniu.
Kopot polega na niestandardowych schematach zestawiania danych zdefiniowanych przez
system Android dla takich dziaa jak porwnywanie numerw telefonw.
Najwidoczniej w przypadku bazy danych SQLite niestandardowe schematy zestawiania s kompilowane jako cz dystrybucji silnika SQLite. Jeeli nie posiadamy bibliotek DLL kompilowanych wraz z dystrybucj systemu Android, eksploratory oglnego przeznaczenia nie bd w stanie
poprawnie odczytywa bazy danych. Poniewa narzdzia te wykorzystuj biblioteki DLL systemu Windows do otwierania bazy danych SQLite utworzonej w systemie Android opartym
na rdzeniu Linuksa, ich dziaania kocz si niepowodzeniem. Poza tym wersja silnika SQLite
dla systemu Windows nie posiada schematw zestawiania, ktre s zdefiniowane jako niezbdny
element bazy kontaktw.
Troch si nam jednak poszczcio, gdy w programie SQLite Explorer znalaz si bd umoliwiajcy przegldanie tabel, chocia nie pozwala to na wywietlanie schematu bazy danych.
Moemy mie wicej szczcia z patnymi aplikacjami. Jeeli Czytelnika interesuj inne alternatywy, poniej zamieszczamy odnonik do listy istniejcych eksploratorw baz danych SQLite:
http://www.sqlite.org/cvstrac/wiki?p=ManagementTools
Jeeli Czytelnik jest naprawd dociekliwy, moe dowiedzie si wicej o schematach zestawiania
z naszego artykuu Exploring Contacts db, dostpnego pod adresem http://www.androidbook.com/
item/3582.
Jeli Czytelnikowi nie uda si eksplorowa bazy danych, nie wszystko jednak jest stracone, poniewa w tym rozdziale umiecilimy list wszystkich najwaniejszych tabel. Zaczniemy najpierw od analizy tak zwanych nieprzetworzonych kontaktw.

Nieprzetworzone kontakty
Przypominamy, e kontakty widoczne po uruchomieniu aplikacji Kontakty s nazywane
kontaktami zbiorczymi. Na kady kontakt zbiorczy skada si zbir tak zwanych nieprzetworzonych kontaktw. Kontakt zbiorczy stanowi jedynie widok zestawu podobnych do siebie

966 Android 3. Tworzenie aplikacji


nieprzetworzonych kontaktw. Aby zrozumie koncepcj kontaktw zbiorczych, najpierw musimy przeanalizowa pojcie nieprzetworzonego kontaktu oraz przechowywanych przez niego
danych. Zajmiemy si wic w pierwszej kolejnoci nieprzetworzonymi kontaktami.
Zestaw kontaktw przypisany do konta jest w rzeczywistoci nazywany zestawem nieprzetworzonych kontaktw. Kady nieprzetworzony kontakt definiuje okrelony aspekt osoby znanej
uytkownikowi w kontekcie danego konta. W przeciwiestwie do kontaktu nieprzetworzonego
kontakt zbiorczy moe by dostpny poza granicami danego konta i w konsekwencji jest oglnie wykorzystywany w urzdzeniu.
Relacja pomidzy kontem a jego zbiorem nieprzetworzonych kontaktw jest utrzymywana
w tabeli nieprzetworzonych kontaktw. Listing 27.6 prezentuje struktur tej tabeli w bazie
kontaktw.
Listing 27.6. Definicja tabeli nieprzetworzonych kontaktw
CREATE TABLE raw_contacts
(_id INTEGER PRIMARY KEY AUTOINCREMENT,
is_restricted INTEGER DEFAULT 0,
account_name STRING DEFAULT NULL,
account_type STRING DEFAULT NULL,
sourceid TEXT,
version INTEGER NOT NULL DEFAULT 1,
dirty INTEGER NOT NULL DEFAULT 0,
deleted INTEGER NOT NULL DEFAULT 0,
contact_id INTEGER REFERENCES contacts(_id),
aggregation_mode INTEGER NOT NULL DEFAULT 0,
aggregation_needed INTEGER NOT NULL DEFAULT 1,
custom_ringtone TEXT
send_to_voicemail INTEGER NOT NULL DEFAULT 0,
times_contacted INTEGER NOT NULL DEFAULT 0,
last_time_contacted INTEGER,
starred INTEGER NOT NULL DEFAULT 0,
display_name TEXT,
display_name_alt TEXT,
display_name_source INTEGER NOT NULL DEFAULT 0,
phonetic_name TEXT,
phonetic_name_style TEXT,
sort_key TEXT COLLATE PHONEBOOK,
sort_key_alt TEXT COLLATE PHONEBOOK,
name_verified INTEGER NOT NULL DEFAULT 0,
contact_in_visible_group INTEGER NOT NULL DEFAULT 0,
sync1 TEXT, sync2 TEXT, sync3 TEXT, sync4 TEXT )

Najwaniejsze pola zostay wyrnione pogrubion czcionk. Podobnie jak w przypadku pozostaych tabel systemu Android, znajdziemy tu kolumn _ID, niepowtarzalnie definiujc nieprzetworzony kontakt. Pola account_name oraz account_type wsplnie definiuj konto tego
kontaktu (dokadniej nieprzetworzonego kontaktu). Pole sourceid wskazuje sposb okrelania
nieprzetworzonego kontaktu w nadrzdnym koncie. Zamy na przykad, e chcemy dowiedzie si, w jaki sposb jest zdefiniowany identyfikator nieprzetworzonego kontaktu wewntrz
konta pocztowego Google. W tym przypadku zazwyczaj pole to przechowuje identyfikator poczty
e-mail uytkownika.

Rozdzia 27 Analiza interfejsu kontaktw

967

Kolumna contact_id odnosi si do kontaktu zbiorczego, ktrego czci jest dany nieprzetworzony kontakt. Kontakt zbiorczy wskazuje przynajmniej jeden kontakt (lub wicej),
ktry w istocie prezentuje t sam osob wykryt na rnych kontach.
Pole display_name przechowuje wywietlan nazw kontaktu. Jest to przede wszystkim pole
tylko do odczytu. Jego warto jest ustanawiana przez wyzwalacze na podstawie informacji
umieszczanych w tabeli danych (ktra zostanie omwiona w nastpnym punkcie).
Pola zawierajce czon sync s wykorzystywane przez konto do synchronizowania kontaktw
pomidzy urzdzeniem a kontem serwerowym, na przykad poczt Gmail.
Chocia do analizy tych pl wykorzystywalimy narzdzia obsugujce silnik SQLite, istnieje
wicej sposobw na ich przegldanie. Zalecanym rozwizaniem jest wykorzystanie definicji klas
zadeklarowanych w interfejsie ContactsContract. Aby przebada kolumny nalece do tabeli
nieprzetworzonych kontaktw, powinnimy przejrze dokumentacj klasy ContactsContract.
RawContact.
Rozwizanie to posiada swoje zalety i wady. Istotn zalet jest moliwo zapoznania si z polami, ktre s opublikowane i akceptowane przez zestaw Android SDK. Kolumny bazodanowe
mog by dodawane lub usuwane bez koniecznoci modyfikowania interfejsu publicznego. Zatem
gdybymy chcieli bezporednio manipulowa kolumnami bazodanowymi, moemy, ale nie musimy na nie natrafi. Z kolei korzystajc z publicznych definicji tych kolumn, jestemy zawsze
zabezpieczeni.
Naley jednak wspomnie, e dokumentacja omawianej klasy zawiera mnstwo innych staych
pomieszanych z nazwami kolumn; nawet my w trakcie opracowywania ksiki pogubilimy si
w ich ogromie. Ta olbrzymia liczba definicji klasy daje wraenie zoonoci interfejsu API, podczas gdy w rzeczywistoci 80 procent jego dokumentacji omawia stae tych kolumn oraz umoliwiajce do nich dostp identyfikatory URI.
Gdy w nastpnych podrozdziaach bdziemy wiczy stosowanie interfejsu kontaktw, zaczniemy wykorzystywa stae zdefiniowane w dokumentacji klasy zamiast bezporednich nazw
kolumny. Mimo to uwaamy, e bezporednia eksploracja tabel jest najszybszym sposobem pozwalajcym na zrozumienie interfejsu kontaktw.
Zastanwmy si teraz, w jaki sposb s przechowywane dane zwizane z kontaktem, na przykad adres e-mail lub numer telefonu.

Tabela danych
Jak ju wspomnielimy podczas omawiania definicji tabeli nieprzetworzonych kontaktw, taki
kontakt (w tym rozczarowujcym znaczeniu) stanowi wycznie identyfikator wskazujcy, z jakim kontem jest zwizany. Wikszo informacji stanowicych zawarto kontaktu jest przechowywana nie w tabeli nieprzetworzonych kontaktw, lecz w tabeli danych. Kady element
danych, taki jak adres e-mail lub numer telefonu, posiada wasne pole w tabeli danych. Wszystkie te wiersze zawierajce dane s powizane z nieprzetworzonym kontaktem za pomoc jego
identyfikatora, ktry stanowi jedn z kolumn tabeli danych oraz jest gwnym identyfikatorem
w tabeli nieprzetworzonych kontaktw.
Tabela danych skada si z szesnastu standardowych kolumn, w ktrych moe by przechowywanych szesnacie dowolnych punktw danych danego elementu, na przykad adresu e-mail.
Na listingu 27.7 widzimy struktur tabeli danych.

968 Android 3. Tworzenie aplikacji


Listing 27.7. Definicja tabeli danych kontaktw
CREATE TABLE data
(_id INTEGER PRIMARY KEY AUTOINCREMENT,
package_id INTEGER REFERENCES package(_id),
mimetype_id INTEGER REFERENCES mimetype(_id) NOT NULL,
raw_contact_id INTEGER REFERENCES raw_contacts(_id) NOT NULL,
is_primary INTEGER NOT NULL DEFAULT 0,
is_super_primary INTEGER NOT NULL DEFAULT 0,
data_version INTEGER NOT NULL DEFAULT 0,
data1 TEXT,data2 TEXT,data3 TEXT,data4 TEXT,data5 TEXT,
data6 TEXT,data7 TEXT,data8 TEXT,data9 TEXT,data10 TEXT,
data11 TEXT,data12 TEXT,data13 TEXT,data14 TEXT,data15 TEXT,
data_sync1 TEXT, data_sync2 TEXT, data_sync3 TEXT, data_sync4 TEXT )

Najwaniejsze kolumny tabeli kontaktw zostay wyrnione pogrubion czcionk. Tak jak
moglimy si spodziewa, pole raw_contact_id stanowi odniesienie do nieprzetworzonego
kontaktu, z ktrym jest zwizana dana informacja.
Pole mimetype_id okrela typ MIME danych wejciowych i wskazuje jeden z typw zdefiniowanych na listingu 27.4. Kolumny od data1 do data15 stanowi standardowe tablice przechowujce cigi znakw, w ktrych moemy umieszcza wszelkie niezbdne informacje, zgodne
z danym typem MIME. Take tutaj pola typu sync obsuguj synchronizacj kontaktw. Tabela
zawierajca informacje o typie MIME identyfikatorw zostaa zaprezentowana na listingu 27.8.
Listing 27.8. Definicja tabeli wyszukiwania typw MIME
CREATE TABLE mimetypes
(_id INTEGER PRIMARY KEY AUTOINCREMENT,
mimetype TEXT NOT NULL)

Podobnie jak ma to miejsce w tabeli nieprzetworzonych danych, analiza kolumn tabeli danych
jest moliwa dziki dokumentacji pomocniczej klasy ContactsContract.Data.
Chocia moemy rozpoznawa kolumny za pomoc definicji tej klasy, nie zapoznamy si w ten
sposb z zawartoci pl od data1 do data15. W tym celu trzeba pozna definicje rnorodnych klas umieszczonych w przestrzeni nazw ContactsContract.CommonDataKinds.
Poniej prezentujemy przykady niektrych zawartych w niej klas:
ContactsContract.CommonDataKinds.Email,
ContactsContract.CommonDataKinds.Phone.
W istocie dla kadego typu danych wymienionego na listingu 27.4 istnieje jedna klasa ze wspomnianej przestrzeni nazw. W ostatecznym rozrachunku wszystkie elementy podrzdne klasy
CommonDataKinds wskazuj, ktre ze standardowych pl danych (data1 data15) s wykorzystywane oraz w jakim celu.

Kontakty zbiorcze
Ostatecznie dochodzimy do wniosku, e kontakt i zwizane z nim dane w sposb jednoznaczny
s przechowywane w tabeli nieprzetworzonych kontaktw i tabeli danych. Z drugiej strony
kontakt zbiorczy jest nieco bardziej heurystyczny i ju nie tak bardzo jednoznaczny.

Rozdzia 27 Analiza interfejsu kontaktw

969

Gdy natrafimy na kontakt, ktry jest taki sam dla rnych kont, chcielibymy, eby jego nazwa
pojawiaa si na licie tylko raz. W tym celu Android umieszcza wszystkie podobne kontakty w
widoku przeznaczonym tylko do odczytu. Takie zbiorcze kontakty s przechowywane w tabeli
zwanej kontaktami. Aby zapeni lub modyfikowa tak tabel kontaktw zbiorczych, Android
wykorzystuje wiele wyzwalaczy wobec tabeli nieprzetworzonych kontaktw i tabeli danych.
Zanim przejdziemy do omwienia mechanizmw zbierania kontaktw, spjrzmy najpierw na
definicj tabeli kontaktw (listing 27.9).
Listing 27.9. Definicja tabeli kontaktw zbiorczych
CREATE TABLE contacts
(_id INTEGER PRIMARY KEY AUTOINCREMENT,
name_raw_contact_id INTEGER REFERENCES raw_contacts(_id),
photo_id INTEGER REFERENCES data(_id),
custom_ringtone TEXT,
send_to_voicemail INTEGER NOT NULL DEFAULT 0,
times_contacted INTEGER NOT NULL DEFAULT 0,
last_time_contacted INTEGER,
starred INTEGER NOT NULL DEFAULT 0,
in_visible_group INTEGER NOT NULL DEFAULT 1,
has_phone_number INTEGER NOT NULL DEFAULT 0,
lookup TEXT,
status_update_id INTEGER REFERENCES data(_id),
single_is_restricted INTEGER NOT NULL DEFAULT 0)

Najwaniejsze pola zostay oznaczone pogrubion czcionk. aden klient nie aktualizuje bezporednio tej tabeli. Po dodaniu nieprzetworzonego kontaktu wraz z jego wspistniejcymi
danymi Android sprawdza pozostae nieprzetworzone kontakty pod ktem podobiestwa. Po
wykryciu powtarzajcego si wpisu jego identyfikator stanie si rwnie identyfikatorem nowego
nieprzetworzonego kontaktu. Nie zostanie wprowadzony aden nowy wpis do tabeli kontaktw
zbiorczych. W przypadku braku obecnoci duplikatu zostanie utworzony nowy kontakt zbiorczy,
a jego identyfikator bdzie przypisany rwnie do nowego nieprzetworzonego kontaktu.
Do okrelenia, czy dwa nieprzetworzone kontakty s podobne, Android stosuje nastpujcy
algorytm:
1. Dwa nieprzetworzone kontakty posiadaj takie same nazwy.
2. Wyrazy tworzce nazwy kontaktw s takie same, uoone s jednak w innej
kolejnoci: pierwszy ostatni, pierwszy, ostatni lub ostatni, pierwszy.
3. Rozpoznawane s zdrobnienia imion, na przykad Robercik jest zdrobnieniem
imienia Robert.
4. Jeli jeden z nieprzetworzonych kontaktw zawiera tylko dane o imieniu lub nazwisku,
zostanie wczony mechanizm poszukiwania innych atrybutw, na przykad numeru
telefonu lub adresu e-mail, i jeeli bd one takie same, kontakty zostan ze sob
powizane.
5. Jeli jeden z nieprzetworzonych kontaktw nie bdzie zawiera imienia ani nazwiska,
podobnie jak w punkcie 4., spowoduje to rwnie wczenie procesu wyszukiwania
innych atrybutw.

970 Android 3. Tworzenie aplikacji


Poniewa mamy tu do czynienia z metodami heurystycznymi, niektre kontakty mog zosta
ze sob powizane przypadkowo. W takim wypadku aplikacje klienckie musz zawiera mechanizm rozdzielajcy te kontakty. W czasie przegldania instrukcji obsugi systemu Android
dowiemy si, e domylna aplikacja Kontakty umoliwia rozdzielanie przypadkowo poczonych kontaktw.
Moemy rwnie uniemoliwi agregacj kontaktw poprzez ustanowienie odpowiedniego
trybu agregacji podczas wstawiania nieprzetworzonego kontaktu. Listing 27.10 zawiera spis
dostpnych trybw agregacji.
Listing 27.10. Stae trybu agregacji
AGGREGATION_MODE_DEFAULT
AGGREGATION_MODE_DISABLED
AGGREGATION_MODE_SUSPENDED

Pierwsza opcja jest oczywista; jest to domylny tryb agregacji.


W drugim trybie (DISABLED) nieprzetworzony kontakt nie podlega procesowi agregacji. Nawet jeli kontakt zosta ju doczony do tabeli kontaktw zbiorczych, zostanie z niej usunity
i otrzyma nowy identyfikator.
Po wybraniu trzeciej opcji (SUSPENDED), nawet jeli waciwoci kontaktu ulegn zmianie, co jest
rwnoznaczne z jego odrzuceniem z biecego zbioru kontaktw, bdzie on cigle powizany
z jego zbiorczym odpowiednikiem.
Z powyszego akapitu wynika, e kontakt zbiorczy jest niestabilny. Zamy, e posiadamy
unikatowy, nieprzetworzony kontakt, w ktrym umieszczone s imi i nazwisko. Jeli dane
te nie dubluj si z danymi adnego innego nieprzetworzonego kontaktu, zostanie przydzielony
do oddzielnego kontaktu zbiorczego. Identyfikator takiego zbiorczego kontaktu bdzie przechowywany w tabeli nieprzetworzonego kontaktu wraz z jego danymi.
Zamy, e zmienimy nazwisko w tym nieprzetworzonym kontakcie w taki sposb, e zduplikuje on zestaw innych powizanych ze sob kontaktw. W takim przypadku nasz zmodyfikowany, nieprzetworzony kontakt zostanie odczony od pierwotnego kontaktu zbiorczego i przeniesiony do innego obiektu tego typu. Identyfikator pierwotnego kontaktu zbiorczego zostanie
natomiast cakowicie porzucony i nie bdzie ju wicej pasowa do adnego kontaktu, poniewa
w rzeczywistoci bdzie to sam identyfikator bez adnych dodatkowych danych.
Zatem kontakt zbiorczy jest niestabilny. Przechowywanie przez duszy czas identyfikatora
takiego zbiorczego kontaktu nie ma wielkiego sensu.
Android oferuje pewne wyjcie z tej kopotliwej sytuacji, mianowicie pozwala na stosowanie pola
lookup w tabelach kontaktw zbiorczych.
Takie pole wyszukiwania pozwala na agregacj (konkatenacj) konta z niepowtarzalnym identyfikatorem kontaktu w przypadku kadego nieprzetworzonego kontaktu. Informacja ta jest jeszcze
bardziej kodyfikowana, dziki czemu mona j wysa w postaci adresu URL w celu odczytania
identyfikatora najnowszego doczonego kontaktu. Android wykorzystuje klucz wyszukiwania
i znajduje wszelkie identyfikatory nieprzetworzonych kontaktw, ktre s przechowywane dla
tego klucza. Nastpnie za pomoc algorytmu najlepszego dopasowania okrela najodpowiedniejszy (lub by moe nowy) identyfikator kontaktu zbiorczego.
Skoro jawnie analizujemy baz kontaktw, przyjrzyjmy si dwm bazodanowym widokom,
ktre mog nam si przyda.

Rozdzia 27 Analiza interfejsu kontaktw

971

view_contacts
Pierwszym ze wspomnianych widokw jest view_contacts. Chocia mamy do dyspozycji tabel przechowujc zbiorcze kontakty (tabela kontaktw), interfejs API nie pozwala na uzyskanie bezporedniego dostpu do tej tabeli. Zamiast tego do przegldania kontaktw zbiorczych
suy wanie widok view_contacts. Gdy wysyamy zapytanie oparte na identyfikatorze URI
ContactsContract.Contacts.CONTENT_URI, otrzymamy kolumny, ktre bazuj na widoku
view_contacts. Definicja tego widoku zostaa umieszczona na listingu 27.11.
Listing 27.11. Widok umoliwiajcy odczytywanie kontaktw zbiorczych
CREATE VIEW view_contacts AS
SELECT contacts._id AS _id,
contacts.custom_ringtone AS custom_ringtone,
name_raw_contact.display_name_source AS display_name_source,
name_raw_contact.display_name AS display_name,
name_raw_contact.display_name_alt AS display_name_alt,
name_raw_contact.phonetic_name AS phonetic_name,
name_raw_contact.phonetic_name_style AS phonetic_name_style,
name_raw_contact.sort_key AS sort_key,
name_raw_contact.sort_key_alt AS sort_key_alt,
name_raw_contact.contact_in_visible_group AS in_visible_group,
has_phone_number,
lookup,
photo_id,
contacts.last_time_contacted AS last_time_contacted,
contacts.send_to_voicemail AS send_to_voicemail,
contacts.starred AS starred,
contacts.times_contacted AS times_contacted, status_update_id
FROM contacts JOIN raw_contacts AS name_raw_contact
ON(name_raw_contact_id=name_raw_contact._id)

Zwrmy uwag, e widok ten czy tabel kontaktw z tabel nieprzetworzonych kontaktw,
a wsplnym mianownikiem jest tu identyfikator zbiorczego kontaktu.

contact_entities_view
Kolejny przydatny widok czy tabel nieprzetworzonych kontaktw z tabel danych. Dziki
niemu moemy odczytywa jednoczenie wszystkie elementy danych okrelonego, nieprzetworzonego kontaktu, a nawet dane bdce czci wielu nieprzetworzonych kontaktw skadajcych si na jeden kontakt zbiorczy. Na listingu 27.12 widzimy definicj widoku tych encji.
Listing 27.12. Widok encji kontaktw
CREATE VIEW contact_entities_view AS
SELECT raw_contacts.account_name AS account_name,
raw_contacts.account_type AS account_type,
raw_contacts.sourceid AS sourceid,
raw_contacts.version AS version,
raw_contacts.dirty AS dirty,

972 Android 3. Tworzenie aplikacji


raw_contacts.deleted AS deleted,
raw_contacts.name_verified AS name_verified,
package AS res_package,
contact_id,
raw_contacts.sync1 AS sync1,
raw_contacts.sync2 AS sync2,
raw_contacts.sync3 AS sync3,
raw_contacts.sync4 AS sync4,
mimetype, data1, data2, data3, data4, data5, data6, data7, data8,
data9, data10, data11, data12, data13, data14, data15,
data_sync1, data_sync2, data_sync3, data_sync4,
raw_contacts._id AS _id,
is_primary, is_super_primary,
data_version,
data._id AS data_id,
raw_contacts.starred AS starred,
raw_contacts.is_restricted AS is_restricted,
groups.sourceid AS group_sourceid
FROM raw_contacts LEFT OUTER JOIN data
ON (data.raw_contact_id=raw_contacts._id)
LEFT OUTER JOIN packages
ON (data.package_id=packages._id)
LEFT OUTER JOIN mimetypes
ON (data.mimetype_id=mimetypes._id)
LEFT OUTER JOIN groups
ON (mimetypes.mimetype='vnd.android.cursor.item/group_membership'
AND groups._id=data.data1)

Identyfikatory URI wymagane do uzyskania dostpu do tego widoku znajdziemy w klasie


ContactsContract.RawContacts.RawContactsEntity.

Praca z interfejsem kontaktw


Do tej pory omawialimy podstawowy mechanizm dziaania interfejsu kontaktw poprzez analizowanie jego tabel i widokw. Korzystajc ze zdobytej wiedzy, utworzymy teraz kilka przykadowych programw. Chocia moemy bez problemu posikowa si kodami zawartymi na listingach, na kocu rozdziau umiecilimy odnonik do plikw zawierajcych gotowe projekty.

Eksploracja kont
Rozpoczniemy wiczenia od napisania programu wywietlajcego list dostpnych kont.
Do tego zadania bd nam potrzebne nastpujce pliki:
TestContactsDriverActivity.java gwna aktywno sterujca, o ktrej bdziemy
wspomina kilkakrotnie w dalszej czci rozdziau. Aktywno ta zawiera zestaw
elementw menu przywoujcych poszczeglne przykady.
DebugActivity.java podstawowa klasa aktywnoci sterujcej, ukrywajca kilka
szczegw implementacji, ktrych znajomo nie jest wymagana do zrozumienia
koncepcji interfejsu kontaktw.

Rozdzia 27 Analiza interfejsu kontaktw

973

debug_activity_layout.xml plik ukadu graficznego wymagany przez aktywno


debugujc, przechowywany w podkatalogu /res/layout.
AccountFunctionTester.java klasa Java, ktra po klikniciu elementu menu wywietla
(poprzez aktywno sterujc) list dostpnych kont w emulatorze lub urzdzeniu.
BaseTester.java bazowa klasa aplikacji AccountsFunctionTester, ukrywajca
szczegy koordynacji pomidzy gwn aktywnoci sterujc a pozostaymi funkcjami
testowymi (kady z prezentowanych przykadw zosta zaimplementowany w postaci
oddzielnej funkcji testowej, dziki czemu kod odzwierciedlajcy kad z omawianych
koncepcji zosta umieszczony w osobnym pliku).
IReportBack.java interfejs implementowany przez klas DebugActivity, przekazywany
klasie BaseTester. Interfejs ten pozwala dziedziczonym funkcjom testowym na
wywietlanie raportw lub komunikatw debuggera na ekranie za pomoc aktywnoci
DebugActivity.
main_menu.xml plik menu obsugujcy wszystkie demonstrowane przez nas przykady.
AndroidManifest.xml niezbdny plik manifest.

Zaprezentujemy teraz po kolei kady z wymienionych plikw. Rozpoczniemy od pliku menu.

Plik menu
Plik menu z listingu 27.13 musi nosi nazw main_menu.xml oraz zosta umieszczony w podkatalogu res/menu naszego projektu.
Listing 27.13. Plik gwnego menu naszego projektu
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa korzysta z domylnej kategorii. -->


<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/menu_show_accounts"
android:title="Konta" />
<item android:id="@+id/menu_da_clear"
android:title="wyczy" />
</group>
</menu>

Na tym etapie umieszczamy w pliku tylko dwa elementy menu. W trakcie tworzenia dalszych
przykadw bdziemy wstawia do niego kolejne obiekty. Pierwszy element menu pozwala na
wywietlanie listy dostpnych kont, drug opcj jest natomiast przydatna funkcjonalno oglnego przeznaczenia, suca do usuwania komunikatw debugowania lub informacji pochodzcych z testowej aktywnoci sterujcej.

Pliki zwizane z funkcjami testujcymi koncepcje kont


Po umieszczeniu pliku menu we waciwym miejscu przyjrzyjmy si plikom zwizanym z implementacj kodu, ktre bd wywoywane w odpowiedzi na kliknicie elementu menu Konta
z listingu 27.13.
IReportBack.java
Pierwszym z tego typu plikw jest IReportBack.java, zaprezentowany na listingu 27.14.

974 Android 3. Tworzenie aplikacji


Listing 27.14. IReportBack.java
//IReportBack.java
public interface IReportBack
{
public void reportBack(String tag, String message);
public void reportTransient(String tag, String message);
}

Interfejs ten jest kontraktem dla dziedziczonych klientw, umoliwiajcym im wysyanie komunikatw informacyjnych oraz debugowania. Miejsce i sposb wywietlania tych komunikatw
nie nale do zada klientw.
BaseTester.java
Wszystkie funkcje testowe bd posiaday dostp do interfejsu IReportBack.java, dziki czemu
bd mogy generowa komunikaty po ich wywoaniu za pomoc elementu menu. Gwarantuje
nam to bazowa klasa dla wszystkich prezentowanych funkcji, zwana BaseTester. Jej kod
rdowy zosta zademonstrowany na listingu 27.15.
Listing 27.15. Kod rdowy klasy BaseTester
public class BaseTester
{
protected IReportBack mReportTo;
protected Context mContext;
public BaseTester(Context ctx, IReportBack target)
{
mReportTo = target;
mContext = ctx;
}
}

Klasa BaseTester przechowuje interfejs IReportBack oraz odniesienie do kontekstu (zazwyczaj jest nim nadrzdna klasa sterujca). Te dwie zmienne s wykorzystywane przez pochodne
funkcje testowe.
AccountsFunctionTester.java
Zaprezentujemy teraz pierwsz z omawianych funkcji testowych,
ktrej kod umieszczono na listingu 27.16.

AccountsFunctionTester,

Listing 27.16. Klasa AccountsFunctionTester


public class AccountsFunctionTester extends BaseTester
{
private static String tag = "tc>";
public AccountsFunctionTester(Context ctx, IReportBack target)
{
super(ctx, target);
}
public void testAccounts()
{

Rozdzia 27 Analiza interfejsu kontaktw

975

AccountManager am = AccountManager.get(this.mContext);
Account[] accounts = am.getAccounts();
for(Account ac: accounts)
{
String acname=ac.name;
String actype = ac.type;
this.mReportTo.reportBack(tag,acname + ":" + actype);
}
}
}

Kod widoczny na listingu 27.16 jest raczej nieskomplikowany. Na pocztku rozdziau omwilimy zagadnienie kont oraz mechanizm wywietlania ich w postaci listy. Kod z listingu 27.16
pobiera jedynie nazw oraz typ kadego konta, a nastpnie wywouje interfejs raportujcy,
dziki ktremu zostan wywietlone wyniki. Dopki istnieje aktywno sterujca, ktra moe
wywoa metod testAccounts(), powyszy kod moe przekazywa nazw i typ konta. Zastanwmy si teraz nad klasami zwizanymi z aktywnoci sterujc.

Klasy aktywnoci sterujcej


Zajmiemy si najpierw podstawow klas aktywnoci sterujcej. Aktywno ta wykonuje nastpujce zadania:
Zapewnienie widoku tekstowego, w ktrym bd wywietlane komunikaty. W tym
celu bdzie wykorzystywany ukad graficzny debug_activity_layout.
Wprowadzenie menu umoliwiajcego wywoywanie poszczeglnych funkcji testowych.
Aktywno sterujca bdzie przyjmowaa wartoci identyfikatora zasobu menu,
otrzymywanego (poprzez konstruktor) z pochodnych klas. Zakadamy nastpnie,
e istnieje predefiniowany element menu menu_da_clear, czyszczcy zdefiniowany
w pliku ukadu graficznego widok tekstowy. Ta klasa bazowa wywietla rwnie
nazw zaznaczonego elementu menu w polu tekstowym ukadu graficznego debuggera.
Skoro ju znamy przeznaczenie tej aktywnoci, moemy przyjrze si plikowi DebugActivity.java,
zaprezentowanemu na listingu 27.17.
DebugActivity.java
Listing 27.17. Definicja klasy DebugActivity
public abstract class DebugActivity extends Activity
implements IReportBack
{

//Najpierw speniamy wymagania pochodnych klas


protected abstract boolean onMenuItemSelected(MenuItem item);

//Zmienne prywatne, konfigurowane za pomoc konstruktora


private static String tag=null;
private int menuId = 0;
public DebugActivity(int inMenuId, String inTag)
{
tag = inTag;
menuId = inMenuId;
}

976 Android 3. Tworzenie aplikacji

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.debug_activity_layout);
}
@Override
public boolean onCreateOptionsMenu(Menu menu){
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(menuId, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
appendMenuItemText(item);
if (item.getItemId() == R.id.menu_da_clear){
this.emptyText();
return true;
}
return onMenuItemSelected(item);
}
private TextView getTextView(){
return (TextView)this.findViewById(R.id.text1);
}
protected void appendMenuItemText(MenuItem menuItem){
String title = menuItem.getTitle().toString();
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + title);
}
protected void emptyText(){
TextView tv = getTextView();
tv.setText("");
}
private void appendText(String s){
TextView tv = getTextView();
tv.setText(tv.getText() + "\n" + s);
Log.d(tag,s);
}
public void reportBack(String tag, String message)
{
this.appendText(tag + ":" + message);
Log.d(tag,message);
}
public void reportTransient(String tag, String message)
{
String s = tag + ":" + message;
Toast mToast = Toast.makeText(this, s, Toast.LENGTH_SHORT);
mToast.show();
reportBack(tag,message);
Log.d(tag,message);
}

Oprcz metod umoliwiajcych wywietlanie komunikatw w widoku tekstowym mamy tu do


czynienia z metod reportTransient() interfejsu IReportBack, ktry suy do wywietlania
informacji za pomoc kontrolki Toast.

Rozdzia 27 Analiza interfejsu kontaktw

977

debug_layout_activity.xml
Widoczny na listingu 27.18 plik debug_layout_activity.xml musi zosta umieszczony w podkatalogu /res/layout.
Listing 27.18. Plik ukadu graficznego debuggera debug_layout_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Tutaj pojawia si tekst debugowania"
/>
</LinearLayout>

TestContactsDriverActivity.java
Listing 27.19 prezentuje gwn aktywno sterujc, ktra koordynuje dziaanie poszczeglnych elementw menu z wywoywaniem odpowiednich metod, umieszczonych w funkcjach
testowych.
Listing 27.19. Gwna aktywno sterujca
public class TestContactsDriverActivity
extends DebugActivity
implements IReportBack
{
public static final String tag="Test kontaktw";
AccountsFunctionTester accountsFunctionTester = null;
public TestContactsDriverActivity()
{
super(R.menu.main_menu,tag);
accountsFunctionTester = new AccountsFunctionTester(this,this);
}
protected boolean onMenuItemSelected(MenuItem item)
{
Log.d(tag,item.getTitle().toString());
if (item.getItemId() == R.id.menu_show_accounts)
{
accountsFunctionTester.testAccounts();
return true;
}
return true;
}
}

978 Android 3. Tworzenie aplikacji


Kod tej aktywnoci sterujcej jest czytelny i przejrzysty, poniewa umiecilimy wikszo jej
funkcji w klasie bazowej.
Pierwsz spraw, ktr naley zauway na listingu 27.19, jest sposb przekazywania zasobu menu
zdefiniowanego na listingu 27.13 (main_menu.xml) do bazowej aktywnoci debuggera. Aktywno ta czy nastpnie menu w cao.
Drugim mechanizmem wartym zaobserwowania jest sposb wykorzystywania funkcji testowych przez aktywno sterujc. W kodzie z listingu 27.19 zademonstrowalimy jedynie funkcj
testow dla kont. Wraz z postpami prac nad projektem bdziemy dodawa kolejne funkcje testowe. Sposb ich stosowania jest zawsze taki sam.
Plik manifest
Na listingu 27.20 zamiecilimy zawarto pliku manifestu i tym samym koczymy prezentowanie wszystkich niezbdnych plikw.
Listing 27.20. Plik manifest przykadowego programu
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.contacts"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon"
android:label="Test kontaktw">
<activity android:name=".TestContactsDriverActivity"
android:label="Test kontaktw">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="5" />
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
</manifest>

Uruchomienie programu
Listing 27.21 zawiera spis plikw wymaganych do skompilowania i uruchomienia naszego prostego przykadu.
Listing 27.21. Kompletny spis plikw tworzcych pierwsz aplikacj testow
IReportBack.java
BaseTester.java
AccountsFunctionTester.java
DebugActivity.java
TestContactsDriverActivity.java
/res/menu/main_menu.xml
/res/layout/debug_layout_activity.xml
AndroidManifest.xml

Rozdzia 27 Analiza interfejsu kontaktw

979

Jeli po skompilowaniu i uruchomieniu kodu zawartego w tych plikach klikniemy element


menu znajdujcy si w gwnej aktywnoci sterujcej, zobaczymy ekran zilustrowany na rysunku 27.15.

Rysunek 27.15. Gwna aktywno sterujca z doczonym menu

Menu pokazane na rysunku 27.15 zawiera dwie opcje. Opcja wyczy stanowi standardowy
element menu, zdefiniowany w bazowej klasie aktywnoci debugowania, za pomoc ktrego
wyzerujemy zawarto widoku tekstowego. Opcja Konta spowoduje wywietlenie listy kont
dostpnych w urzdzeniu. Przekonajmy si, co si stanie po jego klikniciu. Pojawi si ekran
widoczny na rysunku 27.16.

Rysunek 27.16. Gwna aktywno sterujca, prezentujca spis dostpnych kontaktw

980 Android 3. Tworzenie aplikacji


Testowana przez nas wersja emulatora zawieraa tylko jedn konfiguracj konta firmy Google,
dlatego jego nazwa zostaa wywietlona przez nasz aplikacj.

Badanie kontaktw zbiorczych


W nastpnym przykadowym programie zaprezentujemy rozwizanie pozwalajce na badanie
kontaktw zbiorczych. Zademonstrujemy w nim trzy kwestie dotyczce tego rodzaju kontaktw:
Pokaemy, jak przejrze wszystkie wypenione pola poprzez wykorzystanie
identyfikatora URI, dziki ktremu mona odczyta zawarto kontaktw
zbiorczych.
Wywietlimy spis wszystkich zbiorczych kontaktw.
Przedstawimy wszystkie pola przekazywane przez kursor na podstawie
identyfikatora URI wyszukiwania.
W celu odczytywania kontaktw musimy umieci nastpujce uprawnienie w pliku manifecie
(jego kod pokazano na listingu 27.20):
android.permission.READ_CONTACTS

Do przetestowania tego przykadu wymagane te bd nastpujce nowe pliki (obok plikw


utworzonych wczeniej):
Utils.java,
URIFunctionTester.java,
AggregatedContactFunctionTester.java,
AggregatedContact.java.
Kady z tych plikw zostanie omwiony w dalszej czci rozdziau.
Musimy take zmodyfikowa nastpujce pliki, bdce czci poprzedniego przykadu:
main_menu.xml,
TestContactsDriverActivity.java.
Nieco dalej w tym podrozdziale wskaemy, jakie zmiany naley wprowadzi do wymienionych
plikw.
Poniewa w omawianej przez nas funkcji pojawiaj si dostawcy treci, identyfikatory URI oraz
kursory, zebralimy kilka metod uytkowych w pliku Utils.java, widocznym na listingu 27.22.
Listing 27.22. Funkcje uytkowe pozwalajce na prac z kursorami
public class Utils
{
public static String getColumnValue(Cursor cc, String cname)
{
int i = cc.getColumnIndex(cname);
return cc.getString(i);
}
protected static String getCursorColumnNames(Cursor c)
{
int count = c.getColumnCount();
StringBuffer cnamesBuffer = new StringBuffer();

Rozdzia 27 Analiza interfejsu kontaktw

981

for (int i=0;i<count;i++)


{
String cname = c.getColumnName(i);
cnamesBuffer.append(cname).append(';');
}
return cnamesBuffer.toString();
}
}

Pierwsza funkcja, getColumnValue(), powraca z wartoci kolumny, pobierajc jej nazw z biecego wiersza kursora. Bez wzgldu na podstawowy typ danych kolumny funkcja ta przekazuje
t warto w postaci cigu znakw.
Druga funkcja jest bardzo przydatna. Pobiera ona dowolny kursor i przekazuje osobn list
wszystkich jego kolumn. Jest to uyteczne zwaszcza w przypadku badania nowych identyfikatorw URI pod ktem rodzajw pl, ktre okrelaj. Chocia mona udokumentowa te
kolumny w kodzie Java, wspomniana metoda ich odkrywania w czasie dziaania aplikacji moe
si przyda w pewnych sytuacjach.
Poniewa ten i nastpne przykady wykorzystuj koncepcj wysyania identyfikatora URI i odbierania kursora za pomoc aktywnoci, umiecilimy funkcje realizujce te zadania w bazowej
klasie URIFunctionTester. Na listingu 27.23 zamiecilimy kod rdowy tej klasy, po czym
opisalimy kad dostpn w niej metod.
Listing 27.23. Klasa bazowa, umoliwiajca analizowanie funkcji zwizanych z identyfikatorami URI
public class URIFunctionTester extends BaseTester
{
protected static String tag = "tc>";
public URIFunctionTester(Context ctx, IReportBack target)
{
super(ctx, target);
}
protected Cursor getACursor(String uri,String clause)
{

// Uruchamia kwerend
Activity a = (Activity)this.mContext;
return a.managedQuery(Uri.parse(uri), null, clause, null, null);
}
protected Cursor getACursor(Uri uri,String clause)
{

// Uruchamia kwerend
Activity a = (Activity)this.mContext;
return a.managedQuery(uri, null, clause, null, null);
}
protected void printCursorColumnNames(Cursor c)
{
this.mReportTo.reportBack(tag,Utils.getCursorColumnNames(c));
}
}

982 Android 3. Tworzenie aplikacji


Funkcja getACursor() pobiera identyfikator URI albo w postaci cigu znakw, albo jako obiekt
identyfikatora URI wraz z opart na cigu znakw klauzul where, a nastpnie przekazuje kursor.
W omawianych przykadach czsto wywietlamy nazwy kolumn pochodzcych z otrzymywanego kursora, utworzylimy wic metod printCursorColumnNames(), ktra z kolei wykorzystuje klas Utils do analizowania zawartoci kursora i uzyskiwania nazw jego kolumn.
Kady wiersz przekazywany przez kursor kontaktu bdzie posiada pewn liczb pl. W naszym
przykadzie interesuj nas tylko niektre z nich. Wyrazilimy t koncepcj w kolejnej klasie, nazwanej AggregatedContact, ktra zostaa ukazana na listingu 27.24.
Listing 27.24. Kilka pl pochodzcych z kontaktu zbiorczego
public class AggregatedContact
{
public String id;
public String lookupUri;
public String lookupKey;
public String displayName;
public void fillinFrom(Cursor c)
{
id = Utils.getColumnValue(c,"_ID");
lookupKey = Utils.getColumnValue(c,ContactsContract.Contacts.LOOKUP_KEY);
lookupUri = ContactsContract.Contacts.CONTENT_LOOKUP_URI + "/" + lookupKey;
displayName = Utils.getColumnValue(c,ContactsContract.Contacts.DISPLAY_NAME);
}
}

Kod z listingu 27.24 wcale nie jest skomplikowany. Wykorzystalimy tu kursor do wczytania
interesujcych nas pl. Zaprezentujemy teraz na listingu 27.25 klas AggregatedContactFunction
Tester, ktra pomoe nam wypeni zadania ustalone na pocztku tego podrozdziau.
Listing 27.25. Kod umoliwiajcy testowanie kontaktw zbiorczych
public class AggregatedContactFunctionTester extends URIFunctionTester
{
public AggregatedContactFunctionTester(Context ctx, IReportBack target)
{
super(ctx, target);
}

/*
* Pobiera kursor ze wszystkich kontaktw
* Bez klauzuli where
* Nie stosujmy tego w przypadku duego zbioru
*/
private Cursor getContacts()
{

// Uruchamia kwerend
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
+ " COLLATE LOCALIZED ASC";
Activity a = (Activity)this.mContext;
return a.managedQuery(uri, null, null, null, sortOrder);

Rozdzia 27 Analiza interfejsu kontaktw

/*
* Wykorzystuje powysz metod getContacts
* do utworzenia spisu kolumn zawartych w kursorze
*/
public void listContactCursorFields()
{
Cursor c = null;
try
{
c = getContacts();
int i = c.getColumnCount();
this.mReportTo.reportBack(tag, "Liczba kolumn:" + i);
this.printCursorColumnNames(c);
}
finally
{
if (c!= null) c.close();
}
}

/*
* Przy uyciu kursora wypenionego kontaktami
* zostaj wywietlone nazwy kontaktw
* wraz z ich kluczami wyszukiwania
*/
private void printLookupKeys(Cursor c)
{
for(c.moveToFirst();!c.isAfterLast();c.moveToNext())
{
String name=this.getContactName(c);
String lookupKey = this.getLookupKey(c);
String luri = this.getLookupUri(lookupKey);
this.mReportTo.reportBack(tag, name + ":" + lookupKey);
this.mReportTo.reportBack(tag, name + ":" + luri);
}
}

/*
* Wykorzystuje funkcj getContacts()
* do uzyskania kursora i wywietlenia wszystkich
* nazw kontaktw wraz z kluczami ich wyszukiwania
* Stosuje funkcj printLookupKeys()
*/
public void listContacts()
{
Cursor c = null;
try
{
c = getContacts();
int i = c.getColumnCount();
this.mReportTo.reportBack(tag, "Liczba kolumn:" + i);
this.printLookupKeys(c);

983

984 Android 3. Tworzenie aplikacji


}
finally
{
if (c!= null) c.close();
}
}

/*
* Funkcja uytkowa odczytujca
* klucz wyszukiwania z kursora kontaktu
*/
private String getLookupKey(Cursor cc)
{
int lookupkeyIndex = cc.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
return cc.getString(lookupkeyIndex);
}

/*
* Funkcja uytkowa odczytujca
* wywietlan nazw z kursora kontaktu
*/
private String getContactName(Cursor cc)
{
return Utils.getColumnValue(cc,ContactsContract.Contacts.DISPLAY_NAME);
}

/**
* Konstruuje identyfikator wyszukiwania na podstawie
* identyfikatora URI kontaktw i klucza wyszukiwania
*/
private String getLookupUri(String lookupkey)
{
String luri = ContactsContract.Contacts.CONTENT_LOOKUP_URI + "/" + lookupkey;
return luri;
}

/**
* Wykorzystuje identyfikator URI wyszukiwania
* do odczytania pojedynczego kontaktu zbiorczego
*/
private Cursor getASingleContact(String lookupUri)
{

// Uruchamia kwerend
Activity a = (Activity)this.mContext;
return a.managedQuery(Uri.parse(lookupUri), null, null, null, null);
}

/*
* Funkcja sprawdzajca, czy identyfikator URI stworzony za pomoc identyfikatora
* URI wyszukiwania zwraca kursor zawierajcy inny zestaw kolumn.
* Jak mona byo si spodziewa, zwracany jest podobny kursor
* zawierajcy podobny zbir kolumn.
*/

Rozdzia 27 Analiza interfejsu kontaktw

public void listLookupUriColumns()


{
Cursor c = null;
try
{
c = getContacts();
String firstContactLookupUri = getFirstLookupUri(c);
printLookupUriColumns(firstContactLookupUri);
}
finally
{
if (c!= null) c.close();
}
}
public void printLookupUriColumns(String lookupuri)
{
Cursor c = null;
try
{
c = getASingleContact(lookupuri);
int i = c.getColumnCount();
this.mReportTo.reportBack(tag, "Liczba kolumn:" + i);
int j = c.getCount();
this.mReportTo.reportBack(tag, "Liczba wierszy:" + j);
this.printCursorColumnNames(c);
}
finally
{
if (c!=null)c.close();
}
}

/*
* Pobiera list kontaktw
* Wyszukuje pierwszy kontakt
* Przekazuje warto null, jeli nie znajdzie adnego kontaktu
*/
private String getFirstLookupUri(Cursor c)
{
c.moveToFirst();
if (c.isAfterLast())
{
Log.d(tag,"Brak wierszy, z ktorych mozna pobrac pierwszy kontakt");
return null;
}

//Znaleziono wiersz
String lookupKey = this.getLookupKey(c);
String luri = this.getLookupUri(lookupKey);
return luri;
}

/*
* Pobiera list kontaktw
* Wyszukuje pierwszy kontakt i zwraca go

985

986 Android 3. Tworzenie aplikacji


* w formie obiektu AggregatedContact
*/
protected AggregatedContact getFirstContact()
{
Cursor c=null;
try
{
c = getContacts();
c.moveToFirst();
if (c.isAfterLast())
{
Log.d(tag,"Brak kontaktow");
return null;
}

//Znaleziono kontakt
AggregatedContact firstcontact = new AggregatedContact();
firstcontact.fillinFrom(c);
return firstcontact;
}
finally
{
if (c!=null) c.close();
}
}
}

Gwne funkcje publiczne zostay wyrnione pogrubion czcionk. Przeznaczenie kadej z nich
zostao wyjanione w przypisanym do niej komentarzu. Po utworzeniu tej funkcji testowej dodajmy elementy menu z listingu 27.26 do pliku menu (/res/menu/main_menu.xml).
Listing 27.26. Elementy menu zwizane z funkcj testow kontaktw zbiorczych
<item android:id="@+id/menu_show_contact_cursor"
android:title="kursor kontaktw" />
<item android:id="@+id/menu_show_contacts"
android:title="kontakty" />
<item android:id="@+id/menu_show_single_contact_cursor"
android:title="kursor pojedynczego kontaktu" />

Moemy wstawi je w dowolnym miejscu pliku main_menu.xml, proponujemy jednak umieszczenie ich w pocztkowej czci kodu, dziki czemu nowsze funkcje bd wywietlane na pocztku menu. Po dodaniu opcji menu modyfikujemy aktywno sterujc w taki sposb, eby
przypominaa kod z listingu 27.27.
Listing 27.27. Gwna aktywno sterujca dostosowana do testowania kontaktw zbiorczych
public class TestContactsDriverActivity extends DebugActivity
implements IReportBack
{
public static final String tag="TestContactsDriverActivity ";
AccountsFunctionTester accountsFunctionTester = null;

Rozdzia 27 Analiza interfejsu kontaktw

987

AggregatedContactFunctionTester aggregatedContactFunctionTester = null;


public TestContactsDriverActivity()
{
super(R.menu.main_menu,tag);
accountsFunctionTester = new AccountsFunctionTester(this,this);
aggregatedContactFunctionTester =
new AggregatedContactFunctionTester(this,this);
}
protected boolean onMenuItemSelected(MenuItem item)
{
Log.d(tag,item.getTitle().toString());
if (item.getItemId() == R.id.menu_show_accounts)
{
accountsFunctionTester.testAccounts();
return true;
}
if (item.getItemId() == R.id.menu_show_contact_cursor)
{
aggregatedContactFunctionTester.listContactCursorFields();
return true;
}
if (item.getItemId() == R.id.menu_show_contacts)
{
aggregatedContactFunctionTester.listContacts();
return true;
}
if (item.getItemId() == R.id.menu_show_single_contact_cursor)
{
aggregatedContactFunctionTester.listLookupUriColumns();
return true;
}
return true;
}
}

Zwrmy uwag na trzy publiczne funkcje, ktre s wywoywane w wyniku wcinicia odpowiednich opcji menu:
listContactCursorFields(),
listContacts(),
listLookupUriColumns().
Omwimy dziaanie tych funkcji, opierajc si na kodzie umieszczonym na listingu 27.26.
Funkcja listContactCursorFields odczytuje ca list kontaktw i wywietla w kursorze
nazwy kolumn. Identyfikatorem URI sucym do odczytywania wszystkich kontaktw jest
ContactsContract.Contacts.CONTENT_URI.
W celu odczytania kursora przekazujemy ten identyfikator URI metodzie managedQuery().
Moemy przekaza warto null w trakcie rzutowania kolumn, aby wywietli wszystkie
kolumny. Chocia nie jest to zalecane rozwizanie, w naszym przypadku jest ono logiczne, gdy
chcemy pozna wszystkie kolumny przekazane przez identyfikator URI. Na listingu 27.28
widzimy spis wszystkich kolumn otrzymywanych dziki temu identyfikatorowi.

988 Android 3. Tworzenie aplikacji


Listing 27.28. Kolumny kursora przekazywane przez identyfikator URI dostawcy kontaktw
times_contacted;
contact_status;
custom_ringtone;
has_phone_number;
phonetic_name;
phonetic_name_style;
contact_status_label;
lookup;
contact_status_icon;
last_time_contacted;
display_name;
sort_key_alt;
in_visible_group;
_id;
starred;
sort_key;
display_name_alt;
contact_presence;
display_name_source;
contact_status_res_package;
contact_status_ts;
photo_id;
send_to_voicemail;

Nasz przykadowy program wygeneruje list tych kolumn zarwno w widoku programu, jak
rwnie w oknie LogCat. Skopiowalimy te pola z okna LogCat i sformatowalimy w sposb
widoczny na listingu 27.28.
Podczas pracy z dostawcami treci technika polegajca na korzystaniu z identyfikatora
URI oraz wywietlaniu przekazywanych kolumn moe si okaza bardzo przydatna.

Po zapoznaniu si ze spisem kolumn za pomoc identyfikatora URI treci kontaktw zaznaczmy


kilka z nich i sprawdmy, jakie wiersze s dostpne. Kliknijmy w tym celu opcj menu kontakty,
co spowoduje wywoanie funkcji listContacts(). Korzysta ona z tego samego identyfikatora
URI, tym razem jednak wywietla dla kadego kontaktu nastpujce kolumny:
display name,
lookup key,
lookup uri.
Bierzemy te pola pod uwag, poniewa chcemy zobaczy, jak wygldaj klucz wyszukiwania
oraz identyfikator klucza wyszukiwania w porwnaniu do informacji zawartych w czci teoretycznej tego rozdziau. Interesuje nas zwaszcza mechanizm uruchamiania identyfikatora
URI wyszukiwania oraz typ otrzymywanego kursora. Kliknijmy w tym celu element menu kursor pojedynczego kontaktu. Zostanie wywoana funkcja listLookupUriColumns(). Pobierze
ona pierwszy kontakt z listy kontaktw, a nastpnie wygeneruje identyfikator URI wyszukiwania dla tego kontaktu, po czym z niego skorzysta, a my poznamy wyniki.
Okazuje si, e wspomniana funkcja przekae kursor zawierajcy takie same kolumny jak
widoczne na listingu 27.28 jedyna rnica polega na obecnoci tylko jednego wiersza

Rozdzia 27 Analiza interfejsu kontaktw

989

wskazujcego kontakt, ktrego dotyczy ten klucz wyszukiwania. Zauwamy rwnie, e wprowadzilimy nastpujc definicj identyfikatora URI wyszukiwania:
ContactsContract.Contacts.CONTENT_LOOKUP_URI

Podczas dyskusji na temat identyfikatorw URI wyszukiwania stwierdzilimy, e kady tego typu
obiekt symbolizuje zbir poczonych ze sob, nieprzetworzonych kontaktw. W takim przypadku powinnimy si spodziewa, e otrzymamy zestaw takich samych nieprzetworzonych
kontaktw. Powyszy test (listing 27.28) udowadnia nam jednak, e nie dostajemy kursora
zawierajcego nieprzetworzone kontakty, lecz kursor przechowujcy kontakty zbiorcze.
W efekcie wyszukiwania opartego na identyfikatorze wyszukiwania kontaktu
otrzymujemy kontakt zbiorczy, a nie kontakt nieprzetworzony.

Kolejn istotn i ciekaw cech procesu wyszukiwania kontaktu zbiorczego opartego na identyfikatorze wyszukiwania jest to, e nie zachodzi w sposb liniowy i e nie jest dokadny. Oznacza to, e system nie bdzie poszukiwa trafienia dokadnie odpowiadajcego kluczowi dopasowania. Zamiast tego Android analizuje skadni klucza wyszukiwania pod ktem tworzcych go
nieprzetworzonych kontaktw, nastpnie odnajduje identyfikator kontaktu zbiorczego, ktry
jest zgodny z wikszoci rekordw nieprzetworzonych kontaktw, i przekazuje rekord kontaktu zbiorczego utworzonego w ten sposb.
Jedn z konsekwencji tego mechanizmu jest brak moliwoci publicznego przejcia od klucza
wyszukiwania do nieprzetworzonych kontaktw, ktre stanowi jego skadowe. Jedynym rozwizaniem jest odnalezienie identyfikatora kontaktu zwizanego z kluczem wyszukiwania,
a nastpnie uruchomienie obiektu URI nieprzetworzonego kontaktu wobec identyfikatora
znalezionego kontaktu, dziki czemu zostan odczytane jego nieprzetworzone kontakty.

Badanie nieprzetworzonych kontaktw


W nastpnym przykadowym programie przedstawimy rozwizanie pozwalajce na eksploracj
nieprzetworzonych kontaktw. W tym wiczeniu postaramy si wykona trzy zadania:
Odkry wszystkie przekazywane pola poprzez uruchomienie identyfikatora URI
odczytujcego nieprzetworzone kontakty.
Wywietli wszystkie nieprzetworzone kontakty.
Wygenerowa list nieprzetworzonych kontaktw tworzcych kontakt zbiorczy.
W tym przykadzie bd potrzebne nastpujce nowe pliki:
RawContact.java,
RawContactFunctionTester.java.
Pliki te zostan zaprezentowane w trakcie omawiania szczegw przykadu. Bdziemy musieli
zaktualizowa rwnie nastpujce pliki pochodzce z poprzedniego przykadu:
main_menu.xml,
TestContactsDriverActivity.java.
W dalszej czci podrozdziau zaprezentujemy rwnie zmiany, jakie naley wprowadzi w powyszych plikach.
Plik z listingu 27.29, RawContact.java, pobiera kilka istotnych pl z tabeli nieprzetworzonych
kontaktw.

990 Android 3. Tworzenie aplikacji


Listing 27.29. Plik RawContact.java
public class RawContact
{
public String rawContactId;
public String aggregatedContactId;
public String accountName;
public String accountType;
public String displayName;
public void fillinFrom(Cursor c)
{
rawContactId = Utils.getColumnValue(c,"_ID");
accountName = Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_NAME);
accountType = Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_TYPE);
aggregatedContactId = Utils.getColumnValue(c,
ContactsContract.RawContacts.CONTACT_ID);
displayName = Utils.getColumnValue(c,"display_name");
}
public String toString()
{
return displayName
+ "/" + accountName + ":" + accountType
+ "/" + rawContactId
+ "/" + aggregatedContactId;
}
}

Aby mc przetestowa zawarte w tym przykadzie funkcje, musimy do pliku main_menu.xml


doda widoczne na listingu 27.30 elementy menu.
Listing 27.30. Opcje menu umoliwiajce przetestowanie nieprzetworzonych kontaktw
<item android:id="@+id/menu_show_rc_all"
android:title="wszystkie nieprzetworzone kontakty" />
<item android:id="@+id/menu_show_rc"
android:title="nieprzetworzone kontakty" />
<item android:id="@+id/menu_show_rc_cursor"
android:title="kursor nieprzetworzonych kontaktw" />

Kady z tych elementw menu powoduje wywoanie trzech funkcji umieszczonych w pliku
RawContactFunctionTester.java. Zawarty w tym pliku kod jest widoczny na listingu 27.31.
Listing 27.31. Testowanie nieprzetworzonych kontaktw
public class RawContactsFunctionTester
extends AggregatedContactFunctionTester
{
public RawContactsFunctionTester(Context ctx, IReportBack target)
{
super(ctx, target);
}

Rozdzia 27 Analiza interfejsu kontaktw

public void showAllRawContacts()


{
Cursor c = null;
try
{
c = this.getACursor(getRawContactsUri(), null);
this.printRawContacts(c);
}
finally
{
if (c!=null) c.close();
}
}
public void showRawContactsForFirstAggregatedContact()
{
AggregatedContact ac = getFirstContact();
this.mReportTo.reportBack(tag, ac.displayName + ":" + ac.id);
Cursor c = null;
try
{
c = this.getACursor(getRawContactsUri(), getClause(ac.id));
this.printRawContacts(c);
}
finally
{
if (c!=null) c.close();
}
}
private void printRawContacts(Cursor c)
{
for(c.moveToFirst();!c.isAfterLast();c.moveToNext())
{
RawContact rc = new RawContact();
rc.fillinFrom(c);
this.mReportTo.reportBack(tag, rc.toString());
}
}
public void showRawContactsCursor()
{
AggregatedContact ac = getFirstContact();
this.mReportTo.reportBack(tag, ac.displayName + ":" + ac.id);
Cursor c = null;
try
{
c = this.getACursor(getRawContactsUri(),null);
this.printCursorColumnNames(c);
}
finally
{
if (c!=null) c.close();
}
}

991

992 Android 3. Tworzenie aplikacji


private Uri getRawContactsUri()
{
return ContactsContract.RawContacts.CONTENT_URI;
}
private String getClause(String contactId)
{
return "contact_id = " + contactId;
}
}

Na listingu 27.32 znalaz si zaktualizowany kod aktywnoci sterujcej, przejmujcej od elementw menu proces wywoywania funkcji publicznych pochodzcych z testera funkcji nieprzetworzonych kontaktw.
Listing 27.32. Zaktualizowana aktywno sterujca, pozwalajca na testowanie
nieprzetworzonych kontaktw
public class TestContactsDriverActivity extends DebugActivity
implements IReportBack
{

//........kontynuacja
RawContactsFunctionTester rawContactFunctionTester = null;
public TestContactsDriverActivity()
{

//........kontynuacja
rawContactFunctionTester = new RawContactsFunctionTester(this,this);
}
protected boolean onMenuItemSelected(MenuItem item)
{

//........kontynuacja
if (item.getItemId() == R.id.menu_show_single_contact_cursor)
{
aggregatedContactFunctionTester.listLookupUriColumns();
return true;
}

//pocztek nowych wpisw


if (item.getItemId() == R.id.menu_show_rc_cursor)
{
rawContactFunctionTester.showRawContactsCursor();
return true;
}
if (item.getItemId() == R.id.menu_show_rc_all)
{
rawContactFunctionTester.showAllRawContacts();
return true;
}
if (item.getItemId() == R.id.menu_show_rc)
{
rawContactFunctionTester.showRawContactsForFirstAggregatedContact();
return true;
}

//koniec nowych wpisw

Rozdzia 27 Analiza interfejsu kontaktw

993

return true;
}
}

Na powyszym listingu zaprezentowalimy jedynie nowe wiersze, ktre naley wstawi do aktywnoci sterujcej, gdy jest to jeden z aktualizowanych plikw.
Podobnie jak zrobilimy w przypadku kontaktw zbiorczych, przyjrzyjmy si najpierw naturze
identyfikatorw URI nieprzetworzonych kontaktw oraz przekazywanym przez nie wartociom. Sygnatura identyfikatora nieprzetworzonego kontaktu wyglda nastpujco:
ContactsContract.RawContacts.CONTENT_URI;

Jeeli przyjrzymy si kodowi metody showRawContactsCursor(), zauwaymy, e jest w nim


wykorzystywany powyszy identyfikator nieprzetworzonego kontaktu, dziki czemu zostaj
wywietlone pola kursora. Kliknijmy obiekt menu kursor nieprzetworzonych kontaktw.
Zobaczymy, e kursor nieprzetworzonego kontaktu zawiera pola wypisane na listingu 27.33.
Listing 27.33. Pola kursora nieprzetworzonego kontaktu
times_contacted;
phonetic_name;
phonetic_name_style;
contact_id;version;
last_time_contacted;
aggregation_mode;
_id;
name_verified;
display_name_source;
dirty;
send_to_voicemail;
account_type;
custom_ringtone;
sync4;sync3;sync2;sync1;
deleted;
account_name;
display_name;
sort_key_alt;
starred;
sort_key;
display_name_alt;
sourceid;

Skoro zapoznalimy si z kolumnami kursora nieprzetworzonych kontaktw, mog nas zainteresowa rwnie wiersze tej tabeli. Kliknijmy teraz opcj menu wszystkie nieprzetworzone
kontakty. Zostanie wywoana metoda showAllRawContacts(). Metoda ta bdzie manipulowaa
kursorem bez uycia klauzuli WHERE (dziki czemu uzyskamy dostp do wszystkich wierszy)
oraz utworzy obiekt RawContact dla kadego wiersza, po czym wywietli wyniki. Lista nieprzetworzonych kontaktw zostanie ukazana w widoku aplikacji oraz w oknie LogCat.
Za pomoc widocznych na listingu 27.33 kolumn kursora sprawdmy, czy moemy zmodyfikowa kwerend w taki sposb, aby odczytywa kontakty za pomoc danego identyfikatora
kontaktw zbiorczych. Przetestujemy tak moliwo, klikajc element menu nieprzetworzone

994 Android 3. Tworzenie aplikacji


kontakty. Zostanie najpierw wyszukany pierwszy kontakt zbiorczy, a nastpnie wysany identyfikator nieprzetworzonego kontaktu wraz z klauzul WHERE, definiujc warto kolumny
contact_id. Wyniki bd widoczne zarwno w interfejsie uytkownika, jak i w dzienniku LogCat.
Chocia przejrzelimy ju kontakty zbiorcze i nieprzetworzone kontakty, tak naprawd nie
odczytalimy jeszcze zawartoci najwaniejszych ich pl, na przykad adresu e-mail lub numeru
telefonu. W nastpnym punkcie powiemy, jak mona tego dokona.

Przegldanie danych nieprzetworzonego kontaktu


W kolejnym programie ukaemy sposb przegldania danych powizanych z nieprzetworzonymi kontaktami. Sprbujemy teraz wykona dwa zadania:
Wykry wszystkie przekazywane pola poprzez uruchomienie identyfikatora
odczytujcego dane nieprzetworzonych kontaktw.
Odczyta dane przechowywane w zestawie kontaktw zbiorczych.
W tej przykadowej aplikacji pojawiaj si nastpujce nowe pliki:
ContactData.java,
ContactDataFunctionTester.java.
Zawarto tych plikw zostanie ukazana w trakcie omawiania tej aplikacji. Wymagana bdzie
rwnie modyfikacja dwch plikw z poprzedniego przykadu:
main_menu.xml,
TestContactsDriverActivity.java.
Zmiany, ktre trzeba wprowadzi w tych plikach, zostan zaprezentowane w dalszej czci rozdziau. Kod zawarty w pliku ContactData.java suy do pobrania reprezentacyjnego zestawu
danych kontaktu. Na listingu 27.34 znajdziemy kod rdowy tego pliku.
Listing 27.34. Plik ContactData.java
public class ContactData
{
public String rawContactId;
public String aggregatedContactId;
public String dataId;
public String accountName;
public String accountType;
public String mimetype;
public String data1;
public void fillinFrom(Cursor c)
{
rawContactId = Utils.getColumnValue(c,"_ID");
accountName = Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_NAME);
accountType = Utils.getColumnValue(c,ContactsContract.RawContacts.ACCOUNT_TYPE);
aggregatedContactId =
Utils.getColumnValue(c,ContactsContract.RawContacts.CONTACT_ID);
mimetype = Utils.getColumnValue(c,ContactsContract.RawContactsEntity.MIMETYPE);
data1 = Utils.getColumnValue(c,ContactsContract.RawContactsEntity.DATA1);
dataId = Utils.getColumnValue(c,ContactsContract.RawContactsEntity.DATA_ID);
}

Rozdzia 27 Analiza interfejsu kontaktw

995

public String toString()


{
return data1 + "/" + mimetype
+ "/" + accountName + ":" + accountType
+ "/" + dataId
+ "/" + rawContactId
+ "/" + aggregatedContactId;
}
}

Sposb dziaania tego przykadu zosta zdefiniowany w pliku ContactFunctionTester.java. Jego


kod jest widoczny na listingu 27.35.
Listing 27.35. Testowanie danych kontaktu
public class ContactDataFunctionTester extends RawContactFunctionTester
{
public ContactDataFunctionTester(Context ctx, IReportBack target)
{
super(ctx, target);
}
public void showRawContactsEntityCursor()
{
Cursor c = null;
try
{
Uri uri = ContactsContract.RawContactsEntity.CONTENT_URI;
c = this.getACursor(uri,null);
this.printCursorColumnNames(c);
}
finally
{
if (c!=null) c.close();
}
}
public void showRawContactsData()
{
Cursor c = null;
try
{
Uri uri = ContactsContract.RawContactsEntity.CONTENT_URI;
c = this.getACursor(uri,"contact_id w (3,4,5)");
this.printRawContactsData(c);
}
finally
{
if (c!=null) c.close();
}
}
protected void printRawContactsData(Cursor c)
{
for(c.moveToFirst();!c.isAfterLast();c.moveToNext())
{
ContactData dataRecord = new ContactData();
dataRecord.fillinFrom(c);

996 Android 3. Tworzenie aplikacji


this.mReportTo.reportBack(tag, dataRecord.toString());
}
}
}

Do wywoania funkcji publicznych wystpujcych w tej klasie potrzebne nam bd elementy


menu z listingu 27.36, ktre naley doda do pliku main_menu.xml.
Listing 27.36. Opcje menu wymagane do testowania danych kontaktu
<item android:id="@+id/menu_show_rce_data"
android:title="dane kontaktu" />
<item android:id="@+id/menu_show_rce_cursor"
android:title="kursor encji kontaktu" />

Aktywno sterujca musi zosta zmodyfikowana zgodnie z zawartoci listingu 27.37, gdy
w przeciwnym wypadku nie bdzie reagowaa na wcinicia wspomnianych elementw menu
i nie bdzie wywoywaa funkcji klasy ContactDataFunctionTester.
Listing 27.37. Zaktualizowana aktywno sterujca, pozwalajca na testowanie danych kontaktu
public class TestContactsDriverActivity extends DebugActivity
implements IReportBack
{
public static final String tag="TestContacts";

...inne funkcje testowe


...dodajmy poniszy wiersz na kocu pozostaych funkcji testowych
ContactDataFunctionTester contactDataFunctionTester = null;
public TestContactsDriverActivity()
{

...dodajmy poniszy wiersz na kocu niniejszej funkcji


contactDataFunctionTester = new ContactDataFunctionTester(this,this);
}
protected boolean onMenuItemSelected(MenuItem item)
{

...odpowiada na pozostae elementy menu


...dodajmy nastpujce wiersze
if (item.getItemId() == R.id.menu_show_rce_cursor)
{
contactDataFunctionTester.showRawContactsEntityCursor();
return true;
}
if (item.getItemId() == R.id.menu_show_rce_data)
{
contactDataFunctionTester.showRawContactsData();
return true;
}

...koniec nowych wierszy


return true;
}
}

Rozdzia 27 Analiza interfejsu kontaktw

997

Przeanalizujmy teraz powyszy kod i cay przykadowy program. Android ustanawia specjalny
widok, RawContactEntity, sucy do odczytywania danych z tabeli nieprzetworzonych kontaktw oraz z odpowiadajcych jej tabel danych, co zostao zaprezentowane w punkcie dotyczcym widoku Contact_entities_view. Identyfikator URI uzyskujcy dostp do tego widoku
zosta zdefiniowany w pomocniczej klasie. Pena cieka staej tego identyfikatora zostaa zaprezentowana na listingu 27.38.
Listing 27.38. Identyfikator treci nieprzetworzonego kontaktu
ContactsContract.RawContactsEntity.CONTENT_URI

Powyszy identyfikator jest wykorzystywany w omawianym programie do uzyskiwania informacji na temat przekazywanych pl. Spis tych pl otrzymamy po wciniciu opcji menu kursor
encji kontaktu. Listing 27.39 prezentuje spis kolumn uzyskiwany po klikniciu wspomnianego
elementu menu.
Listing 27.39. Kolumny kursora encji kontaktu
data_version;
contact_id;
version;
data12;data11;data10;
mimetype;
res_package;
_id;
data15;data14;data13;
name_verified;
is_restricted;
is_super_primary;
data_sync1;dirty;data_sync3;data_sync2;
data_sync4;account_type;data1;sync4;sync3;
data4;sync2;data5;sync1;
data2;data3;data8;data9;
deleted;
group_sourceid;
data6;data7;
account_name;
data_id;
starred;
sourceid;
is_primary;

Po zapoznaniu si z tym zestawem kolumn moemy zawzi wyniki przekazywane przez ten
kursor poprzez sformuowanie klauzuli WHERE. Przykadowo po klikniciu nastpnego elementu
menu uzyskamy elementy danych, ktrym przypisano identyfikatory kontaktu o wartociach
3, 4 i 5. W tym celu wystarczyo doda w kodzie nastpujc klauzul WHERE:
"contact_id in (3,4,5)"

i wysa j wraz z kursorem. Dokadnie taka operacja zostaa powizana z obiektem menu dane
kontaktu. Po jego wciniciu zostan wywietlone takie informacje, jak imi i nazwisko oraz adres e-mail (element danych rozpoznajemy po jego typie MIME).

998 Android 3. Tworzenie aplikacji

Dodawanie kontaktu oraz szczegowych informacji o nim


Dotychczas omawialimy jedynie odczytywanie kontaktw. Zajmijmy si teraz przykadowym
programem pozwalajcym na dodanie nowego kontaktu zawierajcego imi, nazwisko, adres
e-mail oraz numer telefonu.
Aby mc zapisywa informacje w kontakcie, trzeba doda nastpujce uprawnienie w pliku
manifecie (listing 27.20):
android.permission.WRITE_CONTACTS

Do przetestowania tego przykadu bdziemy potrzebowa nowego pliku:


AddContactFunctionTester.java.
Ponadto musimy zmodyfikowa nastpujce pliki, pochodzce z poprzednich przykadw:
main_menu.xml,
TestContactsDriverActivity.java.
Plik AddContactFunctionTester.java pozwala na dodawanie kontaktu wypenionego danymi.
Na listingu 27.40 widzimy kod rdowy tego pliku.
Listing 27.40. Dodawanie kontaktw zawierajcych szczegowe informacje
Import android.provider.ContactsContract.Data;

//...pozostae instrukcje importu, ktre rodowisko Eclipse moe doda za nas


public class AddContactFunctionTester extends ContactDataFunctionTester
{
public AddContactFunctionTester(Context ctx, IReportBack target)
{
super(ctx, target);
}
public void addContact()
{
long rawContactId = insertRawContact();
this.mReportTo.reportBack(tag, "RawcontactId:" + rawContactId);
insertName(rawContactId);
insertPhoneNumber(rawContactId);
showRawContactsDataForRawContact(rawContactId);
}
private void insertName(long rawContactId)
{
ContentValues cv = new ContentValues();
cv.put(Data.RAW_CONTACT_ID, rawContactId);
cv.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
cv.put(StructuredName.DISPLAY_NAME,"Gall Anonim_" + rawContactId);
this.mContext.getContentResolver().insert(Data.CONTENT_URI, cv);
}
private void insertPhoneNumber(long rawContactId)
{
ContentValues cv = new ContentValues();
cv.put(Data.RAW_CONTACT_ID, rawContactId);
cv.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
cv.put(Phone.NUMBER,"123 123 " + rawContactId);
cv.put(Phone.TYPE,Phone.TYPE_HOME);

Rozdzia 27 Analiza interfejsu kontaktw

999

this.mContext.getContentResolver().insert(Data.CONTENT_URI, cv);
}
private long insertRawContact()
{
ContentValues cv = new ContentValues();
cv.put(RawContacts.ACCOUNT_TYPE, "com.google");
cv.put(RawContacts.ACCOUNT_NAME, "satya.komatineni@gmail.com");
Uri rawContactUri =
this.mContext.getContentResolver()
.insert(RawContacts.CONTENT_URI, cv);
long rawContactId = ContentUris.parseId(rawContactUri);
return rawContactId;
}
private void showRawContactsDataForRawContact(long rawContactId)
{
Cursor c = null;
try
{
Uri uri = ContactsContract.RawContactsEntity.CONTENT_URI;
c = this.getACursor(uri,"_id = " + rawContactId);
this.printRawContactsData(c);
}
finally
{
if (c!=null) c.close();
}
}
}

Jedyn funkcj publiczn jest addContact(). W celu jej wywoania potrzebny nam bdzie element menu zamieszczony na listingu 27.41.
Listing 27.41. Element menu pozwalajcy na dodawanie kontaktu
<item android:id="@+id/menu_add_contact"
android:title="Dodaj kontakt" />

Powysze dwa wiersze umieszczamy w pliku main_menu.xml. Musimy zmodyfikowa take


aktywno sterujc w taki sposb, eby uruchamiaa metod addContact() po wciniciu
utworzonej przed chwil opcji menu. Na listingu 27.42 prezentujemy kod rdowy zmodyfikowanej aktywnoci sterujcej (pamitajmy, e mamy tu do czynienia nie z nowym plikiem,
lecz z aktualizacj starego).
Listing 27.42. Zaktualizowana aktywno sterujca, pozwalajca na dodawanie kontaktw
public class TestContactsDriverActivity extends DebugActivity
implements IReportBack
{

...inne mechanizmy
AddContactFunctionTester addContactFunctionTester = null;
public TestContactsDriverActivity()
{

1000 Android 3. Tworzenie aplikacji


...inne mechanizmy
addContactFunctionTester = new AddContactFunctionTester(this,this);
}
protected boolean onMenuItemSelected(MenuItem item)
{

...inne mechanizmy
if (item.getItemId() == R.id.menu_add_contact)
{
addContactFunctionTester.addContact();
return true;
}
return true;
}
}

Jeeli klikniemy teraz opcj menu Dodaj kontakt, kod zawarty na listingu 27.40 (funkcja testowa dodawania kontaktw) wykona nastpujce czynnoci:
1. Najpierw doda do predefiniowanego konta (korzystajc z jego nazwy i typu)
nieprzetworzony kontakt za pomoc metody insertRawContact().
2. Pobierze identyfikator nieprzetworzonego kontaktu i wstawi w tabeli danych rekord
imienia i nazwiska (metoda insertName()).
3. Znowu pobierze identyfikator nieprzetworzonego kontaktu i wstawi w tabeli danych
rekord numeru telefonu (metoda insertPhoneNumber()).
Na listingu 27.40 widzimy aliasy kolumn wykorzystywane przez wymienione metody podczas
wstawiania rekordw. Umiecilimy je rwnie na listingu 27.43, aby uproci ich przegldanie.
Listing 27.43. Uywanie aliasw kolumn w standardowych strukturach danych kontaktu
cv.put(Data.RAW_CONTACT_ID, rawContactId);
cv.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
cv.put(StructuredName.DISPLAY_NAME,"Gall Anonim_" + rawContactId);
cv.put(Data.RAW_CONTACT_ID, rawContactId);
cv.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
cv.put(Phone.NUMBER,"123 123 " + rawContactId);
cv.put(Phone.TYPE,Phone.TYPE_HOME);
cv.put(RawContacts.ACCOUNT_TYPE, "com.google");
cv.put(RawContacts.ACCOUNT_NAME, "satya.komatineni@gmail.com");

Szczeglnie istotne jest, aby pamita, e takie stae, jak Phone.TYPE czy Phone.NUMBER, odnosz si w rzeczywistoci do nazw kolumn data1 i data2 umieszczonych w tabeli danych.
Aby w kocu ujrze dodany rekord, kliknijmy element menu Dodaj kontakt. Zostan dodane
i wywietlone szczegowe informacje tego rekordu, gdy zostan one odczytane za pomoc
funkcji showRawContactsDataForRawContact(). Kade z pl danych bdzie umieszczone
w strukturze ContactData.

Rozdzia 27 Analiza interfejsu kontaktw

1001

Kontrola agregacji
W tej chwili dla Czytelnika powinno ju by jasne, e klienty aktualizujce lub dodajce kontakty nie modyfikuj tabeli contact w jawny sposb. Tabela ta jest modyfikowana przez obiekty
wyzwalajce, ktre ledz tabel nieprzetworzonych kontaktw oraz tabel danych.
Z kolei dodawane lub zmieniane nieprzetworzone kontakty oddziauj na kontakty zbiorcze
znajdujce si w tabeli kontaktw. Niekiedy jednak powizanie dwch kontaktw ze sob jest
niekorzystne.
Moemy kontrolowa proces czenia nieprzetworzonych kontaktw poprzez ustalenie trybu
agregacji w czasie ich tworzenia. Jak wida po umieszczonych na listingu 27.33 nazwach
kolumn tabeli nieprzetworzonych kontaktw, zawiera ona pole aggregation_mode. Wartoci umieszczone w tym polu zostay wymienione na listingu 27.2 i objanione w punkcie
Kontakty zbiorcze.
Moemy rwnie zapewni, by dwa kontakty pozostaway rozdzielone, poprzez umieszczenie
odpowiednich wierszy w tabeli agg_exceptions. Identyfikatory URI wymagane do wstawiania
danych do tej tabeli s zdefiniowane w klasie ContactsContract.AggregationExceptions.
Struktura tabeli agg_exceptions zostaa zaprezentowana na listingu 27.44.
Listing 27.44. Definicja tabeli zawierajcej wyjtki agregacji
CREATE TABLE agg_exceptions
(_id INTEGER PRIMARY KEY AUTOINCREMENT,
type INTEGER NOT NULL,
raw_contact_id1 INTEGER REFERENCES raw_contacts(_id),
raw_contact_id2 INTEGER REFERENCES raw_contacts(_id))

Kolumna type moe przechowywa jedn ze staych wymienionych na listingu 27.45.


Listing 27.45. Typy agregacji definiowane w tabeli wyjtkw agregacji
TYPE_KEEP_TOGETHER
TYPE_KEEP_SEPARATE
TYPE_AUTOMATIC

Definicje i zadania poszczeglnych typw agregacji s do zrozumiae. Typ TYPE_KEEP_TOGETHER


nie pozwala na rozdzielenie dwch kontaktw. Z kolei warto TYPE_KEEP_SEPARATE uniemoliwia poczenie kontaktw. Ostatnia warto, TYPE_AUTOMATIC, wykorzystuje domylny
algorytm agregacji kontaktw.
Identyfikator URI umoliwiajcy umieszczanie, odczytywanie i aktualizowanie wierszy zawartych w tej tabeli wyglda nastpujco:
ContactsContract.AggregationExceptions.CONTENT_URI

Rwnie stae wykorzystywane wraz z definicjami pl w tej tabeli s dostpne w klasie Contacts
Contract.AggregationExceptions.

1002 Android 3. Tworzenie aplikacji

Konsekwencje synchronizacji
Przez wikszo rozdziau zajmowalimy si wycznie manipulowaniem kontaktami w obrbie
urzdzenia. Zazwyczaj jednak konta i przypisane im kontakty s synchronizowane cznie. Jeli
na przykad utworzylimy konto Google w telefonie obsugiwanym przez system Android,
wszystkie kontakty zostan skopiowane z serwera do tego telefonu.
Za kadym razem, gdy w urzdzeniu dodajemy nowy kontakt lub konto serwerowe, nastpi
proces synchronizacji i odzwierciedlenia danego obiektu w obydwu miejscach.
Jednak w tym wydaniu ksiki nie zajlimy si omwieniem interfejsu synchronizowania ani
mechanizmem jego dziaania. Jest to zagadnienie rwnie obszerne jak tematyka kontaktw.
Znajomo dziaania interfejsu kontaktw znacznie pomaga w zrozumieniu interfejsu synchronizacji. Zalecamy wic zagldanie na stron www.androidbook.com, ktra jest do regularnie aktualizowana.
Natura mechanizmu synchronizacji wpywa rwnie na proces usuwania kontaktw z urzdzenia. W czasie usuwania kontaktu za pomoc identyfikatora kontaktu zbiorczego zostan
wykasowane wszystkie zwizane z nim nieprzetworzone kontakty, a take elementy danych powizane z tymi kontaktami. Jednak system jedynie oznacza te obiekty jako usunite i dopiero
w procesie przebiegajcej w tle synchronizacji z serwerem zaznaczone kontakty zostan trwale
usunite z urzdzenia. Taka kaskada procesw kasowania wystpuje take na poziomie nieprzetworzonych kontaktw, gdzie zostaj usunite elementy danych powizane z danym nieprzetworzonym kontaktem.

Odnoniki
Zaprezentowane poniej odnoniki umoliwi Czytelnikowi dostp do materiaw pomocniczych
oraz rozszerzajcych zakres informacji zawartych w tym rozdziale. Ostatni z zamieszczonych
adresw URL umoliwia pobranie projektw utworzonych specjalnie na potrzeby tego rozdziau.
http://www.google.com/support/mobile/bin/answer.py?answer=182077 adres instrukcji
obsugi Androida w wersji 2.3. Znajdziemy w niej informacje dotyczce aplikacji
Kontakty, pozwalajcej na zarzdzanie kontaktami. Chocia omwilimy podstawowe
informacje zwizane z obsug tej aplikacji, najwaniejsza w tej kwestii pozostaje
instrukcja obsugi. Czytelnik moe w niej znale informacje, ktre my moglimy
przypadkowo przeoczy.
http://www.google.com/help/hc/pdfs/mobile/AndroidUsersGuide-30-100.pdf instrukcja
obsugi Androida w wersji 3.0.
http://developer.android.com/resources/articles/contacts.html ten adres URL
prowadzi do artykuu, w ktrym omwiono sposb korzystania z interfejsu kontaktw.
Jest to podstawowa dokumentacja dotyczca interfejsu kontaktw, stworzona przez
firm Google.
http://www.androidbook.com/item/3585 zrozumienie koncepcji interfejsu
kontaktw polega przede wszystkim na pojciu struktury tworzcych go tabel. Klasa
ContactsContract stanowi jedynie cienk oson wok podstawowej struktury
tabel. Pod tym adresem mona znale informacje o rnorodnych strukturach tabel
opracowanych przez autorw ksiki. Znajdziemy tam nazwy pl, ich typy, widoki
kontaktw zbiorczych i tak dalej.

Rozdzia 27 Analiza interfejsu kontaktw

1003

http://developer.android.com/reference/android/provider/ContactsContract.html
dokumentacja Javadoc opisujca klas wejciow opublikowanego kontraktu
kontaktw. Bardzo przydatny adres dla osb zajmujcych si pisaniem programw
wykorzystujcych interfejs kontaktw.
http://www.netmite.com/android/mydroid/2.0/packages/providers/ContactsProvider/
z powodu niedostatku informacji dotyczcych obsugi kontaktw by moe
Czytelnika zainteresuje kod rdowy dostawcy kontaktw. Pod tym adresem
znajdziemy stron Netmite, gdzie zamieszczono kody rdowe wszystkich plikw
tworzcych dostawc treci.
http://www.netmite.com/android/mydroid/2.0/packages/apps/Contacts/src/com/andr
oid/contacts podobnie jak w poprzednim przypadku, cze to prowadzi do kodu
rdowego aplikacji Kontakty. Jeeli Czytelnik chce pozna mechanizm tworzenia
lub aktualizowania kontaktu zbiorczego, wanie znalaz y zota.
http://www.androidbook.com/item/3537 jeeli Czytelnik przeglda kody rdowe
dostpne pod dwoma powyszymi adresami, prawdopodobnie poczu si nieco
oguszony. Pod tym adresem znajdziemy wic podsumowanie danych tam zawartych,
ktre by moe komu si przyda.
ftp://ftp.helion.pl/przyklady/and3ta.zip pod tym adresem znajdziemy projekty
utworzone specjalnie na potrzeby niniejszej ksiki. Interesujcy nas katalog nosi
nazw ProAndroid3_R27_Kontakty.

Podsumowanie
W niniejszym rozdziale zapoznalimy si ze struktur kontaktw, dostpn w systemie Android.
Moemy wykorzysta zawarte tu informacje do odczytywania lub aktualizowania kontaktw za
pomoc publicznego interfejsu kontaktw.
Chocia powicilimy mnstwo uwagi interfejsowi kontaktw, nie omwilimy pracy z dostawcami treci pracujcymi w trybie wsadowym, w ktrym mona dodawa lub usuwa kontakty. Zestaw Android SDK zawiera klas ContentProviderOperation umoliwiajc przeprowadzenie procesw wstawiania, aktualizowania i usuwania kontaktw w trybie wsadowym,
co pozwala na optymalizacj dziaania systemu.
Tryb wsadowy staje si tym istotniejszy dla dostawcw synchronizacji, im wiksza liczba kontaktw jest aktualizowana i dodawana. W przypadku kwerend oraz sporadycznych aktualizacji
omwione w tym rozdziale rozwizania s cakowicie wystarczajce. Warto jednak co jaki czas
zaglda na stron www.androidbook.com.

1004 Android 3. Tworzenie aplikacji

R OZDZIA

28
Wdraanie aplikacji na rynek
Android Market i nie tylko

Stworzenie wspaniaej aplikacji, ktr pokochaj uytkownicy, to jedna sprawa.


Naley te zadba o wprowadzenie rozwizania umoliwiajcego jej szybkie znalezienie i pobranie. W tym wanie celu firma Android zaprojektowaa sklep Android
Market. Za pomoc ikony umieszczonej po prawej stronie urzdzenia uytkownicy mog przej wprost na stron sklepu i przeglda, wyszukiwa, ocenia oraz
pobiera aplikacje. Uytkownicy mog uzyska dostp do serwisu Android Market
rwnie z poziomu komputera stacjonarnego, chocia pobierane pliki bd
ostatecznie umieszczane w urzdzeniu, a nie w stacji roboczej. Cz aplikacji jest
dostpna za darmo, a w przypadku patnych wersji zostay wprowadzone mechanizmy patnicze usprawniajce szybki zakup.
Android Market jest dostpny nawet z poziomu intencji wewntrz aplikacji, dziki
czemu uytkownik w atwy sposb moe znale miejsce, z ktrego mona pobra
skadniki wymagane przez ten program. Na przykad po wydaniu nowej wersji
aplikacji moemy pozwoli uytkownikowi dosta si bezporednio do lokacji,
z ktrej mona pobra lub zakupi ten plik. Android Market nie jest jednak jedynym miejscem, w ktrym mona zaopatrzy si w aplikacje; w internecie cay czas
pojawiaj si nowe kanay.
Android Market nie jest dostpny z poziomu emulatora (chocia istniej pewne
nielegalne sposoby obejcia tego problemu). Stanowi to swego rodzaju utrudnienie
dla programisty. Najlepszym rozwizaniem jest wasne urzdzenie pozwalajce na
poczenie z Android Market. Sklep ten jest dostpny poprzez urzdzenie Android
Developer Phone, zablokowano w nim jednak dostp do patnych aplikacji. Jest
to jedno z rozwiza firmy Google chronicych te aplikacje przed piractwem.
W rozdziale tym zajmiemy si konfigurowaniem procesu publikowania aplikacji
w sklepie Android Market, przygotowaniem jej do sprzeday, uproszczeniem procesu wyszukiwania, pobierania i korzystania z niej przez uytkownikw, sposobami
zabezpieczania aplikacji przed piractwem, a na kocu zaprezentujemy kilka
alternatywnych sposobw udostpnienia programw bez wykorzystania sklepu
firmy Google.

1006 Android 3. Tworzenie aplikacji

Jak zosta wydawc?


Zanim umiecimy aplikacj w sklepie Android Market, musimy zosta wydawcami. W tym
celu naley utworzy konto programisty (ang. Developer Account). Po zarejestrowaniu takiego
konta bdziemy mogli zamieszcza aplikacje w sklepie Android Market, gdzie bd wyszukiwane i pobierane przez uytkownikw. Proces rejestracji konta programisty jest wzgldnie prosty i stosunkowo tani.
Aby cokolwiek opublikowa, potrzebne jest konto Google na przykad konto pocztowe
gmail.com. Nastpnie tworzymy tosamo w sklepie Android Market. W tym celu otwieramy
stron http://market.android.com/publish/signup. Wprowadzamy tu imi i nazwisko programisty, adres e-mail, adres strony WWW oraz numer telefonu kontaktowego. Po zarejestrowaniu konta dane te bdzie mona zmieni. Nastpnie trzeba uici opat rejestracyjn. Zajmuje
si tym system Google Checkout. Przeprowadzenie caej transakcji wymaga zalogowania si na
konto Google.
Jedn z opcji dostpnych podczas procesu patnoci jest Zachowaj poufny charakter mojego adresu
e-mail. Odnosi si to do biecej transakcji pomidzy programist a usug Google Android
Market, dotyczcej zakupu praw wydawcy. Zaznaczenie tej opcji spowoduje ukrycie adresu
e-mail przed usug Google Android Market. Nie ma to nic wsplnego z ukrywaniem adresu
e-mail przed potencjalnymi uytkownikami aplikacji. Wybr tej opcji nie ma wpywu na dostpno adresu e-mail dla osb kupujcych aplikacj. W dalszej czci rozdziau rozwiniemy
ten temat.
Nastpnie zostanie wywietlona umowa dotyczca dystrybucji produktw przez ich dewelopera
za porednictwem usugi Android Market. Jest to legalny kontrakt zawierany pomidzy programist a firm Google. S w nim okrelone warunki dystrybucji aplikacji, pobierania i zwrotu
patnoci, wsparcia i obsugi technicznej, systemu oceniania, praw nabywcy, praw wydawcy i tak
dalej. Wicej informacji na temat tych regu znajdziemy w podrozdziale Postpowanie zgodnie z zasadami.
Po zaakceptowaniu umowy ujrzymy stron znan powszechnie jako konsola programisty (ang.
Developer Console) http://market.android.com/publish/Home.

Postpowanie zgodnie z zasadami


Umowa dotyczca dystrybucji produktw przez ich dewelopera za porednictwem usugi
Android Market (ang. Android Market Developer Distribution Agreement AMDDA) zawiera
mnstwo regu postpowania. By moe przed jej zaakceptowaniem naleaoby zasign opinii
radcy prawnego, w zalenoci od zakresu planowanych dziaa wewntrz serwisu. W tym
punkcie omwimy kilka kwestii wartych odnotowania.
Aby korzysta ze sklepu Android Market, naley by programist o nieposzlakowanej
reputacji,. Oznacza to, e trzeba przej przez omwiony powyej proces rejestracji,
zaakceptowa umow i przestrzega jej zasad. Skutkiem zamania zasad moe by
zablokowanie dostpu do sklepu i usunicie z niego naszych aplikacji.
Moemy dystrybuowa zarwno produkty bezpatnie, jak i za opat. Umowa dopuszcza
obie moliwoci. W przypadku sprzeday produktw musimy korzysta z takiego
procesora patnoci, jak na przykad Google Checkout. W momencie wydania
platformy Android 2.0 jedyn form pobierania opat pochodzcych ze sklepu
Android Market bya wanie usuga Google Checkout. Obecnie uytkownicy mog

Rozdzia 28 Wdraanie aplikacji na rynek Android Market i nie tylko

1007

po prostu obciy swj rachunek telefoniczny podczas pobierania opat, co zostao


ogoszone przez firm T-Mobile w 2009 roku oraz przez firm AT&T w 2010 roku.
W padzierniku 2010 roku zapowiedziano integracj usugi PayPal z serwisem
Android Market, lecz po ponad roku cigle brakuje takiej opcji. W przyszoci to
podejcie moe jednak ulec zmianie.
W przypadku zakupu patnych aplikacji jest pobierana opata transakcyjna oraz
ewentualnie opata dla operatora sieci komrkowej, ktre zostan odjte od ceny
produktu. W listopadzie 2011 roku opata transakcyjna wynosi 30%, jeli wic produkt
kosztuje 10 dolarw, firma Google otrzymuje 3 dolary, a my 7 (pod warunkiem e
nie dochodz do tego opaty dla operatora).
Do programisty naley obowizek odprowadzania nalenego podatku do waciwych
organw podatkowych. Podczas ustanawiania konta handlowego definiuje si
odpowiednie wysokoci podatku dotyczce zakupu produktu przez osoby znajdujce
si w innych rejonach. Usuga Google Checkout pobierze odpowiedni podatek
w zalenoci od tego, w jaki sposb zostaa skonfigurowana. Opata ta zostanie wysana
do programisty, ktry musi j odprowadzi do urzdu skarbowego. Dodatkowe informacje
na temat sprzeday usug i licencji w Polsce mona znale pod adresem http://www.vat.pl/
sprzedaz_licencji_ebooki_oprogramowanie_firma_w_internecie_1052.php.
Istnieje moliwo umieszczania w serwisie Android Market darmowej wersji
demonstracyjnej aplikacji, posiadajcej opcj odblokowania wszystkich funkcji penej
wersji po wniesieniu opaty; opata musi by jednak uiszczona poprzez autoryzowany
procesor patnoci. Nie moemy skierowa uytkownikw darmowej aplikacji do
innego procesora patnoci w celu pobrania opaty za odblokowanie wszystkich
funkcji programu. Zabronione jest rwnie pobieranie opaty za prenumerowanie
aplikacji dystrybuowanych poprzez sklep Android Market. Opaty za usugi zasadniczo
s nawet zalecane, gdy pomagaj chroni aplikacj przed piractwem oraz zwikszaj
obroty twrcw oprogramowania. Oznacza to jednak, e nie moemy sprzedawa
takiej wersji aplikacji poprzez Android Market. By moe zostanie to zmienione
w przyszoci. Pomylmy o tym w nastpujcy sposb: jeeli chcemy zarabia poprzez
serwis Android Market, firma Google pragnie mie swj udzia w tych zarobkach.
W lutym 2011 roku firma Google zapowiedziaa wprowadzenie
wewntrzaplikacyjnych mikrotransakcji. Jest to dodatkowy pakiet SDK pozwalajcy
na pobieranie opat za cyfrowe towary lub dodatkow zawarto programu. Tak
zawartoci moe by nowa bro lub pakiet poziomw w grze bd pliki graficzne
czy muzyczne. Zapata za takie dodatki wyglda tak samo jak proces kupna aplikacji,
co oznacza, e uytkownicy mog obciy swj rachunek telefoniczny.
Jeeli nasza aplikacja wymaga od uytkownika posiadania konta w jakim serwerze
sieciowym, z ktrego mona korzysta za dodatkow opat abonamentow, serwer
ten moe pobiera t opat w dowolny sposb. Taki sposb odczenia opaty
abonamentowej od aplikacji jest zgodny z umow AMDDA pod warunkiem
e bezpatna wersja aplikacji nie kieruje uytkownikw na jej stron domow.
Nie byoby jednak atwiej rozprowadza aplikacj z tego samego serwera, na ktrym
znajduje si usuga?
Okazuje si, e istnieje moliwo wprowadzania alternatywnych sposobw
przetwarzania patnoci wnoszonych w formie darowizny przeznaczonej na rozwj
darmowej wersji aplikacji, nie mona jednak wewntrz programu wprowadza
adnych bodcw motywujcych do uiszczenia takiej opaty.

1008 Android 3. Tworzenie aplikacji

Zwroty patnoci s nieprzyjemn stron serwisu Android Market. Pocztkowo


uytkownikom przysugiway 24 godziny na zadanie zwrotu pienidzy za zakupion
aplikacj. Nastpnie czas ten zosta wyduony do 48 godzin. Natomiast w grudniu
2010 roku zosta zmieniony na 15 minut! Kwadrans ten jest liczony od samego
momentu zakupu, a nie od chwili zakoczenia pobierania aplikacji. Zdarzay si
nawet przypadki, gdy uytkownik nie zdy jeszcze pobra aplikacji, a okno czasowe
zagwarantowane na zwrot pienidzy zakoczyo dziaanie. Co dziwne, umowa AMDDA
nie zostaa zaktualizowana pod tym ktem i cigle widnieje w niej wpis o 48 godzinach
przysugujcych na zwrot pienidzy. Zwroty pienidzy nie przysuguj uytkownikom,
ktrzy mog przeglda aplikacje przed ich zakupem. Dotyczy to rwnie dzwonkw
i tapet. Usuga Google Checkout pozwala jednak deweloperom na zwrot pienidzy
nawet po wyznaczonym terminie, uytkownicy mog wic mimo wszystko odzyska
pienidze. Deweloperzy jednak nie maj ochoty na wasnorczne oddawanie pienidzy.
Wymagane jest, aby programista zagwarantowa odpowiedni pomoc techniczn
dotyczc produktu. Jeeli pomoc ta nie zostanie zapewniona, uytkownicy mog
da zwrotu pienidzy. Bdzie to si wiza z kosztami dla dewelopera, zwaszcza
e prawdopodobnie bd w to wliczone rwnie koszty manipulacyjne.
Uytkownicy maj prawo do nieograniczonej liczby ponownych instalacji aplikacji
pobranych ze sklepu Android Market. W przypadku przywrcenia ustawie fabrycznych
urzdzenia uytkownik bdzie mg pobra wszelkie zakupione aplikacje bez
koniecznoci ponownego pacenia za nie.
Wydawca gwarantuje ochron prywatnoci i praw uytkownikw. Dotyczy to ochrony
(na przykad zabezpieczania) wszelkich danych, ktre mog zosta zebrane podczas
uytkowania aplikacji. Zmiana warunkw dotyczcych ochrony danych uytkownika
jest dopuszczalna jedynie w przypadku wywietlenia odpowiedniej umowy
i zaakceptowania jej przez uytkownika.
Aplikacja nie moe konkurowa ze sklepem Android Market. Firma Google nie
pozwala na umieszczanie aplikacji umoliwiajcych sprzeda innych produktw
poza sklepem Android Market, co jest rwnoznaczne z ominiciem procesora
patnoci. Nie oznacza to wcale, e nie mona sprzedawa tej aplikacji innymi
kanaami, lecz e ta aplikacja, po jej umieszczeniu w sklepie Android Market,
nie moe umoliwia sprzeday produktw znajdujcych si poza t usug.
Umieszczone produkty zostan objte systemem oceniania. Oceny mog by
przydzielane na podstawie udzielanej pomocy technicznej, szybkoci instalacji
i odinstalowania aplikacji, szybkoci zwrotu kosztw oraz (lub) jako tak zwana
ocena oglna dewelopera (ang. Developer Composite Score). Jest ona szacowana na
podstawie ocen wystawianych dla poprzednich aplikacji i moe wpywa na ocen
przyszych produktw. Z tego powodu istotne jest, aby wydawa aplikacje wysokiej
jakoci, nawet jeli s darmowe. Nie jestemy pewni, czy ocena oglna dewelopera
w ogle istnieje, jeli jednak tak nie mamy w ni wgldu.
Poprzez sprzeda aplikacji w sklepie Android Market udzielamy uytkownikowi
niewycznej i oglnowiatowej licencji na odtwarzanie, prezentowanie i uytkowanie
Produktu w Urzdzeniu. Jednak nic si nie stanie, jeli napiszemy wasne warunki
umowy licencyjnej (ang. End User License Agreement EULA), zastpujce powysze
stwierdzenie. Naley tak umow umieci na wasnej stronie WWW lub
zagwarantowa jaki inny sposb jawnego zaprezentowania jej uytkownikom.

Rozdzia 28 Wdraanie aplikacji na rynek Android Market i nie tylko

1009

Firma Google wymaga przestrzegania cech marki Android. Do tych cech zaliczaj si
ograniczenia w wykorzystywaniu sowa Android, jak rwnie ikony robota, znaku
towarowego oraz kroju pisma. Informacje na ten temat znajdziemy pod adresem
http://www.android.com/branding.html.

Konsola programisty
Konsola programisty jest nasz stron docelow, pozwalajc na kontrolowanie aplikacji umieszczonych w serwisie Android Market. Z poziomu konsoli programisty moemy zakupi urzdzenie ADP (ang. Android Developer Phone telefon programisty systemu Android), skonfigurowa konto handlowe w usudze Google Checkout (dziki czemu moemy nalicza opat
za aplikacj), publikowa aplikacje oraz przeglda informacje na temat opublikowanych programw. Moemy take edytowa takie szczegy konta, jak imi i nazwisko programisty, adres
e-mail, adres strony WWW oraz numer telefonu. Konsola programisty zostaa zaprezentowana
na rysunku 28.1.

Rysunek 28.1. Konsola programisty pozwalajca uzyska dostp do serwisu Android Market

Obecnie istniej trzy rodzaje telefonw testowych obsugujcych system Android: Android Developer Phone, Google Nexus One oraz Google Nexus S. Android Developer Phone (ADP) by
przez dugi czas jedynym telefonem pozwalajcym na testowanie programowanych aplikacji.
Jest to specjalne urzdzenie, zaprojektowane przede wszystkim dla programistw aplikacji dla
systemu Android. Jest to profesjonalny telefon, posiadajcy odblokowane wszystkie funkcje,
niezaleny od operatorw telefonii komrkowej. Akceptuje wszystkie rodzaje kart SIM, a w jego
wyposaeniu znajduje si karta pamici 1 GB, aparat fotograficzny, wysuwana klawiatura
i system GPS. Piszc o odblokowanych funkcjach, mamy na myli moliwo wykonania kadej

1010 Android 3. Tworzenie aplikacji


czynnoci w urzdzeniu, cznie z instalacj nowej wersji oprogramowania sprztowego i systemu Android, nie tylko aplikacji. Chocia moemy instalowa nowe wersje oprogramowania
sprztowego, fabryczna wersja tego urzdzenia jest wyposaona w system Android 1.6.
By moe Czytelnik pamita czasy wydania pierwszego telefonu firmy Google, jakim jest Nexus
One. Chocia by zaprojektowany we wsppracy z firm HTC, z powodu sabych wynikw
sprzeday zaprzestano produkcji tego telefonu, a nastpnie przerobiono go na telefon testowy, pozwalajcy uytkownikom na sprawdzanie dziaania tworzonych aplikacji. Zosta w tej
roli bardzo dobrze przyjty, wic firma Google zwikszya zamwienia na produkcj tego
modelu. Specyfikacja techniczna telefonu Nexus One robi wraenie. Zosta on zaopatrzony
w czujnik zblieniowy, czujnik owietlenia, akcelerometry oraz kompas. Bez wikszego problemu obsuguje system Android 2.2 i jeszcze przez pewien czas nie powinien mie problemu z nowszymi wersjami systemu. Specyfikacja tego telefonu jest dostpna pod adresem
http://www.htc.com/us/support/nexus-one-google/tech-specs/. Wrd wad naley wymieni brak
obsugi technologii 3G, co zmusza do korzystania z innych form sieci bezprzewodowych
(AT&T, 2G lub EDGE).
W grudniu 2010 roku firma Google rozpocza sprzeda telefonu Nexus S, ktrego jednak nie
mona zamwi z poziomu konsoli programisty. Zosta po raz pierwszy udostpniony w sklepie
Best Buy (USA) oraz w Carphone Warehouse (Wielka Brytania) i moe zosta zakupiony bez
umowy z operatorem sieci komrkowej. Urzdzenie to jest jeszcze szybsze i bardziej zaawansowane od telefonu Nexus One, posiada wbudowany yroskop, czujnik NFC, a take aparat
umieszczony z przodu. Jest obsugiwany przez system Android 2.3 (Gingerbread). Specyfikacja,
wraz w filmami demonstracyjnymi, jest dostpna pod adresem www.google.com/nexus/#.
Jeeli chcemy testowa nowe wersje oprogramowania sprztowego lub sam system Android,
potrzebne nam bdzie urzdzenie testowe. Z trzech dostpnych rodzajw telefonw najlepszym
wyborem jest Nexus S. Jednym z pozytywnych aspektw telefonw z grupy Nexus jest pierwszestwo w aktualizowaniu ich do nowej wersji systemu. Jest to jeden z powodw, dla ktrych
warto wybra telefon Nexus zamiast zwykego telefonu zwizanego z operatorem. Jeeli chcemy jedynie pisa aplikacje, a nie modyfikowa w jaki sposb system Android, powinien nam
wystarczy zwyky telefon. Kade urzdzenie obsugujce system Android moe zosta podczone do stacji roboczej w celach projektowych i testowych. Musimy jednak zwraca uwag na
specyfikacj techniczn. Nie wszystkie telefony mog zosta zaktualizowane do najnowszej
wersji systemu, dotyczy to zwaszcza mniej wydajnych modeli.
Jeli nie skonfigurujemy konta handlowego za pomoc usugi Google Checkout, nie bdziemy
mogli pobiera opat za produkty umieszczone w sklepie Android Market. Ustanowienie takiego
konta nie jest skomplikowan czynnoci. Wystarczy klikn odpowiednie cze w konsoli programisty, wypeni formularz aplikacji, zaakceptowa warunki korzystania z usugi i to bdzie
wszystko. Naley mie przygotowany pod rk numer karty kredytowej. Informacje o karcie kredytowej s wprowadzane w celu uiszczenia zwrotu zapaty, w przypadku gdy na koncie Google
Checkout nie ma wystarczajcej iloci rodkw. Moemy take wprowadzi dane konta bankowego, dziki czemu zyski ze sprzeday aplikacji bd przenoszone na to konto. Zwrmy uwag,
e usuga Google Checkout obsuguje nie tylko sklep Android Market. Nie powinnimy wic si
zdziwi, jeli pojawi si informacja o opacie transakcyjnej za sprzeda pochodzc spoza sklepu
Android Market. Wspomniana opata transakcyjna wynoszca 30% ceny aplikacji jest obliczona dla sklepu Android Market. Istniej rwnie dodatkowe opaty transakcyjne dla sprzeday
przeprowadzanych poza t witryn, niezalene od wspomnianej powyej opaty.

Rozdzia 28 Wdraanie aplikacji na rynek Android Market i nie tylko

1011

Prawdopodobnie najczciej wykorzystywanymi funkcjami konsoli programisty bd umieszczanie i monitorowanie aplikacji. W dalszej czci rozdziau zajmiemy si procesem publikowania aplikacji w sklepie. W kwestii monitoringu otrzymujemy narzdzia pozwalajce obserwowa cakowit liczb pobra aplikacji oraz liczb uytkownikw, ktrzy zainstalowali aplikacj.
Widoczna jest oglna ocena programu w zakresie od 0 do 5 gwiazdek, a take liczba osb,
ktre wystawiy ocen. Z poziomu konsoli programisty moemy ponownie opublikowa aplikacj na przykad jej aktualizacj lub wycofa j ze sklepu. Ta ostatnia czynno nie
usuwa aplikacji z urzdze, a nawet nie musi jej usuwa z serwerw Google, dotyczy to zwaszcza patnych aplikacji. Uytkownik, ktry zapaci za aplikacj, a nastpnie j odinstalowa, lecz
nie zada zwrotu kosztw, ma prawo do jej ponownego zainstalowania, nawet jeli zostaa
ona wycofana z obiegu. Program przestaje by naprawd dostpny dla uytkownikw jedynie
w przypadku zamania zasad firmy Google. W marcu 2011 roku firma Google dodaa w konsoli
programisty zestawienia i wykresy pozwalajce na obserwowanie statystyk aplikacji w zalenoci
od wersji systemu operacyjnego, rodzaju urzdzenia, a take krajw i jzykw.
Oprcz oceniania aplikacji uytkownicy mog rwnie pozostawia komentarze. Dla wasnego
dobra powinnimy jak najczciej czyta komentarze dotyczce naszej aplikacji, aby mc szybko
rozwizywa zauwaone problemy. Wraz z komentarzem dostpne s ocena aplikacji, nazwa
uytkownika oraz data umieszczenia komentarza. Niestety, nie moemy bezporednio odpowiada na komentarze ani nawet umieszcza komentarzy pod komentarzami uytkownikw.
W ekstremalnych przypadkach, gdy tre komentarza jest wyjtkowo szkodliwa lub niecenzuralna,
moemy zgosi to obsudze firmy Google, dostpnej pod adresem http://market.android.com/
support/.
Moemy rwnie przeglda komunikaty o bdach wygenerowane przez aplikacj oraz sprawdza, w ktrych momentach ulegaa zawieszeniu lub zostawaa niespodziewanie zamknita.
Na rysunku 28.2 widzimy ekran raportw o bdach aplikacji.

Rysunek 28.2. Ekran Application Error Reports

Przegldajc szczegy raportu, moemy dotrze do ladu stosu, w ktrym nastpia awaria, jak
rwnie uzyska takie informacje, jak rodzaj urzdzenia, na ktrym dziaaa aplikacja, oraz data
awarii. Podobnie jednak jak ma to miejsce w przypadku komentarzy, nie moemy skomunikowa si z uytkownikiem, ktrego aplikacja ulega awarii, aby pozna dalsze szczegy lub

1012 Android 3. Tworzenie aplikacji


pomc mu wyj z opresji. Pozostaje nam nadzieja, e tacy uytkownicy napisz na nasz adres
e-mail lub zostawi informacj na stronie aplikacji. W przeciwnym wypadku jestemy zdani na
siebie i musimy samodzielnie wykry przyczyn problemu oraz sprbowa j naprawi.
Dostpna jest jeszcze jedna funkcja konsoli programisty, ktra moe si okaza przydatna
odnonik do materiaw pomocniczych. Przycisk Help znajduje si w prawym grnym rogu
ekranu. Kliknicie go spowoduje otwarcie witryny pomocy, zawierajcej porzdn dokumentacj dotyczc serwisu Android Market, a take forum, na ktrym moemy poszuka odpowiedzi na drczce nas pytania i umieszcza wasne porady. To na forum znajdziemy informacje
dotyczce najnowszych zasad zwrotu pienidzy, problemw i zaale. Jeeli forum nie okae si
przydatne, znajdziemy tu odnonik do pomocy technicznej (Contacting Support), za pomoc
ktrego moemy wysa wiadomo wprost do przedstawicieli firmy Google.
Omwilimy niektre z przydatnych funkcji konsoli programisty, jednak Czytelnik z pewnoci nie moe si doczeka najprzydatniejszej czci rozdziau omwienia procesu umieszczania aplikacji w serwisie Android Market. Dziki temu procesowi uytkownicy bd mogli
znale i pobra aplikacj. Zanim jednak przejdziemy do tego etapu, musimy powiedzie, w jaki
sposb odpowiednio przygotowa nasz program do pobierania i sprzeday.

Przygotowanie aplikacji do sprzeday


Po utworzeniu kodu aplikacji, ale jeszcze przed jej umieszczeniem w sklepie Android Market,
naley przeprowadzi kilka czynnoci przygotowawczych. Powicimy im teraz troch uwagi
i omwimy kolejne czynnoci, ktre trzeba wykona.

Testowanie dziaania na rnych urzdzeniach


Bardzo istotne jest, aby przy lawinowej produkcji coraz to nowych urzdze pracujcych pod
kontrol systemu Android, z ktrych kade zawiera potencjalnie odmienn konfiguracj
sprztow, utworzona aplikacja zostaa przetestowana pod ktem dziaania na docelowych telefonach. W idealnym przypadku programista ma dostp do kadego rodzaju telefonu, ktry
chce przetestowa. Jest to do kosztowna propozycja. Innym dobrym rozwizaniem jest wykorzystanie urzdze AVD dla kadego rodzaju telefonu poprzez utworzenie odpowiedniej
konfiguracji sprztowej, a nastpnie uruchomienie i przetestowanie takiego urzdzenia na
emulatorze. Niektrzy producenci urzdze udostpniaj pakiety Androida specyficzne dla
danego telefonu, warto zatem przeglda witryny sieciowe danej firmy. Zestaw Android SDK
posiada klas Instrumentation oraz program UI/Application Exerciser Monkey, usprawniajce proces testowania. Wymienione narzdzia umoliwiaj automatyzacj testowania, nie
musimy wic marnowa czasu na powtarzanie tych samych czynnoci. Przed rozpoczciem testowania powinnimy usun zbdne artefakty z kodu oraz z folderu /res. Chcemy przecie,
aby aplikacja bya jak najmniejsza oraz jak najszybsza przy minimalnym zuyciu pamici.

Obsuga rnych rozmiarw ekranu


W momencie wydania rodowiska Android SDK 1.6 programici zaczli si boryka z nowymi
rozmiarami wywietlaczy. Aby uruchomi aplikacj na nowym, mniejszym ekranie, naley ustanowi swoisty element <supports-screens> jako element potomny wza <manifest> w pliku
AndroidManifest.xml. Bez wprowadzenia tego znacznika definiujcego obsug maych ekranw przez aplikacj nie bdzie ona dostpna w sklepie Android Market dla urzdze posiadaj-

Rozdzia 28 Wdraanie aplikacji na rynek Android Market i nie tylko

1013

cych niewielkie wywietlacze. Oznacza to oczywicie, e aplikacja musi zosta skompilowana


wobec rodowiska Android SDK co najmniej w wersji 1.6. Jeli chcemy, aby program dziaa
w urzdzeniach obsugujcych starsze wersje zestawu Android SDK, musimy si upewni, e nie
bdzie korzysta z adnego interfejsu API wprowadzonego w wersji 1.6 lub nowszej oprogramowania. Naley nastpnie przetestowa aplikacj zarwno na urzdzeniach AVD imitujcych
starsze telefony, jak i reprezentujcych nowsze urzdzenia. W celu obsugi rnych rozmiarw
ekranu prawdopodobnie bdziemy musieli utworzy alternatywne pliki zasobw w podkatalogu
/res. Na przykad w przypadku dodatkowej obsugi niewielkich wywietlaczy oprcz plikw znajdujcych si w katalogu /res/layout trzeba bdzie umieci odpowiedniki tych plikw w katalogu /res/layout-small. Nie oznacza to, e musimy tworzy rwnie odpowiedniki tych plikw
w katalogach /res/layout-large i /res/layout-normal, gdy jeli Android nie znajdzie takiego specyficznego katalogu, jak na przykad /res/layout-large, wykorzysta zasoby dostpne w katalogu
/res/layout. Pamitajmy rwnie, e moemy tworzy kombinacje kwalifikatorw dla plikw
zasobw na przykad katalog /res/layout-small-land moe zawiera ukady graficzne dla
maych ekranw zorientowanych w trybie poziomym. Omwilimy to zagadnienie w rozdziale
6. Obsuga maych wywietlaczy oznacza prawdopodobnie rwnie utworzenie alternatywnych
wersji obiektw rysowanych, takich jak ikony. W przypadku tych obiektw moe rwnie zaistnie potrzeba utworzenia alternatywnych katalogw zasobw, odpowiadajcych rozdzielczoci
ekranu oraz jego rozmiarowi.
Oczywicie, pod wzgldem rozmiarw ekranu tablety podaj w przeciwnym kierunku i w ich
przypadku stosujemy warto xlarge. Ten sam znacznik <supports-screens> suy do definiowania aplikacji uruchamianej na bardzo duym ekranie, natomiast wprowadzanym atrybutem jest android:xlargeScreens. W niektrych przypadkach moemy mie do czynienia
z aplikacj obsugiwan wycznie przez tablety, wtedy naley przypisa warto false wszystkim
pozostaym rozmiarom ekranu.

Przygotowanie pliku AndroidManifest.xml


do umieszczenia w sklepie Android Market
Plik AndroidManifest.xml prawdopodobnie powinien zosta troszeczk zmodyfikowany przed
umieszczeniem go w sklepie Android Market. Domylnie narzdzia ADT rodowiska Eclipse
wstawiaj atrybut android:icon do znacznika <application>, a nie do znacznikw <activity>.
Jeeli istnieje moliwo uruchomienia wikszej liczby aktywnoci, powinnimy dla kadej
z nich ustanowi oddzieln ikon, aby uytkownik mg je atwiej rozrnia. Cigle jednak
musi by okrelona jedna ikona w znaczniku <application>, ktra posuy jako domylna
ikona dla wszystkich aktywnoci nieposiadajcych wasnej ikony. Aplikacja posiadajca atrybut
android:icon wycznie wewntrz wza <activity> bdzie bezproblemowo dziaaa w urzdzeniach oraz na emulatorze, jednak podczas umieszczania aplikacji na serwerze usuga Android
Market przeszukuje znacznik <application> pod ktem informacji o ikonie. Przesyanie aplikacji zostaje przerwane rwnie w przypadku, gdy nazwa zastosowanego pakietu rozpoczyna si od
cigw znakowych com.google, com.android, android lub com.example, mamy jednak nadziej, e nie zostay one zastosowane w aplikacjach Czytelnikw.
Istnieje rwnie wiele innych kwestii zwizanych z kompatybilnoci, ktre naley wzi pod
uwag podczas testowania aplikacji na rnych konfiguracjach sprztowych. Niektre urzdzenia posiadaj aparat fotograficzny, inne nie maj klawiatury fizycznej, jeszcze inne maj
manipulator kulkowy zamiast klawiszy nawigacyjnych. W razie potrzeby zastosujmy znaczniki
<uses-configuration> i <uses-feature> w pliku AndroidManifest.xml do zdefiniowania

1014 Android 3. Tworzenie aplikacji


wymaga sprztowych i programowych naszej aplikacji. Android Market wymusi te wymagania
i uniemoliwi wywietlanie naszej aplikacji urzdzeniom nieposiadajcym odpowiedniej konfiguracji. Zwrmy uwag, e mamy tu do czynienia z innymi znacznikami ni <uses-permission>
pliku AndroidManifest.xml. Chocia urzdzenie uytkownika moe by wyposaone w aparat
fotograficzny, nie jest wcale powiedziane, e uytkownik zechce przydzieli naszej aplikacji dostp do tego aparatu. Jednoczenie zadeklarowanie uprawnienia aplikacji do korzystania z aparatu wcale nie musi oznacza, e obecno tej funkcji jest wymagana przez aplikacj. W wikszoci
przypadkw bdziemy umieszcza obydwa znaczniki w pliku AndroidManifest.xml, aby okreli wymg obecnoci aparatu fotograficznego oraz uprawnienie korzystania z aparatu w razie
potrzeby. Jednak nie wszystkie funkcje wymagaj uprawnie, wic w naszym najlepszym interesie ley zdefiniowanie opcji potrzebnych naszej aplikacji.
Istnieje jeszcze jedna zasadnicza rnica pomidzy wzami <uses-permission> a <usesfeature>. Za pomoc tego drugiego znacznika moemy okrela, czy dana funkcja jest konieczna do dziaania aplikacji lub czy nasz program moe si bez niej oby. To znaczy, e
mamy do dyspozycji atrybut android:required, ktremu moemy przypisa warto true
lub false (domylna jest warto true). Na przykad program moe wykorzystywa funkcje
sieci Bluetooth, jeeli jest dostpna, bdzie jednak dziaa rwnie dobrze bez nich. Zatem
moemy wstawi w pliku manifecie nastpujcy wiersz:
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />

Wewntrz kodu aplikacji powinnimy wywoa klas PackageManager w celu okrelenia, czy
technologia Bluetooth jest dostpna, czego moemy dokona za pomoc poniszego fragmentu:
boolean hasBluetooth = getPackageManager().hasSystemFeature(
PackageManager.FEATURE_BLUETOOTH);

Nastpnie moemy wykona odpowiedni czynno, w zalenoci od obecnoci funkcji Bluetooth. Dokumentacja Androida w tym miejscu jest do niejednoznaczna. Jeeli zajrzymy do informacji o znaczniku <uses-feature> w konsoli programisty, nie znajdziemy tak wielu funkcji
jak w dokumentacji klasy PackageManager, w ktrej zostay zdefiniowane stae FEATURE_*
dla kadej dostpnej funkcji.
Znacznik <uses-configuration> jest nieco inny. Definiuje on rodzaje wymaganych elementw urzdzenia, takich jak rodzaj klawiatury, ekran dotykowy czy mechanizm sterowania.
Jednak w przeciwiestwie do wza <uses-feature> nie wprowadzamy tutaj niezalenych
wyborw, lecz tworzymy kombinacje konfiguracji wymaganych przez nasz aplikacj. Jeeli na
przykad nasz program wymaga obecnoci kontrolera ruchu w postaci podkadki kierunkowej
lub manipulatora kulkowego oraz ekranu dotykowego (wykorzystujcego rysik lub palec),
moemy zdefiniowa dwa nastpujce wiersze:
<uses-configuration android:reqFiveWayNav="true" android:reqTouchScreen="stylus" />
<uses-configuration android:reqFiveWayNav="true" android:reqTouchScreen="finger" />

Lokalizacja aplikacji
Jeli aplikacja bdzie wykorzystywana w innych krajach, moemy rozway proces jej lokalizacji.
Z technicznego punktu widzenia jest to wzgldnie prosta czynno. Znalezienie osoby odpowiedzialnej za przeprowadzenie tego procesu to zupenie inna sprawa. Z technicznego punktu
widzenia tworzymy po prostu kolejny folder w katalogu /res na przykad /res/values-fr przechowujcy francusk wersj pliku strings.xml. Nastpnie bierzemy plik strings.xml, tumaczymy
wartoci typu string na nowy jzyk i zachowujemy tak zmodyfikowany plik w nowym folderze

Rozdzia 28 Wdraanie aplikacji na rynek Android Market i nie tylko

1015

zasobw, nie zmieniajc jednoczenie nazwy tego pliku. W przypadku innych rodzajw zasobw na przykad obiektw rysowanych lub menu stosujemy dokadnie tak sam technik.
Obrazy i kolory mog lepiej spenia swoje zadanie, jeli bd przystosowane do odmiennych
krajw i kultur. Z tego wanie powodu nie warto stosowa prawdziwych nazw zasobw kolorw.
W internetowej dokumentacji dotyczcej kolorw czsto mona natrafi na taki zapis:
<color name="solid_red">#f00</color>

Oznacza on, e w takim kodzie lub innym pliku zasobw odnosimy si do koloru za pomoc
jego rzeczywistej nazwy, w naszym przypadku jest to solid_red. Aby dostosowa kolor do innego
kraju lub kultury, najlepiej stosowa nazwy kolorw typu accent_color1 lub alert_color.
W Wielkiej Brytanii odpowiedniejszy moe by kolor czerwony, podczas gdy w Hiszpanii swoje
zadanie bdzie lepiej spenia jaki odcie ci. Poniewa nazwa alert_color nie okrela zastosowanego koloru, jego zmiana nie jest ju tak bardzo dezorientujca. Jednoczenie moemy
zaprojektowa przyjemny schemat kolorystyczny, zawierajcy barwy bazowe i ich odcienie,
majc jednoczenie pewno, e waciwe kolory zostay uyte we waciwych miejscach.
W niektrych krajach opcje menu powinny zosta zmienione poprzez dodanie lub usunicie
pewnych elementw, ewentualnie mona wprowadzi inn organizacj menu, w zalenoci od
miejsca stosowania aplikacji. Pliki menu s zazwyczaj przechowywane w katalogu /res/menu.
Jeeli natrafimy na tak sytuacj, prawdopodobnie bdzie lepiej, jeli umiecimy wszystkie tekstowe cigi znakw w pliku strings.xml lub innym pliku przechowywanym w podkatalogu
/res/values, a nastpnie bdziemy wszdzie odnosi si do tych zasobw za pomoc ich identyfikatorw. Zmniejszymy w ten sposb znacznie niebezpieczestwo pominicia tumaczenia
tekstu w jakim zapomnianym pliku zasobw. Tumaczenie jest wtedy zatem ograniczone do
plikw znajdujcych si w podkatalogu /res/values.

Przygotowanie ikony aplikacji


Osoby kupujce oraz uytkownicy bd wyranie widzie ikon oraz etykiet aplikacji zarwno
w sklepie Android Market, jak i po jej pobraniu w urzdzeniu. Powinnimy powici
szczegln uwag utworzeniu jak najlepszej ikony i etykiety swojej aplikacji oraz jej aktywnoci.
W razie potrzeby moemy je rwnie zlokalizowa. Nie zapominajmy rwnie o ewentualnym dostosowaniu rozmiarw ikon do rozmiarw ekranu. Przygldajmy si dzieom innych
wydawcw, szczeglnie za aplikacjom nalecym do tej samej kategorii co nasza. Chcemy, aby
nasza aplikacja bya widoczna, musimy wic unika wtapiania si w tum. Jednoczenie musimy pamita, e ikona i etykieta aplikacji musz harmonizowa z ikonami innych aplikacji
zainstalowanych w urzdzeniu uytkownika. Uytkownik nie moe zastanawia si nad
przeznaczeniem aplikacji, ktrej ikona wskazuje zupenie inn funkcj.
Podczas tworzenia dowolnego obrazu wykorzystywanego w aplikacji, zwaszcza jej ikony, musimy bra pod uwag gsto ekranu w docelowym urzdzeniu. Gsto jest definiowana jako
liczba pikseli na cal. May ekran zazwyczaj oznacza rwnie ma gsto, wobec czego mniej
pikseli skada si na jednostk odlegoci, podczas gdy wiksze wywietlacze czsto posiadaj
du gsto. W przypadku ekranu posiadajcego niewielk gsto odpowiedni rozmiar ikony
powinien skada si z mniejszej liczby pikseli, najczciej o wymiarach 3636. W przypadku
wywietlacza o wikszej gstoci najprawdopodobniej utworzymy ikon o wymiarach 7272.
Ikona dla ekranu o redniej gstoci przybiera wartoci 4848, a przy bardzo duych gstociach moe posiada rozmiary 9696.

1016 Android 3. Tworzenie aplikacji

Problemy zwizane z zarabianiem pienidzy na aplikacjach


Podczas ustalania ceny za aplikacj trzeba rozway pewne kwestie. Czy utworzy dwie wersje tej
samej aplikacji darmow i patn wymagajce oddzielnej obsugi i zarzdzania nimi? A moe
korzystamy ze wsplnego kodu bazowego i stosujemy jak technik, dziki ktrej wiadomo, e
zostaa uiszczona opata za program? Bez wzgldu na zastosowane rozwizanie, w jaki sposb
chronimy aplikacj przed jej kopiowaniem i instalowaniem na innych urzdzeniach przez
nieupowanione do tego osoby? Z powodu ograniczonych zabezpiecze stosowanych w telefonach oraz zdolnoci pewnych osb do omijania tych rodkw ostronoci zarzdzanie niezawodnymi technologiami ochrony przed nieuprawnionym kopiowaniem jest niezwykle trudne.
Jednym z rozwiza utrzymywania pojedynczego kodu bazowego, pozwalajcego na umieszczenie oddzielnych trybw (bezpatnego i patnego), jest wykorzystanie moliwoci klasy
PackageManager:
this.getPackageManager().checkSignatures(mainAppPkg, keyPkg)

Metoda ta porwnuje sygnatury dwch pakietw oraz przekazuje warto PackageManager.


jeeli obydwa pakiety istniej i s identyczne. Nazwy pakietw musz si
rni dla kadej aplikacji wspistniejcej w sklepie Android Market, ale to nic nie szkodzi.
W naszym kodzie, jeeli chcemy zadecydowa o udostpnieniu wszystkich funkcji aplikacji,
moemy wywoa t metod i wprowadzi nazw pakietu aplikacji gwnej lub aplikacji odblokowujcej. Ten drugi program ustanawiamy nastpnie jako patny. Jeeli uytkownik zakupi
aplikacj odblokowujc i pobierze j na urzdzenie, gwna aplikacja otrzyma dopasowanie
sygnatury i odblokuje dodatkowe funkcje. Nieco mniej przyjemnym rozwizaniem wykorzystujcym pojedynczy kod bazowy jest wprowadzenie systemw oznaczajcych kolejne wersje kodu
rdowego, ktre skonfiguruj odpowiednie wspdzielenie wsplnych elementw, oraz
stworzenie skryptw tworzcych darmowe i patne wersje aplikacji.

SIGNATURE_MATCH,

Kolejnym rozwizaniem umoliwiajcym zarabianie na aplikacjach jest wprowadzenie wewntrznych reklam. Istnieje mnstwo okazji, aby doczy reklamy do aplikacji. Dwoma powszechnie wystpujcymi moliwociami s AdMob i AdSense. Proces polega przede wszystkim
na wstawieniu ich pakietw SDK do naszej aplikacji, okreleniu odpowiedniego miejsca i czasu,
w ktrym bd wywietlane reklamy i dodaniu uprawnienia INTERNET do programu (dziki
czemu wywietlane reklamy bd pobierane z internetu). Na koniec bdziemy otrzymywa
pienidze za kade kliknicie reklamy. Sama aplikacja moe by bezpatna, dziki czemu atwiej
bdzie j umieci w serwisie Android Market, a do tego nie musimy si tak bardzo przejmowa
kwesti piractwa. Wielu programistw przyznaje, e zarabia w ten sposb przyzwoite pienidze.
Kolejn now funkcj jest wprowadzona w lutym 2011 roku waluta klienta (ang. Buyers Currency).
Przedtem klienci uytkownicy musieli paci w walucie sprzedawcy, co mogo stanowi
problem dla osb majcych kopoty z wymian waluty sprzedawcy na swoj wasn. Oznaczao
to rwnie, e sprzedawca mg tak naprawd ustali tylko jedn cen dla uytkownikw na
caym wiecie. Teraz, gdy sprzedawca moe ustali cen dla danego kraju, moe j nie tylko
podnosi lub obnia w zalenoci od rejonu geograficznego, lecz take komfort uytkownika
staje si wyranie wikszy.

Kierowanie uytkownikw z powrotem do sklepu


W systemie Android wprowadzono nowy schemat identyfikatorw URI, uatwiajcy wyszukiwanie aplikacji w sklepie Android Market market://. Jeli na przykad chcemy skierowa
uytkownikw do sklepu w celu znalezienia potrzebnego skadnika lub dokupienia dodatkowej

Rozdzia 28 Wdraanie aplikacji na rynek Android Market i nie tylko

1017

aplikacji odblokowujcej nowe funkcje naszej aplikacji, powinnimy wprowadzi nastpujcy


kod, w ktrym w miejsce MY_PACKAGE_NAME wprowadzamy nazw naszego pakietu:
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse("market://search?q=pname:MY_PACKAGE_NAME"));
startActivity(intent);

Za pomoc tego kodu zostanie uruchomiona aplikacja Market, ktra wywietli uytkownikowi
nazw tego pakietu. Uytkownik moe wtedy pobra lub zakupi aplikacj. Zwrmy uwag, e powyszy schemat nie dziaa w standardowej przegldarce WWW. Moemy wyszukiwa za pomoc nazwy pakietu (pname), a take szuka wydawcy za pomoc wyraenia
market://search?q=pub:\"Fname Lname\" lub przy uyciu dowolnego pola publicznego
sklepu Android Market (nazwa aplikacji, nazwa wydawcy oraz opis aplikacji), wpisujc market://
search?q=<querystring>.
Jeeli Czytelnik poczy zdobyt wiedz z technikami omwionymi w poprzednim podrozdziale, powinien umie napisa kod, ktry bdzie prbowa odnale pakiet odblokowujcy
w danym urzdzeniu. Jeli pakiet nie zostanie znaleziony, moemy wywietli monit i zapyta
uytkownika, czy nie chce go pobra z internetu. W przypadku odpowiedzi twierdzcej aplikacja wywoa intencj otwierajc usug Android Market i automatycznie uruchamiajc stron,
na ktrej bdzie mona pobra lub zakupi aplikacj odblokowujc.

Usuga licencyjna systemu Android


Architektura aplikacji tworzonych dla systemu Android powoduje, niestety, e stanowi one
atwy cel dla piratw. Istnieje moliwo kopiowania tych aplikacji oraz rozsyania tych kopii
do innych urzdze. W jaki sposb moemy wic sprawi, aby uytkownicy, ktrzy nie zakupili
naszej aplikacji, nie mogli jej uruchomi? Nasze wymagania spenia utworzona przez firm Google
biblioteka LVL (ang. License Verification Library biblioteka weryfikujca licencje).
Jeeli dana aplikacja zostaa pobrana za pomoc usugi Android Market, musi istnie kopia
aplikacji Android Market w tym urzdzeniu. Dodatkowo aplikacja ta musiaa wprowadzi
uprawnienia pozwalajce na odczytywanie wartoci przechowywanych w urzdzeniu, takich
jak nazwa konta Google uytkownika, numer IMSI oraz inne informacje. Poczwszy od wersji
1.5 Androida, aplikacja Android Market zostaa zmodyfikowana w taki sposb, e reaguje na
dania weryfikacji licencji pochodzce od aplikacji. W tym celu wywoujemy bibliotek LVL
z poziomu aplikacji, biblioteka ta nawizuje czno z aplikacj Android Market, z kolei
program Android Market czy si z serwerami firmy Google i nasza aplikacja otrzymuje
odpowied wskazujc, czy uytkownik urzdzenia posiada licencj na korzystanie z naszego
kodu. Posiadamy kontrol nad ustawieniami definiujcymi zachowanie aplikacji w przypadku
braku dostpu do sieci. Peny opis implementacji biblioteki LVL znajdziemy pod adresem
http://developer.android.com/guide/publishing/licensing.html.
Powinnimy mie jednak wiadomo, e mechanizm LVL jest naraony na ataki hakerw.
Jeeli kto wie, gdzie znale warto przekazywan w wyniku wywoania biblioteki LVL, i jeli
uzyska dostp do naszego pliku .apk, moe rozoy aplikacj na czynniki pierwsze i odpowiednio j zmodyfikowa. Jeeli zastosujemy oczywiste rozwizanie polegajce na wykorzystaniu
instrukcji switch po otrzymaniu odpowiedzi z mechanizmu LVL, ktre suy do okrelenia zachowania aplikacji w zalenoci od otrzymanej wartoci, haker moe po prostu wymusi przekazanie tej podanej wartoci i w tym momencie zabezpieczenia takiej aplikacji przestaj
istnie. Z tego wanie powodu twrcy Androida zalecaj jak najwiksze zagmatwanie kodu,

1018 Android 3. Tworzenie aplikacji


aby ukry logik odpowiedzialn za sprawdzanie wartoci zwracanej przez bibliotek LVL.
Moemy sobie wyobrazi, e jest to do skomplikowana procedura.
Wraz z wersj 2.3 Androida firma Google wprowadzia pewn form obsugi takiego gmatwania kodu w postaci funkcji ProGuard. Jeeli ustalimy docelow wersj systemu na co najmniej
2.3, nasza aplikacja automatycznie otrzyma plik proguard.cfg. Poprzez skonfigurowanie funkcji
ProGuard w tym pliku moemy zmusi narzdzia ADT do gmatwania kodu w momencie
kompilowania wersji rynkowej pliku .apk. Jeeli wolimy tworzy aplikacje za pomoc narzdzia
ant, moemy je rwnie skonfigurowa w taki sposb, aby zostaa w nim wykorzystana funkcja
ProGuard do gmatwania kodu. Aby wczy t moliwo, musimy wprowadzi waciwo
proguard.config w pliku default.properties, ktry umieszczamy w tej samej lokacji co plik
proguard.cfg. W trakcie przeprowadzania procesu gmatwania funkcja ProGuard wygeneruje plik
mapping.txt wraz z plikiem .apk. Musimy pozostawi ten plik, gdy jest on niezbdny do
odwrcenia procesu gmatwania stosu w aplikacji.

Przygotowanie pliku .apk do wysania


Aby przygotowa swoj aplikacj do wysania to znaczy utworzy w tym celu plik .apk trzeba
postpi zgodnie z poniszym algorytmem (zosta on szczegowo omwiony w rozdziale 10.):
1. Jeli jeszcze tego nie zrobie, utwrz certyfikat produktu, za pomoc ktrego
podpiszesz swoj aplikacj.
2. Jeeli aplikacja wykorzystuje mapy, zamie klucz API MAP w pliku AndroidManifest.xml
na klucz API MAP produktu. Jeli tego nie zrobisz, uytkownicy nie bd widzieli map.
3. Eksportuj aplikacj poprzez kliknicie prawym przyciskiem myszy nazwy projektu
w oknie Package explorer rodowiska 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. bdzie trzeba wprowadzi nazw pliku wyjciowego, ktra bdzie stanowi
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 pamici, dziki czemu uzyskasz lepsz wydajno po uruchomieniu
aplikacji. To wanie teraz wprowadza si ostateczn nazw pliku .apk naszej aplikacji.
6. Obecnie w rodowisku Eclipse moesz wykorzysta opcj Export Signed Application
Package, wykorzystujc kreator do przeprowadzenia etapw 3., 4. i 5.

Wysyanie aplikacji
Wysyanie aplikacji jest prostym procesem, wymagajcym jednak nieco przygotowa. Przed
rozpoczciem wysyania musimy ustanowi kilka parametrw oraz podj pewne decyzje. Niniejszy podrozdzia zosta powicony omwieniu tych przygotowa i decyzji. Gdy ju wszystko
bdzie gotowe, przejdziemy do konsoli programisty i wybierzemy opcj Upload Application.
Zostaniemy poproszeni o wprowadzenie wielu informacji dotyczcych naszej aplikacji, usuga
Market przebada aplikacj oraz dostarczone dane i w kocu nasz program zostanie przygotowany do publikacji w sklepie Android Market.

Rozdzia 28 Wdraanie aplikacji na rynek Android Market i nie tylko

1019

W poprzednim podrozdziale omwilimy proces przygotowania pliku .apk do wysania. Przycignicie uwagi klientw wymaga z naszej strony szczypty marketingu. Musimy stworzy dobry
opis aplikacji oraz jej przeznaczenia, konieczne te bdzie wykonanie zrzutw ekranu, aby uytkownicy wiedzieli, e nie kupuj kota w worku.
Jednym z pierwszych elementw, o jakie zostaniemy poproszeni podczas wysyania aplikacji,
s zrzuty ekranu. Najprostszym sposobem ich uzyskania jest zastosowanie narzdzia DDMS.
Uruchamiamy rodowisko Eclipse, nastpnie wczamy aplikacj na emulatorze lub w rzeczywistym urzdzeniu i przeczamy perspektyw na widoki DDMS i Device. Wewntrz widoku
Device wybieramy urzdzenie, na ktrym uruchomilimy aplikacj, i klikamy przycisk Screen
Capture (symbolizuje go ikona maego obrazu umieszczona w prawym grnym rogu ekranu)
lub wybieramy go z menu View. Jeeli pojawi si taka moliwo, wybierzmy 24-bitowy kolor.
Android Market przekonwertuje zrzuty ekranu na skompresowane pliki JPEG; pocztkowa
warto 24 bitw przyniesie lepsze rezultaty ni pocztkowa warto 8 bitw. Wybierzmy takie
zrzuty, ktre przy ukazywaniu oryginalnoci aplikacji prezentuj jednoczenie jej funkcje. Musimy
wprowadzi co najmniej dwa zrzuty ekranu, maksymalnie za moemy zamieci ich osiem.
Nastpn kwesti jest wygenerowana ikona aplikacji w wysokiej rozdzielczoci. Moemy w tym
celu wykorzysta projekt tej samej ikony, ktra zostanie zastosowana w urzdzeniu, ale musi
ona posiada wymiary 512512 pikseli. Jest to jeden z wymogw usugi Android Market.
Moemy umieci rwnie grafik promujc, jej rozmiar bdzie jednak mniejszy od zrzutu
ekranu. Chocia taka grafika stanowi jedynie dodatek, warto j rwnie opublikowa. Nigdy nie
wiadomo, kiedy zostanie wywietlona; bez niej nie bdziemy pewni, co (jeeli cokolwiek) pojawi si na jej miejscu. Jednym z miejsc, w ktrym pojawia si grafika promocyjna, jest grna cz
ekranu wywietlajcego szczegy aplikacji w sklepie Android Market.
Kolejnym opcjonalnym rodzajem obrazu jest grafika wymieniajca cechy aplikacji, o rozmiarze
1024500 pikseli. Grafika ta bdzie wykorzystywana w sekcji Polecane serwisu Android Market,
wic powinnimy si naprawd postara, aby ten obraz wyglda jak najlepiej.
Ostatnim elementem graficznym zwizanym z nasz aplikacj jest opcjonalny plik wideo, ktry
moemy zamieci w serwisie YouTube. Adres do tego pliku moemy umieci w opisie aplikacji.
Android Market poprosi nastpnie o informacj tekstow dotyczc aplikacji, ktra bdzie widoczna dla klientw, wcznie z tytuem, opisem oraz tekstem promujcym. Moliwo wprowadzenia tekstu promujcego stanie si dostpna jedynie po umieszczeniu grafiki promujcej.
Moemy opublikowa tekst w wielu jzykach, gdy nasza aplikacja bdzie dostpna na caym
wiecie. Grafika promujca moe by umieszczona w sklepie Android Market wycznie jednorazowo, zatem jeli zrzuty ekranu wygldaj inaczej w rnych ustawieniach regionalnych,
powinnimy rozway umieszczenie ich w innym miejscu dostpnym dla klientw, na przykad
na stronie domowej. Takie podejcie moe w przyszoci ulec zmianie.
Jeli napisalimy wasn wersj umowy EULA, powinnimy w opisie zamieci odnonik do niej,
dziki czemu uytkownicy zapoznaj si z jej treci przed pobraniem aplikacji. Naley wzi pod
uwag, e klienci prawdopodobnie bd chcieli wykorzysta funkcj wyszukiwania aplikacji,
zatem najlepiej byoby umieci w tekcie odpowiednie sowa kluczowe, dziki czemu znaczco
wzronie prawdopodobiestwo natrafienia na nasz aplikacj przez osoby szukajce zapewnianych przez ni funkcji. W kocu warto rwnie umieci krtki komentarz wraz z adresem
e-mail na wypadek pojawienia si problemw z aplikacj. Bez tej prostej zachty uytkownicy
mog czciej wystawia negatywne opinie, a taka negatywna opinia naprawd ogranicza moliwo rozwizania problemu w porwnaniu do wymiany informacji z uytkownikiem, ktry natrafi na jaki bd.

1020 Android 3. Tworzenie aplikacji


Jedn z wad omwionego wczeniej mechanizmu wsparcia technicznego jest brak rozrnienia
pomidzy wersjami aplikacji. Jeeli wersja 1. oprogramowania otrzymaa negatywne opinie,
a my wydalimy wersj 2. pozbawion wszystkich usterek poprzedniczki, recenzje dotyczce
wersji 1. nie znikaj, a klienci nie wiedz, ktrej wersji dotycz te opinie. Po wydaniu zaktualizowanej aplikacji jej ocena (liczba gwiazdek) rwnie nie zostaje wyzerowana. Czciowo z tego
powodu firma Google zacza implementowa pole tekstowe Najnowsze zmiany, gdzie moemy umieci list zmian wprowadzonych w nowej wersji aplikacji. To wanie tutaj moemy
stwierdzi, e jaki bd zosta naprawiony lub wymieni nowe funkcje.
Dostpne jest take oddzielne pole na tekst promujcy, pozwalajce na wstawienie 80 znakw.
Po wywietleniu naszej aplikacji na samej grze listy w sklepie Android Market tam wanie s
umieszczone grafika promujca oraz tekst promujcy. Wypenienie tych pl informacjami jest
naprawd dobrym pomysem.
Jednym z obowizkw wydawcy aplikacji jest ujawnienie w opisie tej aplikacji wymaganych
przez ni uprawnie. Mamy na myli te same uprawnienia, ktre s definiowane za pomoc
znacznikw <uses-permission> w pliku AndroidManifest.xml. Kiedy uytkownik pobiera
aplikacj na urzdzenie, system sprawdza plik AndroidManifest.xml i przed zakoczeniem
instalacji pyta uytkownika o wszystkie zamieszczone w nim uprawnienia. Rwnie dobrze
moemy umieci je w opisie aplikacji. W przeciwnym wypadku ryzykujemy otrzymanie negatywnych opinii od uytkownikw zaskoczonych faktem, e aplikacja wymaga uprawnie,
ktrych nie zamierzali jej przyzna. Nie wspominamy nawet o zwrotach kosztw, ktre zaniaj
ogln ocen dewelopera. Podobnie jak w przypadku uprawnie, jeli aplikacja wymaga okrelonego typu ekranu, aparatu fotograficznego lub jakiej innej funkcji urzdzenia, odpowiednie
informacje powinny zosta zamieszczone w opisie programu. Najlepszym rozwizaniem jest
nie tylko zamieszczenie wymaganych przez aplikacj uprawnie i funkcji, lecz take opis sposobu ich wykorzystania. Powinnimy z gry odpowiedzie na pytanie, dlaczego taka aplikacja
wymaga obecnoci funkcji X.
W trakcie wysyania aplikacji musimy wybra jej rodzaj i okreli kategori. Poniewa z upywem czasu wartoci te ulegaj zmianom, nie bdziemy ich wymienia, wystarczy przej do
ekranu Upload Application, eby ujrze dostpne moliwoci.
Nastpnym etapem jest ustalenie ceny aplikacji. Domylnie aplikacja jest darmowa, a eby to
zmieni, trzeba posiada skonfigurowane konto handlowe w usudze Google Checkout. Wybr
odpowiedniej ceny dla aplikacji wcale nie jest taki atwy, chyba e posiadamy wyjtkowo
rozwinity zmys marketingowca, a nawet wtedy nietrudno o pomyk. Zbyt wysokie opaty
mog zniechca uytkownikw, poza tym zwikszaj odczuwalne dla wydawcy skutki zwracania kosztw ludziom, ktrzy uznali aplikacj za niewart swojej ceny. Z kolei zbyt niskie ceny
mog rwnie zniechci ludzi, ktrzy uznaj, e taka aplikacja jest niskobudetowa.
Android Market posiada opcj wczenia ochrony przed kopiowaniem po wysaniu aplikacji.
Serwis ten automatycznie wyposay nasz aplikacj w ten mechanizm, powinnimy jednak
pamita, e rozwizanie to nieco bardziej obcia pami urzdzenia. Nie jest ono rwnie
niezawodne i nie gwarantuje cakowitego zabezpieczenia aplikacji. Poniewa funkcja ochrony
przez kopiowaniem znajduje si na etapie wycofywania, powinnimy wzi pod uwag alternatywne lub dodatkowe sposoby ochrony programu, na przykad opisan wczeniej usug licencyjn systemu Android.
Pod koniec 2010 roku firma Google wprowadzia schemat oceniania aplikacji. Jego zadaniem
jest okrelenie przedziau wiekowego grupy docelowej uytkownikw. Niestety, poowa grup
wiekowych dotyczy nastolatkw. Do wyboru mamy oceny: Dla wszystkich, Niska dojrzao,

Rozdzia 28 Wdraanie aplikacji na rynek Android Market i nie tylko

1021

rednia dojrzao i Wysoka dojrzao. Wybr odpowiedniej grupy wiekowej zaley od treci
aplikacji oraz od iloci tej treci. Firma Google zdefiniowaa zasady zwizane z pooeniem geograficznym oraz umieszczaniem lub publikowaniem lokacji. Najlepiej samemu przejrze te zasady, znajdziemy je pod adresem www.google.com/support/androidmarket/bin/answer.py?hl=
en&answer=188189.
Jedn z ostatnich decyzji, jakie naley podj, jest wybr regionw geograficznych oraz operatorw telefonii komrkowej, dla ktrych nasza aplikacja bdzie widoczna. Poprzez wybr opcji
All aplikacja bdzie dostpna na caym wiecie. Czasami jednak naley ograniczy dystrybucj
do danego regionu geograficznego lub operatora. W zalenoci od funkcji oferowanych przez aplikacj wymagane jest nieraz ograniczenie liczby krajw, w ktrych aplikacja bdzie dopuszczona do obrotu, ze wzgldu na konieczno przestrzegania prawa eksportowego Stanw Zjednoczonych. Ograniczenie dystrybucji aplikacji do okrelonych operatorw jest konieczne,
gdy wystpi problemy z kompatybilnoci z urzdzeniami lub niezgodno z zasadami danego operatora. Aby przejrze list dostpnych operatorw, klikamy w konsoli programisty nazw
wybranego kraju, dziki czemu zostanie wywietlony spis dostpnych operatorw w danym
pastwie. Zaznaczenie opcji All spowoduje rwnie, e dana aplikacja bdzie dostpna dla
wszelkich kolejnych pastw i operatorw, ktrym kiedy firma Google udostpni serwis Android Market w tym celu nie bd potrzebne adne dziaania programisty.
Chocia profil programisty zawiera informacje kontaktowe, moemy wprowadzi inne dane
podczas wysyania kadej aplikacji. Usuga Market monituje o wprowadzenie adresu strony
WWW, adresu e-mail oraz numeru telefonu, sucych jako informacje kontaktowe zwizane
z dan aplikacj. W celu umoliwienia obsugi klientw musimy wypeni przynajmniej jedno
z wymienionych pl, nie jest konieczne jednak wprowadzenie danych do wszystkich trzech
elementw. Podawanie tutaj prywatnego adresu e-mail nie jest najlepszym pomysem, tak samo jak nie chcielibymy najprawdopodobniej podawa tutaj prywatnego numeru telefonu. Gdy
bdziemy zarabia miliony dolarw na sprzeday naszej aplikacji, raczej zatrudnimy kogo odbierajcego telefony i odpowiadajcego na e-maile od uytkownikw. Jeeli od razu zaoymy
adres e-mail do celw obsugi pomocy technicznej, nie bdziemy mieli pniej problemu z rozdzieleniem poczty osobistej i wiadomoci od uytkownikw.
Po podjciu wszystkich omwionych decyzji musimy naszej aplikacji nada atest przestrzegania
polityki treci w usudze Android Market dla programistw (nie jest zbyt rygorystyczna),
a take drugi atest umoliwiajcy eksportowanie programu poza granice Stanw Zjednoczonych. Aplikacje udostpniane poprzez serwis Android Market podlegaj prawu eksportowemu Stanw Zjednoczonych, poniewa serwery Google s umieszczone w tym kraju. Dotyczy to nawet aplikacji utworzonych w innym pastwie oraz sytuacji, kiedy zarwno programista,
jak i jego uytkownicy znajduj si poza USA. Nie zapominajmy, e zawsze moemy dystrybuowa aplikacj innymi kanaami. Gdy ju wprowadzimy wszystkie niezbdne informacje i wylemy zdjcie, moemy w kocu wcisn przycisk Save. Nasza aplikacja zostanie wtedy przygotowana do wysania w wiat.
W kocu moemy opublikowa nasz aplikacj poprzez wcinicie przycisku Publish. Android
Market sprawdzi publikowany program, zwaszcza pod ktem daty wyganicia certyfikatu aplikacji. Jeeli cay proces przebiegnie pomylnie, nasz kod stanie si dostpny do pobrania przez
innych uytkownikw. Gratulacje!

1022 Android 3. Tworzenie aplikacji

Korzystanie ze sklepu Android Market


Z poziomu urzdze Android Market jest dostpny ju od pewnego czasu, natomiast mniej
wicej od lutego 2011 roku mona rwnie korzysta z niego poprzez internet. Wydawcy nie
maj adnej kontroli nad dziaaniem sklepu, mog co najwyej wstawi ciekawy tekst i zrzuty
ekranu do opisu umieszczanej aplikacji. Zatem pod tym wzgldem o komfort uytkownika musi zadba sama firma Google. Za pomoc urzdzenia uytkownik moe przeszukiwa baz danych przy zastosowaniu sowa kluczowego, przeglda najczciej pobierane aplikacje (patne
oraz darmowe), zalecane programy lub nowoci oraz cae kategorie. Po znalezieniu odpowiedniej aplikacji uytkownik moe j od razu zaznaczy, co spowoduje wywietlenie ekranu szczegw programu, na ktrym mona wybra opcj jego instalacji lub kupna. Wybr opcji kupna
uruchomi usug Google Checkout, gdzie zostanie przeprowadzona finansowa cz transakcji.
Pobrana aplikacja pojawia si na urzdzeniu uytkownika wrd pozostaych programw.
Interfejs uytkownika w wersji internetowej serwisu Android Market (http://market.android.com)
wyglda praktycznie tak samo jak w urzdzeniach, zosta jednak dostosowany do rozmiarw
monitora. Jedna z zasadniczych rnic polega na koniecznoci wprowadzenia nazwy uytkownika konta Google, aby mc przeglda wersj sieciow serwisu Android Market. W ten
sposb firma Google czy dziaania uytkownika w internetowej wersji serwisu Android Market
z dziaaniami uytkownika na urzdzeniu. Oznacza to, e po pierwsze, kiedy uytkownik
korzysta z wersji sieciowej serwisu, Android Market ma dostp do informacji, ktre aplikacje s
zainstalowane w nalecym do uytkownika urzdzeniu. Po drugie, w trakcie zakupw pobierana aplikacja bdzie wysyana wprost do urzdzenia uytkownika, nawet jeli zostaa zakupiona
poprzez stacj robocz.
Witryna Android Market daje moliwo przegldania pobranych aplikacji w katalogu Moje
zamwienia. Mona tu oglda wszystkie zarwno zainstalowane aplikacje, jak i zakupione
programy, nawet po ich usuniciu (najczciej zostaj usunite wycznie z powodu braku
miejsca w urzdzeniu). Oznacza to, e uytkownik moe usun patn aplikacj i ponownie
j zainstalowa w innym terminie bez ponoszenia dodatkowych kosztw. Oczywicie, po wybraniu opcji zwrotu kosztw dana aplikacji nie bdzie wywietlana w katalogu Moje zamwienia. W folderze tym nie bd rwnie pokazywane darmowe aplikacje po ich wykasowaniu.
Lista aplikacji umieszczonych w tym katalogu jest powizana z kontem Google obsugiwanym
przez urzdzenie. Oznacza to, e moemy zmieni urzdzenie bez obaw o utrat zakupionych
aplikacji. Pamitajmy jednak o tym, e jeli posiadamy kilka tosamoci na serwerach Google,
zakupione wczeniej aplikacje moemy pobra jedynie z konta, na ktrym za nie zapacilimy.
Podczas przegldania aplikacji w katalogu Moje zamwienia wszelkie nowe wersje programw
bd zaznaczone oraz gotowe do uaktualnienia.
Android Market filtruje aplikacje dostpne dla okrelonych uytkownikw. Proces ten jest przeprowadzany na wiele sposobw. W niektrych krajach dostpne s wycznie bezpatne wersje programw z powodu rnorakich wymogw prawa handlowego, ktre nie odpowiadaj
firmie Google w danym pastwie. Firma Google bardzo stara si przezwyciy te przeszkody,
aby patne aplikacje byy dostpne na caym wiecie. Do tego czasu uytkownicy w niektrych
krajach mog cieszy si jedynie darmowymi aplikacjami. Osoby posiadajce urzdzenia pracujce pod kontrol starszej wersji systemu Android nie maj dostpu do aplikacji obsugiwanych przez nowsze wersje zestawu Android SDK. Uytkownicy korzystajcy z urzdze niespeniajcych wymaga sprztowych danej aplikacji (definiowanych w znacznikach <uses-feature>
pliku AndroidManifest.xml) rwnie nie bd widzie takich programw. Na przykad aplikacje nieobsugujce maych wywietlaczy nie bd widziane w sklepie Android Market przez

Rozdzia 28 Wdraanie aplikacji na rynek Android Market i nie tylko

1023

uytkownikw posiadajcych urzdzenia z takimi wanie ekranami. Taka filtracja zostaa


wprowadzona gwnie po to, aby uchroni uytkownikw przed pobraniem aplikacji, ktra nie
bdzie dziaa w ich telefonach.
Jeeli kupujemy aplikacj w innych krajach, na etapie transakcji moe nastpi przewalutowanie,
co zazwyczaj oznacza dodatkow opat, chyba e sprzedawca ustali cen w naszej walucie.
Aplikacje s w rzeczywistoci kupowane z kraju sprzedawcy za porednictwem usugi Google
Checkout. Sklep Android Market wywietla przyblion kwot, lecz w rzeczywistoci opaty
mog by nieco inne w zalenoci od momentu przeprowadzenia transakcji oraz uytych procesorw patnoci. Osoby kupujce mog zauway, e ich konto zostaje obcione symboliczn
opat (na przykad 1 dolara) w czasie przeprowadzania transakcji. Firma Google upewnia si
w ten sposb, e wprowadzone informacje o patnoci s poprawne, a wspomniana opata
w rzeczywistoci nie zostanie uiszczona.
Istnieje w internecie kilka stron, ktre stanowi odzwierciedlenie witryny Android Market.
Uytkownicy mog tam wyszukiwa aplikacje, przeglda kategorie oraz czyta informacje na
temat programw bez koniecznoci posiadania urzdzenia. Rozwizanie to dziaa na zasadzie
filtrowania przez Android Market konfiguracji urzdzenia oraz regionu geograficznego, w ktrym znajduje si uytkownik. Nie ma jednak moliwoci pobrania w ten sposb aplikacji na urzdzenie. Przykadami takich lustrzanych witryn s www.androlib.com, www.androidzoom.com
oraz www.cyrket.com.

Alternatywy dla serwisu Android Market


Serwis Android Market nie jest jedynym graczem na rynku. Nie istnieje aden przymus korzystania z tej usugi. Powinnimy bra pod uwag rwnie inne kanay dystrybucji, nie tylko po
to, aby udostpnia aplikacje uytkownikom w niektrych krajach, lecz rwnie po to, aby
korzysta z innych metod pobierania opat oraz moliwoci zarabiania.
Istniej sklepy z aplikacjami zupenie niezalene od witryny Android Market. Przykadami mog
by www.andappstore.com, slideme.org, www.getjar.com i www.androidgear.com. Serwis
Amazon rwnie uruchamia wasn wersj sklepu Android App Store. Moemy na tych stronach wyszukiwa, przeglda, czyta informacje o aplikacjach, a take pobiera je zarwno
z poziomu telefonu, jak i przegldarki. Witryny te nie musz da przestrzegania zasad firmy
Google, wliczajc w to rwnie opaty transakcyjne oraz formy patnoci. Te oddzielne sklepy
akceptuj takie procesory patnoci, jak na przykad PayPal. Zostaje w nich rwnie pominite
ograniczenie dotyczce rejonu geograficznego i konfiguracji sprztowej. Niektre ze sklepw
oferuj instalacj klienta Android, inne za dokonuj jego wstpnej instalacji w urzdzeniu.
Uytkownicy mog po prostu otworzy przegldark WWW w urzdzeniu i wyszuka w internecie dan aplikacj; po jej zapisaniu na karcie pamici system bdzie posiada informacje niezbdne do jej zainstalowania. Pobrany plik .apk jest traktowany jak aplikacja systemu Android.
Jeeli uytkownik kliknie w przegldarce w historii pobranych plikw nazw takiego pliku
(nie naley myli tego mechanizmu z omwionym wczeniej katalogiem Moje zamwienia),
zostanie zapytany, czy chce zainstalowa pobran aplikacj. Taka swoboda oznacza, e programista moe ustanawia wasne metody pobierania aplikacji przez uytkownikw, nawet z domowej strony WWW, oraz zastosowa wybrane przez siebie metody patnoci. Nadal jednak
trzeba wypenia zobowizania podatkowe wobec urzdu skarbowego.
Chocia takie alternatywne metody dystrybuowania aplikacji nie s ograniczane przez zasady
firmy Google, nie oferuj rwnie tak wysokiego poziomu ochrony kupca, jak ma to miejsce

1024 Android 3. Tworzenie aplikacji


w sklepie Android Market. Istnieje moliwo, e uytkownik zakupi z takiego rda aplikacj,
ktra nie bdzie dziaa na jego urzdzeniu. Osoba kupujca musi rwnie zapewni sobie tworzenie kopii zapasowych aplikacji na wypadek jej przypadkowej utraty lub w momencie zmiany
urzdzenia.
Te inne kanay dystrybucji pozwalaj nam zarabia na sprzeday kadego egzemplarza aplikacji,
podobnie jak ma to miejsce w przypadku usugi Android Market. Moemy take implementowa
w ich obszarze alternatywne mechanizmy uiszczania zapaty. Oczywicie, moemy rwnie
wprowadza reklamy do aplikacji i zarabia w ten sposb. Nie ma take przeciwwskaza co do
umieszczania tych mechanizmw wewntrz aplikacji. Na przykad serwis PayPal zawiera odpowiedni bibliotek dostosowan do systemu Android (warto zajrze na stron http://www.x.com).
W ten sposb umoliwiamy uytkownikom zakup dodatkw, nowej treci lub patnych aktualizacji wprost z poziomu aplikacji. W ten sam sposb mona uzyskiwa datki. Moemy nawet
zaimplementowa mobilny sklepik, w ktrym byby wykorzystywany mechanizm PayPal.
Pamitajmy, e firma Google nie zabrania wydawcom jednoczesnej sprzeday aplikacji w wielu
rnych sklepach i w usudze Android Market. Zatem w celu zwikszenia efektywnoci powinnimy wzi pod uwag wszystkie moliwoci.

Odnoniki
Poniej prezentujemy odnoniki do materiaw, ktre mog pomc w zrozumieniu koncepcji
omwionych w niniejszym rozdziale:
http://developer.android.com/guide/topics/manifest/manifest-intro.html jest to
strona poradnika programisty powicona plikowi AndroidManifest.xml, zawierajca
opisy zastosowania znacznikw <supports-screens>, <uses-configuration>
oraz <uses-feature>.
http://developer.android.com/guide/practices/screens_support.html strona
poradnika programisty zawierajca informacje o tak zwanej obsudze wielu ekranw.
Znajdziemy tu wiele przydatnych informacji na temat korzystania z rnorodnych
rozmiarw i gstoci wywietlaczy.
http://developer.android.com/guide/practices/ui_guidelines/icon_design.html strona
poradnika programisty zawierajca wskazwki dotyczce projektowania ikon.
Umieszczono tu interesujce informacje na temat tworzenia ikon wpywajcych
na jako naszej aplikacji.
http://android-developers.blogspot.com/2010/09/securing-android-lvl-applications.html oraz
http://android-developers.blogspot.com/2010/09/proguard-android-and-licensing-server.html
dwa wpisy dotyczce metod stosowania biblioteki LVL w celu zapobieenia piractwu.
http://developer.android.com/guide/market/billing/index.html dokumentacja dotyczca
wbudowanego moduu pobierania opat.

Podsumowanie
Teraz moemy ju podbi cay wiat naszymi aplikacjami utworzonymi dla systemu Android!
Pokazalimy, w jaki sposb naley przygotowa siebie oraz swoj aplikacj, jak naley j opublikowa oraz umoliwi uytkownikom jej wyszukanie, pobranie i uytkowanie.

R OZDZIA

29
Koncepcja fragmentw
oraz inne pojcia
dotyczce tabletw

A do teraz zajmowalimy si mechanizmami wsplnymi dla wszystkich wersji


systemu Android. Zdumiewajce, e miny zaledwie dwa lata od zaprezentowania
Androida w urzdzeniach dostpnych na rynku, a ju nasta wit nowej ery urzdze wykorzystujcych ten system tabletw. Interfejs uytkownika dostpny
w wersji 3.0 Androida zosta od podstaw zaprojektowany na potrzeby tabletw. Na
szczcie nie oznacza to wcale, e musimy zignorowa ca dotychczas zdobyt
wiedz i rozpocz cay proces nauki od pocztku. W rzeczywistoci wszystko, czego si do tej pory dowiedzielimy, okae si przydatne w procesie pisania aplikacji
przeznaczonych dla tabletw. Wraz z wersj 3.0 Androida zostay zaprezentowane
nowe koncepcje oraz funkcje, ktrych opanowanie jest niezbdne do posugiwania
si bardzo duymi (xlarge) ekranami tabletw. Wikszo aplikacji napisanych dla
wczeniejszych wersji systemu bdzie dziaaa na tabletach, jednak mog si pojawi
kopoty z ich optymalizacj. Jest to pierwszy rozdzia tej ksiki, w ktrym zajmiemy si objanieniem nowych poj i funkcji.
Jedn z nowych rdzennych klas systemu Android 3.0 jest klasa Fragment, zawierajca
kilka klas potomnych. W tym rozdziale zapoznamy si z koncepcj fragmentu, jego
budow oraz powizaniami z architektur aplikacji, a take sposobami jego wykorzystania. Dziki fragmentom moemy przeprowadza teraz wiele czynnoci, ktre
wczeniej byy bardzo trudne do zaimplementowania. Interesujcy jest rwnie fakt,
e fragmenty mog zosta wykorzystane w aplikacjach dla starszych wersji Androida,
poniewa firma Google wydaa zestaw SDK zawierajcy architektur fragmentw
dziaajc z tymi systemami. Zatem nawet jeli nie jestemy zainteresowani tworzeniem aplikacji dla tabletw, moemy stwierdzi, e fragmenty uatwi nam ycie nawet w urzdzeniach, ktre nie s wyposaone w ekran o wysokiej rozdzielczoci.
Zacznijmy od koncepcji fragmentw.

1026 Android 3. Tworzenie aplikacji

Czym jest fragment?


W pierwszym podrozdziale wyjanimy, czym s fragmenty i do czego su. Najpierw jednak
Czytelnik powinien przyswoi sobie odpowiednie podstawy, aby zrozumie, po co w ogle zostaa zaprojektowana koncepcja fragmentw. Jak ju powiedzielimy, aplikacje systemu Android
wykorzystuj aktywnoci w urzdzeniach wyposaonych w niewielkie ekrany do prezentowania
danych oraz rnorodnych funkcji uytkownikowi, a kada taka aktywno posiada w miar
proste, wyranie zdefiniowane przeznaczenie. Przykadowo za pomoc aktywnoci moemy wywietla list kontaktw umieszczonych w ksice adresowej. Inna aktywno moe pozwala
na pisanie wiadomoci e-mail. Aplikacja skada si z zestawu tego typu aktywnoci, zgrupowanych w wiksze jednostki w celu spenienia bardziej zoonego zadania, na przykad zarzdzania
kontem pocztowym poprzez odczytywanie i wysyanie wiadomoci. Jest to dobre rozwizanie
w przypadku urzdze wyposaonych w niewielkie gabarytowo ekrany, jeeli jednak mamy do
czynienia z bardzo duym wywietlaczem (co najmniej 10 cali), do dyspozycji bdziemy mie
miejsce pozwalajce na wykonywanie wikszej liczby czynnoci. By moe przydatna okae si
moliwo przegldania listy wiadomoci w skrzynce wiadomoci przychodzcych i jednoczesnego podgldu zaznaczonego listu w osobnym oknie. Ewentualnie aplikacja moe wywietla
list kontaktw i w tym samym czasie prezentowa szczegowy widok zaznaczonego kontaktu.
Jako programici aplikacji dla Androida wiemy, e moemy osign ten cel poprzez zdefiniowanie jeszcze jednego ukadu graficznego, przeznaczonego dla bardzo duych ekranw, zawierajcego kontrolki ListView i inne rodzaje widokw. Poprzez jeszcze jeden ukad graficzny
mamy na myli dodatkowy ukad graficzny, wystpujcy rwnorzdnie z analogicznymi ukadami, zdefiniowanymi dla urzdze posiadajcych mniejsze wywietlacze. Oczywicie, niezbdne okae si zaprojektowanie oddzielnego ukadu graficznego dla trybu portretowego i krajobrazowego. W przypadku bardzo duego ekranu oznacza to do duo widokw dla etykiet, pl,
obrazw i innych obiektw, ktre naley rozmieci i odpowiednio zaprogramowa. Nasuwa
si myl, e przydaby si jaki sposb pogrupowania tych elementw i utworzenia dla nich
wsplnej logiki, dziki czemu dane elementy skadowe aplikacji mogyby by wykorzystywane
w aplikacjach dla ekranw o rnych rozmiarach oraz dla rnych urzdze, minimalizujc
w ten sposb prac programisty. Dlatego wanie zostaa zaprojektowana koncepcja fragmentw.
Fragment mona uzna za swego rodzaju podaktywno. Rzeczywicie, semantyka fragmentu
bardzo przypomina semantyk aktywnoci. Zawiera on podobn hierarchi widokw oraz analogiczny cykl ycia. Fragmenty mog nawet reagowa w taki sam sposb na wcinicia przycisku
cofania jak aktywnoci. Jeeli Czytelnik zastanawia si, czy w ten sposb mona jednoczenie
umieci wiele aktywnoci na ekranie tabletu, to oznacza, e jest na dobrej drodze. Jednak poniewa utrzymywanie wicej ni jednej uruchomionej aktywnoci w danej aplikacji moe
powodowa nieporzdek, zadanie to zostao przypisane wanie fragmentom. Oznacza to, e
fragmenty s przechowywane wewntrz aktywnoci. Mog one przebywa jedynie we wntrzu
kontekstu aktywnoci; fragment nie moe bez niej istnie. Wspegzystuj one z pozostaymi
elementami aktywnoci, co oznacza, e nie musimy konwertowa caego interfejsu uytkownika,
aby mc korzysta z fragmentu. Moemy utworzy wczeniejszy ukad graficzny aktywnoci
i wykorzysta fragment odnoszcy si do tylko jednego elementu interfejsu uytkownika.
W porwnaniu do aktywnoci fragmenty zachowuj si jednak inaczej, gdy mamy do czynienia
z zachowywaniem i odczytywaniem stanu. Struktura fragmentu zawiera kilka funkcji, ktre
pozwalaj na o wiele atwiejsze zapisywanie i wczytywanie jego stanu, ni ma to miejsce w przypadku aktywnoci.
To, kiedy naley wprowadzi fragmenty, zaley od kilku czynnikw, ktre teraz omwimy.

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1027

Kiedy naley stosowa fragmenty?


Jednym z gwnych powodw stosowania fragmentw jest moliwo wykorzystywania elementw interfejsu uytkownika oraz jego funkcji w rnych urzdzeniach oraz dla rnych
rozmiarw ekranu. Stwierdzenie to dotyczy zwaszcza tabletw. Pomylmy, ile rzeczy naraz
moe si dzia na ekranie tabletu. W tym przypadku mamy do czynienia raczej z komputerem
biurkowym ni telefonem, a wiele aplikacji biurowych posiada wielopanelowy interfejs uytkownika. Zgodnie z tym, co powiedzielimy wczeniej, moemy w jednym momencie wywietli
na ekranie list elementw oraz szczegowy opis jednego z nich. atwo to zobrazowa w trybie
krajobrazowym, gdzie lista moe si znajdowa po lewej stronie ekranu, a szczegowy opis po
prawej. Co si jednak stanie, w przypadku gdy uytkownik obrci urzdzenie do trybu portretowego i ekran stanie si wszy i wyszy? By moe bdziemy teraz chcieli, aby lista znalaza
si w grnej czci wywietlacza, a szczegy w jego dolnej czci. Co jednak zrobi, w przypadku gdy wywietlacz bdzie zbyt may na jednoczesne wywietlanie dwch elementw? Czy
najlepszym rozwizaniem nie byoby oddzielenie aktywnoci listy od aktywnoci szczegw,
lecz w taki sposb, aby mogy wspdzieli logik wykorzystywan w tej samej aplikacji, ale
podczas obsugi duych ekranw? Mamy nadziej, e Czytelnik odpowiedzia twierdzco. Take
w tym przypadku fragmenty okazuj si pomocne.
Cofnijmy si do przykadu ze zmian orientacji ekranu. Wiemy, e w przypadku pisania kodu
obsugujcego zmiany, ktre zachodz w aktywnoci podczas obrotu urzdzenia, prawdziwym
utrapieniem okazuje si zapisywanie biecego stanu aktywnoci oraz jego przywracanie po
odtworzeniu aktywnoci w nowym trybie. Czy nie podobaoby si nam, gdyby aktywno skadaa si z elementw, ktre byyby utrzymywane w trakcie zmian trybu orientacji, dziki
czemu uniknlibymy tego caego chaosu zwizanego z usuwaniem i odtwarzaniem aktywnoci?
Oczywicie, e bardzo by nam si podobao. Od tego s fragmenty.
Wyobramy sobie teraz, e uytkownik korzysta z naszej aktywnoci i przeprowadza jak czynno. Zamy, e interfejs uytkownika uleg zmianie w obrbie tej aktywnoci i uytkownik
chce cofn si o jeden albo dwa ekrany, a moe nawet trzy. W klasycznej aktywnoci wcinicie przycisku cofania uniemoliwi uytkownikowi powrt do danej aktywnoci. W przypadku
fragmentw kade wcinicie tego przycisku spowoduje cofnicie o jeden fragment w ich stosie
i uytkownik cay czas bdzie mg korzysta z biecej aktywnoci.
Pomylmy teraz o interfejsie uytkownika, w ktrym zmianie ulega ogromny zakres treci; chcielibymy, aby ten proces przebiega w elegancki sposb, jak na dopracowan aplikacj przystao.
Take tutaj fragmenty oka si pomocne.
Skoro ju mniej wicej wiemy, czym jest fragment oraz do czego moe nam si przyda, zajrzyjmy nieco gbiej w jego struktur.

Struktura fragmentu
Jak ju wspomnielimy, fragment przypomina nieco podaktywno: posiada jasno okrelone
przeznaczenie i niemal zawsze prezentuje interfejs uytkownika. Jednak aktywno stanowi klas
podrzdn w stosunku do kontekstu, natomiast fragment jest rozszerzeniem klasy Object, bdcej czci pakietu android.app. Fragment nie stanowi rozszerzenia aktywnoci. Jednak, podobnie jak ma to miejsce w przypadku aktywnoci, klasa Fragment (lub jej elementy podrzdne)
bdzie zawsze rozszerzana w celu przesonicia jej zachowania.

1028 Android 3. Tworzenie aplikacji


Fragment moe posiada hierarchi widokw sucych do nawizywania kontaktu z uytkownikiem. Hierarchia ta nie rni si od innych hierarchii widokw pod tym wzgldem, e moe
zosta utworzona (rozwinita) za pomoc specyfikacji ukadu graficznego w pliku XML lub
implementacji w kodzie Java. Jeeli ma by widziana przez uytkownika, taka hierarchia musi
zosta doczona do hierarchii widokw aktywnoci nadrzdnej, czym wkrtce zajmiemy si
dokadniej. Obiekty tworzce hierarchi widokw fragmentw nie rni si od widokw wykorzystywanych w innych rejonach Androida. Zatem caa wiedza zdobyta na temat widokw
znajduje rwnie zastosowanie w przypadku fragmentw.
Oprcz hierarchii widokw fragment zawiera take pakiet sucy jako argumenty inicjalizacyjne. Analogicznie do aktywnoci, fragment moe zosta automatycznie zachowany, a nastpnie wczytany przez system. W trakcie wczytywania fragmentu zostaje wywoany domylny
konstruktor (na przykad nieposiadajcy argumentw), nastpnie odczytany pakiet argumentw wobec nowego fragmentu. Kolejne metody zwrotne fragmentu uzyskuj dostp do tych argumentw, ktre mog zosta wykorzystane do przywrcenia poprzedniego stanu. Z tego powodu koniecznie musimy:
upewni si, e istnieje domylny konstruktor klasy fragmentu;
doda pakiet argumentw tu po utworzeniu nowego fragmentu, dziki czemu
nastpne metody skonfiguruj nasz fragment we waciwy sposb, a system w razie
koniecznoci bezbdnie go wczyta.
Aktywno w danej chwili moe posiada kilka aktywnych fragmentw, a jeeli jeden fragment
zosta wymieniony na inny, cay proces wymiany zostanie zapisany w stosie drugoplanowym.
Stos ten jest zarzdzany przez powizany z aktywnoci meneder fragmentw. To wanie za
jego pomoc definiowane jest zachowanie fragmentw po wciniciu przycisku cofania. Meneder fragmentw zostanie omwiony w dalszej czci tego rozdziau. Teraz Czytelnikowi wystarczy wiedza, e fragment doskonale wie, z ktr aktywnoci jest powizany, dziki czemu
moe bez problemu nawiza komunikacj z menederem fragmentw. Fragment moe rwnie uzyska dostp do zasobw poprzez aktywno.
Poniewa istnieje moliwo zarzdzania fragmentem, zawiera on pewne dane identyfikacyjne,
w tym znacznik oraz identyfikator. Dane te przydaj si podczas wyszukiwania fragmentu,
dziki czemu moe by wielokrotnie wykorzystywany.
Rwnie analogicznie do aktywnoci, podczas odtwarzania pakietu mona zapisa jego stan
w obiekcie pakietu, ktry zostaje przekazany do metody zwrotnej onCreate(). Taki zachowany
pakiet jest rwnie przekazywany do metod onInflate(), onCreateView() i onActivity
Created(). Zwrmy uwag, e nie jest to ten sam pakiet, ktry jest wykorzystywany jako argument inicjalizacji. To wanie w tym pakiecie bdziemy najprawdopodobniej zapisywa stan
fragmentu, a nie wartoci wykorzystywane do jego inicjalizacji.

Cykl ycia fragmentu


Zanim zaczniemy testowa fragmenty w przykadowych aplikacjach, musimy koniecznie zapozna si z ich cyklem ycia. Dlaczego? Cykl ycia fragmentu jest bardziej skomplikowany od
cyklu ycia aktywnoci i jest bardzo wane, aby zrozumie, kiedy mona wykona dan operacj
na fragmencie. Na rysunku 29.1 przedstawilimy cykl ycia fragmentu.
Jeeli porwnamy go z rysunkiem 2.15 (na ktrym pokazalimy cykl ycia aktywnoci), dostrzeemy kilka rnic, najczciej zwizanych z oddziaywaniem pomidzy fragmentem a aktywnoci.

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1029

Rysunek 29.1. Cykl ycia fragmentu

Fragment jest bardzo uzaleniony od aktywnoci, do ktrej jest przypisany, i moe przej przez
kilka etapw cyklu ycia, podczas gdy aktywno w tym samym czasie znajduje si cigle na
jednym etapie.
Na samym pocztku tworzy si wystpienie fragmentu. Istnieje on teraz jako obiekt w pamici.
Prawdopodobnie pierwsz czynnoci bdzie dodanie argumentw inicjalizacji do tego obiektu.
Stwierdzenie to jest najbardziej prawdziwe w przypadku odtwarzania wczeniej zachowanego
stanu fragmentu. W momencie odtwarzania stanu fragmentu zostaje przywoany domylny
konstruktor, po czym nastpuje doczenie pakietu argumentw inicjalizacji. W przypadku
tworzenia nowego wystpienia fragmentu warto zapozna si z kodem z listingu 29.1 zaprezentowalimy w nim bardzo przydatny schemat metody fabrykujcej obiekty wewntrz definicji klasy MyFragment.
Listing 29.1. Tworzenie wystpienia fragmentu za pomoc statycznej metody fabrykujcej
public static MyFragment newInstance(int index) {
MyFragment f = new MyFragment();
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);
return f;
}

Z perspektywy klienta otrzymuje on nowe wystpienie fragmentu poprzez wywoanie statycznej


metody newInstance() zawierajcej jeden argument. Otrzymuje z powrotem utworzon instancj
obiektu, natomiast argument inicjalizacji zosta zdefiniowany w pakiecie argumentw fragmentu.
Jeeli ten fragment zostanie zapisany i odtworzony w pniejszym terminie, system przeprowadzi bardzo podobny proces, angaujcy domylny konstruktor i przyczajcy argumenty

1030 Android 3. Tworzenie aplikacji


inicjalizacji. W tym konkretnym przypadku moglibymy zdefiniowa sygnatur metody (lub
metod) newInstance(), ktra przyjmowaaby waciw liczb i typ argumentw, a nastpnie poprawnie zbudowaaby pakiet argumentw. Jest to jedyne zadanie metody newInstance().
Wystpujce po niej metody zwrotne prowadz pozosta cz procesu konfiguracji fragmentu.

Metoda zwrotna onInflate()


Nastpnym procesem, jaki mgby nastpi, jest rozwinicie widoku ukadu graficznego. Jeeli
nasz fragment jest zdefiniowany przez znacznik <fragment> w rozwijanym ukadzie graficznym (najczciej ma to miejsce w momencie wywoania metody setContentView() wobec
gwnego ukadu graficznego aktywnoci), w naszym fragmencie zostaaby wywoana osobna
metoda zwrotna onInflate(). Zostaj w niej przekazane interfejs AttributeSet, atrybuty
znacznika <fragment> oraz pakiet atrybutw zachowanego stanu. Jeeli dany fragment zosta
odtworzony oraz zosta wczeniej zapisany jaki stan w metodzie onSaveInstanceState(), we
wspomnianym pakiecie s przechowywane wartoci tego stanu. Oczekujemy po metodzie
onInflate(), e bd odczytywane wartoci stanu oraz zostan one zachowane na pniej.
W rzeczywistoci na tym etapie istnienia fragmentu jest jeszcze za wczenie, eby mc cokolwiek zrobi z interfejsem uytkownika. Fragment nie zosta jeszcze nawet powizany z aktywnoci. Ale to bdzie wanie nastpna czynno, jaka zostanie na nim przeprowadzona.
Do rejestru bdw zosta wprowadzony defekt numer 14796, ktry wynika z rozbienoci
pomidzy dokumentacj metody onInflate() a tym, co faktycznie zachodzi w systemie
Honeycomb. Zgodnie z dokumentacj metoda onInflate() jest zawsze wywoywana
przed metod onAttach(). W rzeczywistoci po ponownym uruchomieniu aktywnoci
metoda ta moe zosta wywoana po metodzie onCreateView(). Jest ju wtedy za pno
na umieszczenie wartoci w pakiecie i wywoanie metody setArguments(). Wicej
informacji na ten temat znajdziemy pod adresem http://code.google.com/p/android/issues/
detail?id=14796. Z tego samego powodu metoda zwrotna onInflate() nie zostaa
umieszczona na schemacie z rysunku 29.1; zbyt trudno przewidzie, kiedy zostanie wywoana.

Metoda zwrotna onAttach()


Metoda zwrotna onAttach() zostaje przywoana po przyczeniu fragmentu do aktywnoci.
Jeeli chcemy wykorzysta odniesienie do aktywnoci, moemy uzyska do niego dostp. Tak
aktywno moemy wykorzysta przynajmniej do uzyskania informacji o otaczajcej aktywnoci. Moemy rwnie stosowa aktywno w postaci kontekstu dla innych czynnoci. Warto
odnotowa fakt, e klasa Fragment zawiera metod getActivity(), ktra w razie potrzeby zawsze
bdzie przekazywaa aktywno doczon do naszego fragmentu. Pamitajmy, e przez cay cykl
ycia pakiet argumentw inicjalizacji bdzie dostpny za pomoc metody getArguments()
fragmentu. Jednak po przyczeniu fragmentu do aktywnoci nie bdziemy mogli ju wywoa
tej metody, zatem moemy dodawa argumenty inicjalizacji jedynie na samym pocztku.

Metoda zwrotna onCreate()


Nastpnym etapem jest metoda onCreate(). Przypomina ona analogiczn metod aktywnoci,
rnica polega na tym, e nie powinnimy w jej wntrzu umieszcza kodu zalenego od obecnoci hierarchii widokw aktywnoci. Chocia nasz fragment moe by ju powizany z aktywnoci, nie zostalimy jeszcze powiadomieni o zakoczeniu dziaania metody onCreate()
aktywnoci. Dopiero zbliamy si do tego momentu. Jeeli posiadamy pakiet argumentw

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1031

zachowanego stanu, zostanie on przekazany tej metodzie. Metoda ta moe jako pierwsza utworzy wtek drugoplanowy, pobierajcy dane wymagane przez fragment. Kod fragmentu jest
przetwarzany w wtku interfejsu uytkownika i nie chcemy, aby byy w nim przeprowadzane
rwnie operacje wejcia-wyjcia lub sieciowe. W rzeczywistoci logicznym rozwizaniem jest
przygotowanie danych za pomoc wtku pobocznego. To wanie w nim powinny wystpowa
wszystkie wywoania blokujce. Musimy pniej poczy si w jaki sposb z tymi danymi,
istniej na to jednak odpowiednie sposoby.
Jednym ze sposobw wczytania danych w wtku pobocznym jest zastosowanie klasy
Loader. Zabrako nam miejsca na jej opis w ksice, zapraszamy jednak do przejrzenia
naszej oficjalnej strony WWW w celu zapoznania si z niezbdnymi informacjami.

Metoda zwrotna onCreateView()


Kolejn metod zwrotn stanowi onCreateView(). Spodziewamy si po niej, e przekae naszemu fragmentowi hierarchi widokw. Wrd przekazywanych argumentw znajdziemy
tutaj klas LayoutInflater (suc do rozwijania ukadu graficznego danego fragmentu), klas
nadrzdn ViewGroup (na listingu 29.2 noszc nazw container) oraz pakiet zachowanego
stanu (jeeli istnieje). Jest bardzo istotne, aby zwrci tutaj uwag, e nie powinnimy docza
hierarchii widoku do przekazywanego widoku potomnego ViewGroup. Powizanie to nastpi
automatycznie pniej. Mamy do dyspozycji klas nadrzdn, dziki czemu moemy j wykorzysta wraz z metod inflate() klasy LayoutInflater, chocia w razie koniecznoci moemy
samodzielnie sprawdzi t klas. Najprawdopodobniej jednak, jeeli przyczymy w tej metodzie
zwrotnej hierarchi widokw fragmentu do klasy nadrzdnej, pojawi si wyjtki. Na listingu
29.2 prezentujemy przykad operacji, jak mona wykona w tej metodzie.
Listing 29.2. Utworzenie hierarchii widokw fragmentu w metodzie onCreateView()
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.details, container, false);
TextView text1 = (TextView) v.findViewById(R.id.text1);
text1.setText(myDataSet[ getPosition() ] );
return v;
}

Widzimy tutaj, w jaki sposb moemy uzyska dostp do ukadu graficznego wykorzystywanego
wycznie przez ten fragment i rozwin go do widoku, ktry zostanie nastpnie przekazany
obiektowi wywoujcemu. Takie rozwizanie posiada kilka zalet. Moemy zawsze utworzy
hierarchi widokw za pomoc kodu, jednak poprzez rozwinicie ukadu graficznego z pliku
XML wykorzystujemy moliwoci technologii wyszukiwania zasobw. W zalenoci od konfiguracji urzdzenia lub, dokadniej mwic, od aktualnie uywanego urzdzenia zostanie wybrany najwaciwszy plik ukadu graficznego. Moemy wtedy uzyska dostp do okrelonego
widoku przechowywanego w ukadzie graficznym; w naszym przypadku pola text1 klasy
TextView. Jeszcze raz powtarzamy: nie naley docza w tej metodzie zwrotnej widoku danego
fragmentu do klasy nadrzdnej. Na listingu 29.2 widzimy, e wykorzystujemy pojemnik
w wywoaniu metody inflate(), przekazujemy jednak rwnie warto false w parametrze
attachToRoot.

1032 Android 3. Tworzenie aplikacji

Metoda zwrotna onActivityCreated()


Zbliamy si do momentu, w ktrym uytkownik bdzie mg oddziaywa na fragment. Nastpn metod cyklu ycia fragmentu jest onActivityCreated(). Zostaje ona wywoana po zakoczeniu dziaania metody zwrotnej onCreate(). Mamy teraz pewno, e hierarchia widokw
aktywnoci, w tym rwnie hierarchia widokw fragmentu (jeli zostaa wczeniej przekazana),
jest gotowa i dostpna. To wanie na tym etapie wprowadzamy ostatnie poprawki w interfejsie,
zanim oddamy go w rce uytkownika. Jest to szczeglnie istotne w przypadku odtwarzania
aktywnoci i jej fragmentw z zachowanego stanu. Teraz wanie do aktywnoci zostaj rwnie doczone pozostae fragmenty.

Metoda zwrotna onStart()


Kolejna metoda cyklu ycia fragmentu to onStart(). Na tym etapie fragment jest ju widoczny
dla uytkownika. Nie rozpoczlimy jednak jeszcze interakcji z uytkownikiem. Metoda ta jest
powizana z metod zwrotn onStart() aktywnoci. W ten sposb moemy wstawi cz operacji uprzednio umieszczanych w metodzie onStart() aktywnoci do metody onStart()
fragmentu, gdy to wanie w nim znajduj si skadniki interfejsu uytkownika.

Metoda zwrotna onResume()


Ostatni metod zwrotn wystpujc przed rozpoczciem interakcji uytkownika z fragmentem jest onResume(). Metoda ta jest powizana z metod onResume() aktywnoci. Po jej powrocie uytkownik moe ju korzysta z fragmentu. Jeli na przykad nasz fragment zawiera podgld widoku aparatu fotograficznego, uruchomimy go prawdopodobnie w metodzie onResume()
tego fragmentu.
Dotarlimy w kocu do punktu, w ktrym nasza aplikacja ku radoci uytkownika szczliwie rozpoczyna dziaanie. Po pewnym czasie uytkownik zechce zakoczy prac z programem albo poprzez wcinicie przycisku cofania, albo przycisku ekranu startowego, albo ewentualnie poprzez uruchomienie innej aplikacji. Podobnie jak w przypadku aktywnoci, nastpuje
tu sekwencja zdarze postpujcych w kierunku przeciwnym do omwionej powyej.

Metoda zwrotna onPause()


Pierwsz metod zwrotn anulujc fragment jest onPause(). Jest ona powizana z metod
onPause() aktywnoci; podobnie jak ma to miejsce w przypadku aktywnoci, jeeli fragment
zawiera odtwarzacz multimediw lub jaki inny wspdzielony obiekt, moemy wstrzyma,
zatrzyma lub odebra wyniki jego dziaania wanie poprzez t metod. Mamy tu do czynienia
z takimi samymi zasadami dobrego wychowania co w przypadku aktywnoci: muzyka nie
powinna by odtwarzana, kiedy uytkownik rozmawia przez telefon. Istnieje moliwo przejcia fragmentu od metody onPause() z powrotem do metody onResume().

Metoda zwrotna onStop()


Kolejna metoda zwrotna anulujca fragment to onStop(). Jest ona powizana z metod onStop()
aktywnoci i peni podobn funkcj. Zatrzymany fragment moe przej wprost do metody
onStart(), skd wiedzie bezporednia droga do metody onResume().

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1033

Metoda zwrotna onDestroyView()


Jeeli nasz fragment ma zosta zamknity lub jeli trzeba zapisa jego stan, nastpn metod
zwrotn wystpujc na ciece jego anulowania jest onDestroyView(). Zostanie ona wywoana
po odczeniu hierarchii widokw z danego fragmentu, utworzonej w metodzie onCreateView().

Metoda zwrotna onDestroy()


Nastpnie mamy do czynienia z metod onDestroy(). Zostaje ona wywoana, w przypadku gdy
fragment przestaje by potrzebny. Zwrmy uwag, e jest on nadal doczony do aktywnoci
i cigle mona go odnale, na niewiele si ju jednak przyda.

Metoda zwrotna onDetach()


Ostatni metod zwrotn cyklu ycia fragmentu jest onDetach(). Po jej wywoaniu fragment
przestaje by powizany z aktywnoci, nie posiada ju hierarchii widokw i wszystkie zwizane
z nim zasoby zostaj zwolnione.

Stosowanie metody setRetainInstance()


By moe Czytelnik zwrci uwag na poczenia oznaczone przerywan lini na rysunku 29.1.
Jedn z bardziej interesujcych cech fragmentu jest moliwo zadeklarowania, e nie chcemy
cakowicie pozbywa si go w przypadku odtwarzania aktywnoci, dziki czemu nasze fragmenty mog si pojawia pniej. Taki fragment pojawia si wic wraz z wywoaniem metody
setRetainInstance(), ktra przyjmuje warto logiczn. Moemy sobie obrazowo przedstawi,
e warto ta moe znaczy: Tak, ten fragment ma istnie w trakcie odtwarzania aktywnoci
lub Nie, ten fragment zostanie usunity i utworzymy od pocztku nowy fragment. Najlepszym miejscem na wywoanie tej metody jest wntrze metody onCreate() fragmentu.
Jeli parametr przyjmie warto true, oznacza to, e chcemy przechowa obiekt fragmentu
w pamici i nie mamy zamiaru tworzy nowego obiektu od podstaw. Jeeli jednak aktywno
zostaje usunita i odtworzona, musimy odczy fragment od starego obiektu i podczy go do
nowej aktywnoci. Wniosek z tego taki, e jeeli warto przechowywanego wystpienia wynosi
true, w rzeczywistoci nie bdziemy cakowicie usuwa instancji fragmentu, zatem nie bdziemy musieli tworzy nowego fragmentu. Jednak wszystkie pozostae metody zwrotne zostan
wywoane. Poczenia zaznaczone przerywan lini oznaczaj, e moemy podczas wychodzenia pomin metod onDestroy() oraz na etapie ponownego podczania fragmentu do
aktywnoci metod onCreate(). Poniewa aktywno jest najprawdopodobniej odtwarzana
z powodu zmian konfiguracyjnych, metody zwrotne fragmentu powinny si opiera na zaoeniu, e takie zmiany nastpiy i naley podj odpowiednie dziaania. Takim dziaaniem moe
by na przykad rozwinicie ukadu graficznego tworzcego now hierarchi widokw w metodzie onCreateView(). Do tego moe posuy przykadowo kod zamieszczony na listingu
29.2. Jeeli postanowimy skorzysta z funkcji przechowywania instancji, by moe powinnimy
pomin wstawienie czci logiki inicjalizacji w metodzie onCreate(), poniewa nie bdzie ona
wywoywana zawsze w taki sam sposb jak pozostae metody zwrotne.

Przykadowa aplikacja ukazujca cykl ycia fragmentu


Nic nie pozwala bardziej doceni omawianej koncepcji, jak zademonstrowanie jej na dziaajcym przykadzie. Utworzymy aplikacj prezentujc w dziaaniu wszystkie omwione powyej
metody zwrotne. Jeden fragment bdzie zawiera list dzie Szekspira; po klikniciu ktrego

1034 Android 3. Tworzenie aplikacji


tytuu zostanie wywietlony obszerny cytat z tej sztuki w osobnym fragmencie. Aplikacja ta
dziaa na tablecie zarwno w trybie portretowym, jak i krajobrazowym. Skonfigurujemy nastpnie ten przykad w taki sposb, aby dziaa na mniejszym ekranie, dziki czemu Czytelnik
nauczy si rozdziela fragmenty tekstowe na aktywnoci. Rozpoczniemy od ukadu graficznego aktywnoci w trybie krajobrazowym, zaprezentowanym na listingu 29.3 i zilustrowanym
na rysunku 29.2.
Na kocu rozdziau zamiecilimy adres URL, pod ktrym znajdziemy list projektw
utworzonych na potrzeby rozdziau. Moemy je zaimportowa bezporednio do rodowiska
Eclipse.
Listing 29.3. Ukad graficzny aktywnoci w trybie krajobrazowym
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout-land/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.androidbook.fragments.bard.TitlesFragment"
android:id="@+id/titles" android:layout_weight="1"
android:layout_width="0px"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/details" android:layout_weight="2"
android:layout_width="0px"
android:layout_height="match_parent" />
</LinearLayout>

Rysunek 29.2. Interfejs uytkownika przykadowej aplikacji ukazujcej zastosowanie fragmentw

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1035

Powyszy ukad graficzny nie rni si zbytnio od ukadw graficznych prezentowanych w poprzednich rozdziaach: jest on uoony poziomo, z dwoma elementami umieszczonymi z lewej
i z prawej strony. Mamy jednak do czynienia z nowym znacznikiem <fragment>, w ktrym
znajdziemy nowy atrybut noszcy nazw class. Pamitajmy, e fragment nie jest widokiem,
zatem ukad graficzny fragmentu nieco si rni od innych ukadw graficznych. Naley rwnie pamita, e znacznik ten stanowi jedynie wypeniacz w tym ukadzie graficznym.
Pozostae atrybuty fragmentu wygldaj podobnie jak atrybuty widoku i peni analogiczne
funkcje. Atrybut class znacznika fragmentu definiuje rozszerzon klas dla listy tytuw. Oznacza to, e musimy rozszerzy klas Fragment w celu zaimplementowania logiki, a w znaczniku
<fragment> musi si znale nazwa tej rozszerzonej klasy. Fragment posiada wasn hierarchi
widokw, ktra zostanie w pniejszym okresie samoistnie utworzona. Nastpnym znacznikiem jest FrameLayout, a nie kolejny znacznik <fragment>. Dlaczego? Wyjanimy to dokadniej w dalszej czci rozdziau, na razie jednak Czytelnik powinien wiedzie, e bdziemy wprowadza pewne modyfikacje tekstu i wymienia fragmenty midzy sob. Znacznik FrameLayout
suy jako pojemnik widoku przechowujcy biecy fragment tekstu. W przypadku fragmentu
przechowujcego tytuy istnieje jeden (i tylko jeden) fragment, o ktry trzeba zadba; nie ma
adnego zamieniania miejscami i adnych innych modyfikacji. W przypadku obszaru wywietlajcego szekspirowski poemat mamy do czynienia z kilkoma fragmentami.
Kod klasy MainActivity zosta umieszczony na listingu 29.4.
Listing 29.4. Kod rdowy klasy MainActivity
// Jest to plik MainActivity.java
import
import
import
import
import
import
import
import
import

android.app.Activity;
android.app.Fragment;
android.app.FragmentManager;
android.app.FragmentTransaction;
android.content.Intent;
android.content.res.Configuration;
android.os.Bundle;
android.os.Environment;
android.util.Log;

public class MainActivity extends Activity {


public static final String TAG = "Szekspir";
@Override
public void onCreate(Bundle savedInstanceState) {
Log.v(TAG, "w metodzie onCreate aktywnosci MainActivity");
super.onCreate(savedInstanceState);
FragmentManager.enableDebugLogging(true);
setContentView(R.layout.main);
}
@Override
public void onAttachFragment(Fragment fragment) {
Log.v(TAG, "w metodzie onAttachFragment aktywnosci MainActivity. Id fragmentu = "
+ fragment.getId());
super.onAttachFragment(fragment);
}
@Override

1036 Android 3. Tworzenie aplikacji


public void onStart() {
Log.v(TAG, "w metodzie onStart aktywnosci MainActivity");
super.onStart();
}
@Override
public void onResume() {
Log.v(TAG, "w metodzie onResume aktywnosci MainActivity");
super.onResume();
}
@Override
public void onPause() {
Log.v(TAG, "w metodzie onPause aktywnosci MainActivity");
super.onPause();
}
@Override
public void onStop() {
Log.v(TAG, "w metodzie onStop aktywnosci MainActivity");
super.onStop();
}
@Override
public void onSaveInstanceState(Bundle outState) {
Log.v(MainActivity.TAG, "w metodzie onSaveInstanceState aktywnosci
MainActivity");
super.onSaveInstanceState(outState);
}
@Override
public void onDestroy() {
Log.v(TAG, "w metodzie onDestroy aktywnosci MainActivity");
super.onDestroy();
}
public boolean isMultiPane() {
return getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
}

/**
* Funkcja pomocnicza ukazujca szczegy zaznaczonego elementu albo poprzez
* wywietlanie fragmentu znajdujcego si w biecym interfejsie uytkownika, albo
* poprzez rozpoczcie nowej aktywnoci, w ktrej bd one wywietlane.
*/
public void showDetails(int index) {
Log.v(TAG, "w metodzie showDetails(" + index + ") aktywnosci MainActivity");
if (isMultiPane()) {

// Sprawdza, ktry fragment jest wywietlany, w razie potrzeby zamienia go.


DetailsFragment details = (DetailsFragment)
getFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {

// Tworzy nowy fragment w celu ukazania szczegw zaznaczenia.

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1037

details = DetailsFragment.newInstance(index);

// Przeprowadza operacj zastpienia istniejcego


// fragmentu fragmentem umieszczonym wewntrz ramki.
Log.v(TAG, "tuz przed uruchomieniem operacji FragmentTransaction...");
FragmentTransaction ft
= getFragmentManager().beginTransaction();
ft.setTransition(
FragmentTransaction.TRANSIT_FRAGMENT_FADE);

//ft.addToBackStack("details");
ft.replace(R.id.details, details);
ft.commit();
}
} else {

// W przeciwnym wypadku musimy uruchomi now aktywno


// wywietlajc fragment zawierajcy okno dialogowe z zaznaczonym tekstem.
Intent intent = new Intent();
intent.setClass(this, DetailsActivity.class);
intent.putExtra("indeks", index);
startActivity(intent);
}
}
}

Mamy do czynienia z bardzo prost aktywnoci. Jedynym powodem umieszczenia wszystkich


metod zwrotnych w kodzie rdowym jest moliwo wywietlania komunikatw w dzienniku.
Gdyby nie ten fakt, jedyn wymagan metod byaby onCreate(), z kolei metodami pomocniczymi s isMultiPane() oraz showDetails(). Trudno byoby wprowadzi prostsz metod od
ukazanej tu onCreate(). Suy ona tutaj wycznie do uruchomienia trybu debugowania menedera fragmentw oraz ustanowienia widoku treci w postaci ukadu graficznego z listingu
29.3. Aby zdefiniowa tryb wielopanelowy (na przykad w celu umieszczania obok siebie wielu
fragmentw), wykorzystujemy jedynie pooenie wywietlacza. Jeeli wywietlacz jest uoony
w trybie krajobrazowym, moemy korzysta z wielu paneli rwnoczenie; w trybie portretowym jest to niemoliwe. Na koniec zauwamy, e pomocnicza metoda showDetails() suy do
zdefiniowania sposobu wywietlania szczegw zaznaczonego tekstu. Indeks definiuje w tym
przypadku pozycj na licie tytuw. Jeeli aplikacja pracuje w trybie wielopanelowym, wykorzystamy fragment do wywietlenia tekstu. Fragment ten zosta nazwany DetailsFragment i do
jego utworzenia (wraz z indeksem) stosujemy metod fabrykujc. Kod klasy DetailsFragment
zosta zaprezentowany na listingu 29.5. Pniej jeszcze powrcimy do metody showDetails().
Listing 29.5. Kod rdowy klasy DetailsFragment
import
import
import
import
import
import
import
import
import

android.app.Activity;
android.app.Fragment;
android.os.Bundle;
android.util.AttributeSet;
android.util.Log;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.widget.TextView;

1038 Android 3. Tworzenie aplikacji


public class DetailsFragment extends Fragment {
private int mIndex = 0;
public static DetailsFragment newInstance(int index) {
Log.v(MainActivity.TAG, "w metodzie newInstance(" +
index + ") klasy DetailsFragment");
DetailsFragment df = new DetailsFragment();

// Dostarcza indeks w postaci argumentu.


Bundle args = new Bundle();
args.putInt("indeks", index);
df.setArguments(args);
return df;
}
public static DetailsFragment newInstance(Bundle bundle) {
int index = bundle.getInt("indeks", 0);
return newInstance(index);
}
@Override
public void onInflate(AttributeSet attrs, Bundle savedInstanceState)
{
Log.v(MainActivity.TAG,
"w metodzie onInflate klasy DetailsFragment. Interfejs AttributeSet
zawiera:");
for(int i=0; i<attrs.getAttributeCount(); i++)
Log.v(MainActivity.TAG, " " + attrs.getAttributeName(i) +
" = " + attrs.getAttributeValue(i));
super.onInflate(attrs, savedInstanceState);
}
@Override
public void onAttach(Activity myActivity) {
Log.v(MainActivity.TAG,
"w metodzie onAttach klasy DetailsFragment; aktywnoscia jest: " +
myActivity);
super.onAttach(myActivity);
}
@Override
public void onCreate(Bundle myBundle) {
Log.v(MainActivity.TAG,
"w metodzie onCreate klasy DetailsFragment. Pakiet zawiera:");
if(myBundle != null) {
for(String key : myBundle.keySet()) {
Log.v(MainActivity.TAG, " " + key);
}
}
else {
Log.v(MainActivity.TAG, " Obiekt myBundle jest pusty");
}
super.onCreate(myBundle);
mIndex = getArguments().getInt("indeks", 0);

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1039

}
public int getShownIndex() {
return mIndex;
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
Log.v(MainActivity.TAG,
"w metodzie onCreateView klasy DetailsFragment. pojemnik = " +
container);

// Nie wimy tego fragmentu z niczym za pomoc obiektu pompujcego.


// Android zajmuje si za nas przyczaniem fragmentw.
// Pojemnik jest jedynie przepuszczany, wic wiemy dziki niemu,
// dokd trafi hierarchia widokw.
View v = inflater.inflate(R.layout.details, container, false);
TextView text1 = (TextView) v.findViewById(R.id.text1);
text1.setText(Shakespeare.DIALOGUE[ mIndex ] );
return v;
}
@Override
public void onActivityCreated(Bundle savedState) {
Log.v(MainActivity.TAG,
"w metodzie onActivityCreated klasy DetailsFragment. Klasa savedState
zawiera:");
if(savedState != null) {
for(String key : savedState.keySet()) {
Log.v(MainActivity.TAG, " " + key);
}
}
else {
Log.v(MainActivity.TAG, " Klasa savedState jest pusta");
}
super.onActivityCreated(savedState);
}
@Override
public void onStart() {
Log.v(MainActivity.TAG, "w metodzie onStart klasy DetailsFragment");
super.onStart();
}
@Override
public void onResume() {
Log.v(MainActivity.TAG, "w metodzie onResume klasy DetailsFragment");
super.onResume();
}
@Override
public void onPause() {
Log.v(MainActivity.TAG, "w metodzie onPause klasy DetailsFragment");
super.onPause();
}

1040 Android 3. Tworzenie aplikacji


@Override
public void onSaveInstanceState(Bundle outState) {
Log.v(MainActivity.TAG,
"w metodzie onSaveInstanceState klasy DetailsFragment");
super.onSaveInstanceState(outState);
}
@Override
public void onStop() {
Log.v(MainActivity.TAG, "w metodzie onStop klasy DetailsFragment");
super.onStop();
}
@Override
public void onDestroyView() {
Log.v(MainActivity.TAG,
"w metodzie onDestroyView klasy DetailsFragment, widok = " +
getView());
super.onDestroyView();
}
@Override
public void onDestroy() {
Log.v(MainActivity.TAG, "w metodzie onDestroy klasy DetailsFragment");
super.onDestroy();
}
@Override
public void onDetach() {
Log.v(MainActivity.TAG, "w metodzie onDetach klasy DetailsFragment");
super.onDetach();
}
}

Klasa DetailsFragment jest w istocie rwnie nieskomplikowana. Jedynym powodem duych


rozmiarw kodu jest wprowadzenie instrukcji sucych do wywietlania informacji w dzienniku. Gdyby nie byy nam one potrzebne, w powyszym fragmencie pozostawilibymy jedynie
metody newInstance(), getShownIndex(), onCreate() oraz onCreateView(). Teraz Czytelnik ju wie, w jaki sposb utworzono wystpienie tego fragmentu. Wana jest informacja, e
wystpienie tego fragmentu jest tworzone w kodzie, poniewa ukad graficzny definiuje pojemnik ViewGroup (dokadniej obiekt FrameLayout), do ktrego trafi fragment przechowujcy
szczegowe informacje. Poniewa w przeciwiestwie do fragmentu zawierajcego tytuy omawiany fragment nie jest zdefiniowany w pliku ukadu graficznego aktywnoci, musimy tworzy
wystpienia fragmentw za pomoc kodu.
Aby utworzy nowy fragment przechowujcy szczegowe informacje, stosujemy metod
Jak ju wczeniej stwierdzilimy, ta metoda fabrykujca przywouje domylny
konstruktor, a nastpnie ustanawia pakiet argumentw za pomoc wartoci indeksu. Po uruchomieniu metody newInstance() fragment przechowujcy szczegy moe odczyta wartoci
indeksu w dowolnej metodzie zwrotnej poprzez odniesienie do pakietu argumentw za pomoc
metody getArguments(). Dla naszej wygody moemy zapisa w metodzie onCreate()
warto indeksu pochodzc z pakietu argumentw, dokadniej za w polu czonkowskim
klasy DetailsFragment.
newInstance().

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1041

Moemy si zastanawia, dlaczego po prostu nie wprowadzilimy wartoci mIndex w metodzie


newInstance(). Wynika to z faktu, e system poza naszym wzrokiem odtworzy fragment za
pomoc domylnego konstruktora. Nastpnie zostanie wykorzystany pakiet argumentw do
przywrcenia poprzedniego stanu. Android nie wykorzysta metody newInstance(), wic jedynym pewnym sposobem ustanowienia wartoci zmiennej mIndex jest odczytanie jej z pakietu
argumentw i wprowadzenie jej w metodzie onCreate(). Metoda zoona getShownIndex()
odczytuje wartoci tego indeksu. Do opisania pozostaa nam ju tylko metoda onCreateView(),
ktrej zrozumienie rwnie nie stanowi wielkiego wyzwania.
Zadaniem metody onCreateView() jest przekazywanie hierarchii widokw do fragmentu. Pamitajmy, e na podstawie konfiguracji chcemy uzyska wszystkie moliwe rodzaje ukadw
graficznych dotyczce tego fragmentu, zatem najczciej spotykan czynnoci jest spoytkowanie pliku ukadu graficznego tego fragmentu. W naszej przykadowej aplikacji takim plikiem
jest details.xml, ktry definiujemy za pomoc zasobu R.layout.details. Zawarto pliku
details.xml zostaa umieszczona na listingu 29.6.
Listing 29.6. Plik ukadu graficznego details.xml zdefiniowany dla fragmentu przechowujcego
szczegowe informacje
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout/details.xml -->


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView android:id="@+id/scroller"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/text1"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</ScrollView>
</LinearLayout>

W przypadku naszej przykadowej aplikacji moemy stosowa ten sam ukad graficzny dla trybu
krajobrazowego i portretowego podczas wywietlania szczegowych informacji. Jest to ukad
przeznaczony nie dla aktywnoci, lecz wycznie do wywietlania tekstu fragmentu. Poniewa
moe by on uznany za domylny ukad graficzny, moemy umieci go w katalogu res/layout,
gdzie zostanie znaleziony i zastosowany, nawet jeli wywietlacz znajduje si w trybie krajobrazowym. Podczas wyszukiwania pliku ukadu graficznego sucego do wywietlania szczegw
system sprawdza najpierw katalogi cile powizane z konfiguracj urzdzenia, powrci jednak
do katalogu res/layout, jeli nigdzie indziej nie znajdzie pliku details.xml. Oczywicie, jeeli chcemy
zaprojektowa inny ukad graficzny fragmentu dla trybu krajobrazowego, moemy zdefiniowa
osobny plik details.xml i umieci go w katalogu /res/layout-land. Nic nie stoi na przeszkodzie,
eby eksperymentowa z rnymi plikami details.xml.
W momencie wywoania metody onCreate() fragmentu przechowujcego szczegy system
wybierze i rozwinie ukad graficzny z odpowiedniego pliku details.xml, do ktrego wstawi tekst
z klasy Shakespeare. Nie zamiecimy tu caego kodu klasy Shakespeare, lecz jedynie jego cz
(listing 29.7), aby uatwi Czytelnikowi zrozumienie jego dziaania. Peny kod rdowy znajdziemy w gotowym projekcie, do ktrego adres zosta umieszczony w podrozdziale Odnoniki
na kocu rozdziau.

1042 Android 3. Tworzenie aplikacji


Listing 29.7. Kod rdowy klasy Shakespeare
public class Shakespeare {
public static String TITLES[] = {
"Henryk IV (1) (J. Paszkowski)",
"Henryk V (L. Ulrich)",
"Ryszard II (S. Komian)",
"Romeo i Julia (J. Paszkowski)",
"Hamlet (J. Paszkowski)",
"Kupiec wenecki (J. Paszkowski)",
"Otello (J. Paszkowski)"
};
public static String DIALOGUE[] = {
"Po tylu troskach, po tylu wtrznieniach,1\n...

...i tak dalej...

Zatem obecnie nasza hierarchia widokw we fragmencie zawierajcym szczegowe informacje przechowuje tekst z wybranej sztuki. Fragment ten jest ju przygotowany do wywietlenia. Moemy teraz wrci do metody showDetails(), aby zaj si omwieniem klasy
FragmentTransaction.

Klasy FragmentTransaction
i drugoplanowy stos fragmentw
Kod w metodzie showDetails(), ktry suy do wstawiania nowego fragmentu przechowujcego
szczegowe informacje (ukazany ponownie na listingu 29.8), wyglda do prosto, wykonuje
jednak mnstwo zada. Warto powici troch czasu na wyjanienie, co tu si dzieje i dlaczego tak
jest. Jeeli nasza aktywno dziaa w trybie wielopanelowym, chcemy zaprezentowa fragment
przechowujcy szczegy obok fragmentu zawierajcego list. By moe nasza aplikacja wywietla
ju ten pierwszy fragment, co oznacza, e sta si widoczny dla uytkownika. W kadym przypadku identyfikator zasobu R.id.details jest przeznaczony dla pojemnika FrameLayout
aktywnoci, co jest widoczne na listingu 29.3. Jeeli fragment przechowujcy szczegy znajduje
si wewntrz ukadu graficznego, z powodu braku wasnego identyfikatora otrzyma wanie
wspomniany identyfikator. Aby wic dowiedzie si, czy w ukadzie graficznym znajduje si jaki
fragment, moemy wysa zapytanie do metody findFragmentById() menedera fragmentw.
Zostanie przekazana albo warto null, jeli ukad graficzny jest pusty, albo informacje na temat
biecego fragmentu. Moemy wtedy zadecydowa, e powinnimy umieci nowy fragment
w ukadzie graficznym, poniewa ukad graficzny moe by pusty lub moe by w nim umieszczony ukad graficzny reprezentujcy szczegy nieodpowiedniego tytuu. Po podjciu decyzji
o utworzeniu i wykorzystaniu nowego fragmentu wywoujemy w tym celu metod fabrykujc.
Moemy teraz wstawi nowy fragment, ktry zostanie zaprezentowany uytkownikowi.
Listing 29.8. Przykad transakcji fragmentu
public void showDetails(int index) {
Log.v(TAG, "w metodzie showDetails(" + index + ") aktywnosci MainActivity");
if (isMultiPane()) {
1

Przekad J. Paszkowskiego przyp. tum.

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1043

// Sprawdza, ktry fragment jest wywietlany, podmienia go w razie potrzeby.


DetailsFragment details = (DetailsFragment)
getFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {

// Tworzy nowy fragment, sucy do wywietlania szczegw wybranego elementu.


details = DetailsFragment.newInstance(index);

// Przeprowadza transakcj i zamienia dowolny


// fragment na fragment umieszczony w ramce.
Log.v(TAG, "tuz przed uruchomieniem operacji FragmentTransaction...");
FragmentTransaction ft
= getFragmentManager().beginTransaction();
ft.setTransition(
FragmentTransaction.TRANSIT_FRAGMENT_FADE);

//ft.addToBackStack("details");
ft.replace(R.id.details, details);
ft.commit();
}

// Reszta zostaa pominita w celu zaoszczdzenia miejsca.


}

Kluczow koncepcj, ktr trzeba zrozumie, jest to, e fragment musi si znale w pojemniku widokw, zwanym rwnie grup widokw. Wynika to czciowo z faktu, e fragment
sam w sobie nie jest widokiem. Klasa ViewGroup zawiera takie elementy, jak ukady graficzne
i ich pochodne klasy. To wanie dlatego wybralimy klas FrameLayout do pliku main.xml
stanowicego ukad graficzny aktywnoci. Nasz fragment przechowujcy szczegy zostanie
umieszczony w pojemniku FrameLayout. Gdybymy zamiast tego zdefiniowali jeszcze jeden
wze <fragment> w pliku ukadu graficznego aktywnoci, nie moglibymy przeprowadzi wymaganej operacji zamiany. To wanie za pomoc klasy przeprowadzamy zamian fragmentw.
W czasie transakcji zamieniamy miejscami dowolny fragment umieszczony w ramce z nowym
fragmentem przechowujcym szczegy. Moglibymy rozwiza to w inny sposb, mianowicie
lokalizujc identyfikator zasobu kontrolki TextView, ktra przechowuje tekst szczegw,
i wprowadzajc w niej nowy tekst dla nowej wybranej sztuki Szekspira. Istnieje jednak jeszcze
jeden fakt dotyczcy fragmentw, przemawiajcy za korzystaniem z klasy FragmentTransaction.
Jak wiemy, aktywnoci s poukadane na stosie i w trakcie zagbiania si w aplikacj okazuje
si, e czsto na stosie znajduje si kilka jednoczenie uruchomionych aktywnoci. Po wciniciu przycisku cofania aktywno znajdujca si na wierzchu stosu jest z niego usuwana, a jej
miejsce zajmuje nastpna w kolejce aktywno, ktra zostaje wznowiona. Proces ten jest przeprowadzany wzdu caego stosu a do poziomu ekranu startowego.
Stanowio to znakomite rozwizanie w przypadku prostych aktywnoci, teraz jednak omawiamy
takie, ktre zawieraj po kilka jednoczenie dziaajcych fragmentw. Ponadto, skoro moemy
coraz bardziej zagbia si w aplikacj bez koniecznoci opuszczania aktywnoci znajdujcej
si na wierzchu stosu, trzeba byo rozwin koncepcj korzystania z przycisku cofania w taki
sposb, aby uwzgldni take fragmenty. W istocie fragmenty wymagaj zastosowania tej koncepcji nawet bardziej ni proste aktywnoci. Gdy mamy do czynienia z kilkoma fragmentami
oddziaujcymi ze sob rwnoczenie wewntrz aktywnoci i nastpuje jednoczesne przejcie
do nowej treci, ktre dotyczy wszystkich tych fragmentw, to wcinicie przycisku cofania powinno sprawi, e fragmenty te razem zostan cofnite o jeden etap. Aby zapewni, e to cofnicie
obejmie wszystkie fragmenty w ramach danej aktywnoci, utworzono klas FragmentTransaction
zapewnia ona koordynacj tego procesu.

1044 Android 3. Tworzenie aplikacji


Naley pamita, e drugoplanowy stos fragmentw nie jest wymagany we wntrzu aktywnoci. Moemy zaprogramowa dziaanie przycisku cofania w taki sposb, eby obejmowa on
wycznie aktywno, a nie fragmenty. Jeeli nie zdefiniujemy drugoplanowego stosu fragmentw, wcinicie przycisku cofania spowoduje usunicie biecej aktywnoci ze stosu i powrt
do wczeniejszej aktywnoci. Jeli za Czytelnik postanowi wykorzysta moliwoci oferowane przez stos fragmentw, powinien na listingu 29.8 usun znaki komentarza z wiersza
ft.addToBackStack("details"). W tym konkretnym przypadku zamiecilimy w kodzie
parametr znacznika w postaci cigu znakw details. Znacznik ten powinien stanowi cig
znakw symbolizujcy stan fragmentw w momencie przeprowadzania transakcji. Moemy
w kodzie modyfikowa stos drugoplanowy za pomoc wartoci znacznika, co umoliwia usunicie pewnych wpisw czy te uniknicie innych. Powinnimy nadawa przemylane nazwy
znacznikom transakcji, aby mc je pniej atwiej znajdowa.

Przejcia i animacje zachodzce


podczas transakcji fragmentu
Jedn z wyjtkowo eleganckich cech transakcji fragmentu jest moliwo zilustrowania zamiany starego fragmentu na nowy za pomoc przej i animacji. Nie mamy tu do czynienia
z animacjami omawianymi w rozdziaach 16. i 20. Animacje przedstawione w tym rozdziale s
o wiele prostsze i nie wymagaj zaawansowanej wiedzy o grafice. Warto wykorzysta jedno
z przej pozwalajcych na dodanie efektw specjalnych podczas zmiany starego fragmentu na
nowy. W ten sposb nasza aplikacja stanie si bardziej elegancka, a zmiany fragmentw nabior
pynnoci. Jedn z pozwalajcych na to metod jest widoczna na listingu 29.8 setTransition().
Mamy jednak do dyspozycji rwnie kilka innych przej. W naszym przykadzie skorzystalimy z efektu zanikania, moemy jednak wprowadzi metod setCustomAnimations() do opisania innych efektw specjalnych, na przykad wsuwania si jednego fragmentu z prawej strony
ekranu, a drugiego z lewej. Niestandardowe animacje wykorzystuj nowe definicje obiektw
animacji, a nie stare. Stare pliki animacji zawieraj takie znaczniki, jak <translate>, podczas
gdy w nowych stosowane s znaczniki typu <objectAnimator>. Stare pliki animacji znajduj
si w katalogu /data/res/anim w odpowiednim miejscu platformy Android SDK (na przykad
platforms/android-11 dla systemu Honeycomb). Znajdziemy tu rwnie nowe pliki umieszczone
w katalogu /data/res/animator. Kod przejcia moe wyglda nastpujco:
ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);

Powoduje on, e stary fragment zanika, podczas gdy nowy stopniowo si pojawia. Pierwszy parametr odnosi si do nowego fragmentu, a drugi do fragmentu usuwanego. Warto przejrze
katalog animator, aby zapozna si z innymi domylnymi animacjami. Jeeli chcemy utworzy
wasn animacj, w dalszej czci rozdziau znajdziemy sekcj powicon animatorowi obiektw. Bardzo wana jest rwnie informacja, e wywoanie przejcia musi nastpi przed wywoaniem metody replace(), w przeciwnym wypadku zostanie ono zignorowane.
Uywanie animatora obiektw do programowania efektw specjalnych widocznych podczas
zmiany fragmentw moe by zabawne. W klasie FragmentTransaction znajdziemy jeszcze
dwie metody, z ktrymi powinnimy si zapozna: hide() i show(). Parametrem w przypadku
obydwu tych metod jest fragment. Zadania, jakie metody te realizuj, wynikaj z ich nazw.
W przypadku fragmentu powizanego z kontenerem widokw powoduj one jego ukrywanie
lub wywietlanie w interfejsie uytkownika. W ten sposb fragment nie zostaje usunity z menedera fragmentw, musi by jednak zwizany z kontenerem widokw, aby metody miay
wpyw na jego widoczno. Jeeli fragment nie zawiera hierarchii widokw lub hierarchia ta nie
jest powizana z hierarchi wywietlanych widokw, metody te oka si bezuyteczne.

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1045

Po zdefiniowaniu efektw specjalnych wystpujcych w czasie transakcji fragmentu musimy


okreli rwnie gwn czynno. W naszym przypadku zamieniamy fragment znajdujcy si
w ramce na nowy fragment przechowujcy szczegy. Posuymy si tutaj metod replace().
Stanowi ona ekwiwalent wywoania metody remove() wobec dowolnych fragmentw znajdujcych si w ramce, a nastpnie metody add() wobec nowego fragmentu zawierajcego szczegy, co oznacza, e w razie potrzeby moemy po prostu wywoywa zamiast niej metody
remove() i add().
Ostatnim dziaaniem, jakie musimy podj podczas transakcji fragmentu, jest jej zatwierdzenie.
Metoda commit() nie powoduje natychmiastowego wykonania czynnoci, lecz raczej ustanowienie jej harmonogramu na czas, gdy wtek interfejsu uytkownika zostanie przygotowany.
Teraz ju Czytelnik powinien rozumie, dlaczego zmiana treci w pojedynczym fragmencie jest
taka kopotliwa. Zmieniamy tu nie tylko tekst; moemy w trakcie przejcia wstawi specjalne
efekty graficzne. Istnieje rwnie moliwo zapisania szczegw przejcia w transakcji fragmentu, dziki czemu proces ten moe zosta pniej odwrcony. Ostatnie zdanie moe wydawa si nieco niezrozumiae, dlatego Czytelnikowi naley si wyjanienie.
Nie mamy tu do czynienia z transakcj w dosownym tego sowa znaczeniu. Gdy usuwamy
transakcje fragmentw ze stosu drugoplanowego, nie cofamy zmian w danych, ktre mogy
zosta wprowadzone. Jeeli zmiany zostay wprowadzone w obrbie aktywnoci, na przykad
w trakcie tworzenia transakcji fragmentw w stosie drugoplanowym, wcinicie przycisku cofania nie sprawi, e zmienione wartoci danych zostan przywrcone do stanu pocztkowego.
Cofamy si jedynie poprzez widoki interfejsu uytkownika, podobnie jak miao do miejsce
w przypadku aktywnoci, teraz jednak dotyczy to fragmentw. Poniewa fragmenty s zapisywane i odczytywane w taki, a nie inny sposb, wewntrzny stan fragmentu odczytanego z atrybutw zachowanego stanu bdzie zalee od wartoci zachowanych we fragmencie oraz od
sposobu ich odczytania. Zatem fragmenty mog wyglda tak jak wczeniej, nie mona jednak
bdzie tego powiedzie o aktywnoci, chyba e w trakcie odczytywania stanu fragmentw bdziemy odczytywa rwnie stan aktywnoci.
W naszym przykadzie pracujemy tylko z jednym pojemnikiem widokw i wprowadzamy tylko
jeden fragment przechowujcy szczegy. W przypadku bardziej zoonych interfejsw uytkownika moemy manipulowa pozostaymi fragmentami za pomoc transakcji. W rzeczywistoci zajmujemy si tyko rozpoczciem transakcji, co oznacza, e zamieniamy stary fragment
przechowujcy szczegy w ramce na nowy fragment, okrelamy animacj przejcia oraz zatwierdzamy przeprowadzenie tego procesu. Oznaczylimy jako komentarz cz kodu, w ktrej
transakcja jest dodawana do stosu drugoplanowego, mona jednak usun z tego fragmentu
znaki komentarza i tym samym doczy j do stosu.

Klasa FragmentManager
Klasa FragmentManager jest skadnikiem obsugujcym fragmenty przechowywane w aktywnoci.
Zaliczaj si do nich rwnie fragmenty przechowywane w stosie drugoplanowym oraz niepodczone fragmenty. Wyjanijmy to. Fragmenty powinny by tworzone wycznie w kontekcie aktywnoci. Dzieje si to albo poprzez rozwinicie ukadu graficznego aktywnoci, albo poprzez bezporednie utworzenie wystpienia obiektu, co zostao ukazane na listingu 29.1. W tym
drugim przypadku fragment zostaje zazwyczaj doczony do aktywnoci za pomoc transakcji, natomiast w kadym wypadku uzyskujemy dostp i zarzdzamy fragmentami poprzez klas
FragmentManager.

1046 Android 3. Tworzenie aplikacji


Metod getFragmentManager() wykorzystujemy wobec aktywnoci lub wobec przyczonego
fragmentu, aby uruchomi meneder fragmentw. Z listingu 29.8 wiemy, e to wanie z poziomu menedera fragmentw uzyskujemy dostp do transakcji. Poza tym za pomoc menedera moemy odczyta identyfikator fragmentu, jego znacznik lub kombinacj pakiet atrybutw klucz, by w ten sposb znale dany fragment.
W tym celu mamy do dyspozycji metody pobierajce findFragmentById(), findFragment
ByTag() oraz getFragment(). Ta ostatnia moe zosta wykorzystana razem z metod put
Fragment(), ktra rwnie pobiera pakiet atrybutw, klucz oraz wstawiany fragment. Bdziemy
mieli najprawdopodobniej do czynienia z pakietem savedState, a metoda putFragment()
bdzie wstawiona w metodzie zwrotnej onSaveInstanceState(), dziki czemu zostanie zachowany stan biecej aktywnoci (lub innego fragmentu). Metoda getFragment() moe prawdopodobnie zosta wywoana w metodzie onCreate(), aby miaa zwizek z metod putFragment(),
chocia jak ju zostao wczeniej omwione w przypadku fragmentu pakiet atrybutw jest
rwnie dostpny dla pozostaych metod zwrotnych.
Oczywicie, nie moemy stosowa metody getFragmentManager() wobec fragmentw, ktre
nie zostay podczone do aktywnoci. Prawdziwe jest jednak rwnie stwierdzenie, e moemy
doczy fragment do aktywnoci w taki sposb, e nie bdzie jeszcze widoczny dla uytkownika.
Jeeli zdecydujemy si na to rozwizanie, naprawd powinnimy doczy znacznik zawierajcy
okrelony cig znakw do fragmentu, dziki czemu nie bdziemy mieli pniej problemw
z jego wyszukaniem. Prawdopodobnie wykorzystamy w tym celu nastpujc metod klasy
FragmentTransaction:
public FragmentTransaction add (Fragment fragment, String tag)

W rzeczywistoci moemy otrzyma fragment, ktry nie eksponuje hierarchii widokw. Moe
si to okaza przydatne, jeli zechcemy zawrze okrelon logik w taki sposb, aby mc j
doczy do aktywnoci, lecz jednoczenie pozostawi jej pewn doz autonomii, odgradzajc
j od cyklu ycia aktywnoci i pozostaych fragmentw. Gdy aktywno przechodzi przez
cykl odtwarzania wynikajcy ze zmiany konfiguracji urzdzenia, taki fragment niebdcy
czci interfejsu uytkownika moe pozostawa w duej czci nietknity, podczas gdy sama
aktywno zostaje usunita i na jej miejsce wkracza nowa. Takie rozwizanie jest interesujc
opcj dla metody setRetainInstance().
Meneder fragmentw obsuguje rwnie stos drugoplanowy. Podczas transakcji fragmentw
system umieszcza fragmenty na tym stosie, podczas gdy meneder fragmentw moe je stamtd usuwa. Zazwyczaj dokonujemy tego przy uyciu identyfikatora lub znacznika fragmentu,
rwnie dobrze moemy jednak w tym celu wprowadzi pozycj w stosie lub po prostu usun
fragment znajdujcy si na wierzchu.
Na koniec naley stwierdzi, e meneder fragmentw zawiera metody uatwiajce proces debugowania, w tym takie jak umoliwiajce umieszczanie komunikatw w oknie LogCat (metoda
enableDebugLogging()) lub umieszczanie biecego stanu menedera fragmentw w strumieniu (metoda dump()). Zwrmy uwag, e na listingu 29.4 wczylimy tryb debugowania menedera fragmentw w metodzie onCreate().

Ostrzeenie dotyczce stosowania odniesie do fragmentw


Nadszed czas, aby powrci do dyskusji na temat cyklu ycia fragmentu, argumentw i pakietw z zachowanymi stanami. System moe zachowa jeden z fragmentw w wielu rnych
sytuacjach. Oznacza to, e w momencie, gdy trzeba bdzie odczyta dany fragment, moe go nie

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1047

by w pamici. Z tego wanie powodu ostrzegamy przed uznaniem zmiennego odniesienia do


fragmentu za obiekt, ktry zachowuje wano przez dugi czas. Jeeli fragmenty s wymieniane
w pojemniku widokw za pomoc transakcji, kade odniesienie do poprzedniego fragmentu
bdzie teraz wskazywa fragment, ktry prawdopodobnie znajduje si w stosie drugoplanowym. Ewentualnie fragment moe zosta odczony od hierarchii widokw aktywnoci w trakcie zmiany konfiguracji urzdzenia, na przykad w momencie zmiany pozycji urzdzenia.
Bdmy ostroni.
Jeeli Czytelnik zamierza przechowywa odniesienie do fragmentu, powinien wiedzie, kiedy
moe ono zosta zachowane. W przypadku koniecznoci jego znalezienia naley wykorzysta
jedn z metod pobierania, zawart w menederze fragmentw. Jeeli chcemy koniecznie zatrzyma odniesienie do fragmentu, na przykad podczas odtwarzania aktywnoci w trakcie zmiany
konfiguracji, moemy zastosowa metod putFragment() wraz z odpowiednim pakietem atrybutw. W przypadku zarwno aktywnoci, jak i fragmentw takim pakietem jest savedState
wykorzystywany w metodzie onSaveInstanceState() oraz ponownie pojawiajcy si w metodzie onCreate() (lub innych wczenie wystpujcych metodach zwrotnych cyklu ycia fragmentu). Prawdopodobnie Czytelnik nigdy nie bdzie bezporednio przechowywa odniesienia
do fragmentu w pakiecie argumentw; jeeli kto poczuje jednak tak pokus, powinien to
bardzo dokadnie przemyle.
Innym mechanizmem pozwalajcym na uzyskanie dostpu do okrelonego fragmentu jest wysanie zapytania zawierajcego jego identyfikator lub znacznik. Uprzednio omwione metody
pobierajce pozwalaj na odczytanie w ten sposb fragmentw z menedera, co oznacza, e posiadamy moliwo zachowania takiego znacznika lub identyfikatora, dziki czemu moemy za
ich pomoc uzyska dostp do danego fragmentu, co jest alternatyw metod putFragment()
i getFragment().

Klasa ListFragment i wze <fragment>


Aby nasza aplikacja zyskaa pen funkcjonalno, musimy zaj si jeszcze kilkoma zagadnieniami. Pierwszym z nich jest klasa TitlesFragment. To wanie ona zostaje utworzona
za pomoc pliku layout.xml naszej aktywnoci. Znacznik <fragment> gra rol wypeniacza,
w ktrym zostanie umieszczony omawiany fragment. Nie ma tu zdefiniowanej hierarchii widokw tego fragmentu. Kod klasy TitlesFragment zosta umieszczony na listingu 29.9. Klasa
ta suy do wywietlania listy tytuw.
Listing 29.9. Kod klasy TitlesFragment
import
import
import
import
import
import
import
import
import
import

android.app.Activity;
android.app.ListFragment;
android.os.Bundle;
android.util.AttributeSet;
android.util.Log;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.widget.ArrayAdapter;
android.widget.ListView;

public class TitlesFragment extends ListFragment {


private MainActivity myActivity = null;
int mCurCheckPosition = 0;

1048 Android 3. Tworzenie aplikacji


@Override
public void onInflate(AttributeSet attrs, Bundle savedInstanceState) {
Log.v(MainActivity.TAG,
"w metodzie onInflate klasy TitlesFragment. Obiekt AttributeSet zawiera:");
for(int i=0; i<attrs.getAttributeCount(); i++) {
Log.v(MainActivity.TAG, " " + attrs.getAttributeName(i) +
" = " + ("id".equals(attrs.getAttributeName(i))?
Integer.toHexString(attrs.getAttributeIntValue(i, -1)):
attrs.getAttributeValue(i)));
}
super.onInflate(attrs, savedInstanceState);
}
@Override
public void onAttach(Activity myActivity) {
Log.v(MainActivity.TAG,
"w metodzie onAttach klasy TitlesFragment; aktywnosc: " + myActivity);
super.onAttach(myActivity);
this.myActivity = (MainActivity)myActivity;
}
@Override
public void onCreate(Bundle myBundle) {
Log.v(MainActivity.TAG,
"w metodzie onCreate klasy TitlesFragment. Pakiet zawiera:");
if(myBundle != null) {
for(String key : myBundle.keySet()) {
Log.v(MainActivity.TAG, " " + key);
}
}
else {
Log.v(MainActivity.TAG, " Obiekt myBundle jest pusty");
}
super.onCreate(myBundle);
}
@Override
public View onCreateView(LayoutInflater myInflater,
ViewGroup container, Bundle myBundle) {
Log.v(MainActivity.TAG,
"w metodzie onCreateView klasy TitlesFragment. Pojemnikiem jest: "
+ container);
return super.onCreateView(myInflater, container, myBundle);
}
@Override
public void onActivityCreated(Bundle savedState) {
Log.v(MainActivity.TAG,
"w metodzie onActivityCreated klasy TitlesFragment. Ob. savedState zawiera:");
if(savedState != null) {
for(String key : savedState.keySet()) {
Log.v(MainActivity.TAG, " " + key);
}
}
else {
Log.v(MainActivity.TAG, " Obiekt savedState jest pusty");
}

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1049

super.onActivityCreated(savedState);

// Zapenia list tytuami pochodzcymi ze statycznej tabeli.


setListAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1,
Shakespeare.TITLES));
if (savedState != null) {

// Odczytuje ostatni stan sprawdzanej pozycji.


mCurCheckPosition = savedState.getInt("curChoice", 0);
}

// Uzyskuje dostp do widoku ListView klasy ListFragment i aktualizuje go.


ListView lv = getListView();
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
lv.setSelection(mCurCheckPosition);

// Aktywno zostaje utworzona, fragmenty staj si dostpne.


// Fragment przechowujcy szczegy zostaje zapeniony.
myActivity.showDetails(mCurCheckPosition);
}
@Override
public void onStart() {
Log.v(MainActivity.TAG, "w metodzie onStart klasy TitlesFragment");
super.onStart();
}
@Override
public void onResume() {
Log.v(MainActivity.TAG, "w metodzie onResume klasy TitlesFragment");
super.onResume();
}
@Override
public void onPause() {
Log.v(MainActivity.TAG, "w metodzie onPause klasy TitlesFragment");
super.onPause();
}
@Override
public void onSaveInstanceState(Bundle outState) {
Log.v(MainActivity.TAG, "w metodzie onSaveInstanceState klasy TitlesFragment");
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
@Override
public void onListItemClick(ListView l, View v, int pos, long id) {
Log.v(MainActivity.TAG,
"w metodzie onListItemClick klasy TitlesFragment. pozycja = "
+ pos);
myActivity.showDetails(pos);
mCurCheckPosition = pos;
}

1050 Android 3. Tworzenie aplikacji


@Override
public void onStop() {
Log.v(MainActivity.TAG, "w metodzie onStop klasy TitlesFragment");
super.onStop();
}
@Override
public void onDestroyView() {
Log.v(MainActivity.TAG, "w metodzie onDestroyView klasy TitlesFragment");
super.onDestroyView();
}
@Override
public void onDestroy() {
Log.v(MainActivity.TAG, "w metodzie onDestroy klasy TitlesFragment");
super.onDestroy();
}
@Override
public void onDetach() {
Log.v(MainActivity.TAG, "w metodzie onDetach klasy TitlesFragment");
super.onDetach();
myActivity = null;
}
}

Podobnie jak wczeniej wikszo zamieszczonego tu kodu jest zbdna z punktu widzenia dziaania aplikacji i suy jedynie do przechowania instrukcji wywietlajcych informacje w dzienniku, dziki czemu bdzie wiadomo, kiedy fragment przechodzi do okrelonego etapu cyklu ycia.
W przeciwiestwie do klasy DetailsFragment, w tym fragmencie metoda onCreateView() nie
posiada specjalnego przeznaczenia. Wynika to z faktu, e rozszerzamy klas ListFragment,
ktra ju zawiera widok ListView. Domylne ustawienia metody onCreateView() w klasie
ListView powoduj przekazanie widoku kontrolki ListView. Waciwe operacje s przeprowadzane dopiero na etapie wywoania metody onActivityCreated(). Do tego czasu moemy
by pewni, e zostanie utworzona hierarchia widokw aktywnoci wraz z hierarchi aktywnoci
fragmentu. Identyfikatorem zasobu dla kontrolki ListView jest android.R.id.list1. eby
jednak uzyska do niej odniesienie, naley wywoa metod getListView() wewntrz metody
onActivityCreated(). Poniewa jednak klasa ListFragment nie jest tym samym co kontrolka
ListView, nie podczamy adaptera bezporednio do widoku ListView. Musimy zamiast
tego uy metody setListAdapter() klasy ListFragment. Poniewa zostaa skonfigurowana
hierarchia widokw aktywnoci, moemy bezpiecznie powrci do aktywnoci, aby wywoa
metod showDetails().
Na tym etapie cyklu ycia aktywnoci dodalimy adapter do widoku listy, odczytalimy biec
pozycj (jeeli powrcilimy z etapu przywracania, spowodowanego na przykad zmian pooenia urzdzenia) oraz zaprogramowalimy aktywno (w metodzie showDetails()), aby
wprowadzia tekst zwizany z odpowiednim tytuem sztuki szekspirowskiej.
Klasa TitlesFragment rwnie posiada obiekt nasuchujcy listy, zatem gdy uytkownik zaznaczy inny tytu, system wywoa metod zwrotn onListItemClick() i zmieni tekst na odpowiadajcy danej sztuce, znowu za pomoc metody showDetails().

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1051

Kolejnym elementem rnicym ten fragment od wczeniej omawianego fragmentu przechowujcego szczegy jest zapisywanie stanu w pakiecie (wartoci wskazujcej biec pozycj na
licie) oraz odczytywanie jej w metodzie onCreate() podczas zamykania i odtwarzania fragmentu. W przeciwiestwie do fragmentu przechowujcego szczegy, ktry jest wymieniany
w kontrolce FrameLayout ukadu graficznego aktywnoci, mamy tu do czynienia z tylko jednym fragmentem przechowujcym tytuy. Jeli wic nastpuje zmiana konfiguracji i nasz fragment przechodzi przez operacj zapisywania i odczytywania, chcemy zapamita biec pozycj. W przypadku fragmentw przechowujcych szczegy odtwarzamy je bez koniecznoci
zapamitywania poprzedniego stanu.

Wywoywanie odrbnej aktywnoci w razie potrzeby


Istnieje cz kodu, o ktrej jeszcze nie wspominalimy chodzi o fragment metody show
Kod ten przydaje si wtedy, gdy urzdzenie znajdujce si w trybie portretowym
wywietla fragment przechowujcy szczegy, ktry wymiarami nie odpowiada fragmentowi
przechowujcemu tytuy. Potraktujmy to jako problem, chocia w przypadku tabletw nie
musimy si tym martwi. Poniewa jednak technika fragmentw staje si dostpna rwnie
w starszych wersjach Androida, bdziemy mogli korzysta z nich zarwno w telefonach, jak
i w tabletach. Oznacza to, e cakiem czsto bdziemy si spotyka z tym, e parametry ekranu
uniemoliwi dogodne wywietlenie fragmentu, ktry w normalnej sytuacji byby umieszczony
obok innych fragmentw. W takiej sytuacji musimy uruchomi oddzieln aktywno, suc
do wywietlania tego fragmentu. W naszym przykadzie postanowilimy zaimplementowa
w ten sposb aktywno przechowujc szczegy; jej kod znajdziemy na listingu 29.10.

Details().

Listing 29.10. Wywietlanie nowej aktywnoci, jeli okrelony fragment nie mieci si na ekranie
// Jest to plik DetailsActivity.java
import
import
import
import

android.app.Activity;
android.content.res.Configuration;
android.os.Bundle;
android.util.Log;

public class DetailsActivity extends Activity {


@Override
public void onCreate(Bundle savedInstanceState) {
Log.v(MainActivity.TAG, "w metodzie onCreate klasy DetailsActivity");
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE) {

// Jeeli ekran znajduje si w trybie krajobrazowym, oznacza to,


// e klasa MainActivity wywietla zarwno
// tytuy, jak i tekst, zatem niniejsza aktywno jest
// ju niepotrzebna. Zignorujmy j i pozwlmy klasie MainActivity
// zaj si wszystkimi zadaniami.
finish();
return;
}
if(getIntent() != null) {

// Jest to inny sposb utworzenia wystpienia fragmentu

1052 Android 3. Tworzenie aplikacji


// przechowujcego szczegy.
DetailsFragment details =
DetailsFragment.newInstance(getIntent().getExtras());
getFragmentManager().beginTransaction()
.add(android.R.id.content, details)
.commit();
}
}
}

Warto przyjrze si kilku interesujcym aspektom tego kodu. Przede wszystkim jest on naprawd prosty do implementacji. Dokonujemy prostego okrelenia trybu orientacji urzdzenia
i jeeli znajduje si ono w trybie portretowym, wstawiamy fragment przechowujcy szczegy
do oddzielnej aktywnoci. W trybie krajobrazowym aktywno MainActivity moe wywietla
obydwa fragmenty obok siebie, zatem nie ma potrzeby pokazywania dodatkowej aktywnoci.
Czytelnik moe si zastanawia, po co chcielibymy w ogle tworzy t aktywno w trybie
krajobrazowym. Odpowied jest prosta: nie chcemy. Jeeli jednak aktywno przechowujca
szczegy zostaa utworzona w trybie portretowym i uytkownik obrci urzdzenie do trybu
krajobrazowego, zostanie ona uruchomiona ponownie z powodu zmiany konfiguracji. Otrzymalimy wic dodatkow aktywno w trybie krajobrazowym. W tym momencie jedynym rozsdnym rozwizaniem okazuje si zakoczenie tej aktywnoci i przekazanie klasie MainActivity
wszystkich zada.
Kolejnym interesujcym aspektem aktywnoci przechowujcej szczegy jest brak moliwoci
ustawienia gwnego widoku treci za pomoc metody setContentView(). W jaki wic sposb
zostaje utworzony interfejs uytkownika? Jeeli przyjrzymy si uwanie wywoaniu metody
add() w transakcji fragmentu, zauwaymy, e pojemnik widokw, do ktrego dodajemy dany
fragment, zosta okrelony jako zasb android.R.id.content. Jest to gwny pojemnik widokw aktywnoci, zatem jeli doczamy do niego hierarchi widokw fragmentw, oznacza
to, e hierarchia ta staje si jedyn hierarchi widokw w aktywnoci. Do utworzenia nowego
fragmentu (na przykad przyjmujcego pakiet w postaci argumentu) wykorzystalimy tutaj dokadnie tak sam klas DetailsFragment co wczeniej, lecz wprowadzilimy inn metod
newInstance(), a nastpnie doczylimy go po prostu do gwnego poziomu hierarchii
widokw aktywnoci. W ten sposb fragment zostaje wywietlony we wntrzu tej aktywnoci.
Z punktu widzenia uytkownika oglda on teraz jedynie widok zawierajcy fragment, w ktrym jest przechowywany tekst sztuki Szekspira. Jeeli bdzie chcia przej do innego tytuu,
musi wcisn przycisk cofania, co spowoduje powrt do gwnej aktywnoci (przechowujcej
wycznie fragment z tytuami). Alternatywnym rozwizaniem jest obrt urzdzenia i przejcie
do trybu krajobrazowego. Wtedy w aktywnoci przechowujcej szczegy zostanie wywoana
metoda finish(), co spowoduje jej zamknicie i pojawienie si odtworzonej aktywnoci gwnej.
Kiedy urzdzenie znajduje si w trybie portretowym i jeli w gwnej aktywnoci nie wywietlamy fragmentu przechowujcego szczegy, naley utworzy osobny plik ukadu graficznego
main.xml, ktrego zawarto zostaa zaprezentowana na listingu 29.11.
Listing 29.11. Ukad graficzny aktywnoci dla trybu portretowego
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1053

android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.androidbook.fragments.bard.TitlesFragment"
android:id="@+id/titles"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

Oczywicie, ten ukad graficzny mona zmodyfikowa w dowolny sposb. W celach demonstracyjnych suy on jedynie do wywietlania fragmentu przechowujcego tytuy. Bardzo dobrze si stao, e klasa tego fragmentu nie wymaga duej iloci kodu do przetwarzania zmian
konfiguracji urzdzenia.
Ostatnim elementem, jaki chcemy doczy w tym przykadzie, jest plik AndroidManifest.xml,
zaprezentowany na listingu 29.12.
Listing 29.12. Plik AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0" package="com.androidbook.fragments.bard">
<uses-sdk android:minSdkVersion="11" />
<application android:icon="@drawable/icon"
android:label="Szekspir">
<activity
android:name="com.androidbook.fragments.bard.MainActivity"
android:label="Szekspir">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.androidbook.fragments.bard.DetailsActivity"
android:label="Szekspir szczegy">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

Jest to standardowy plik manifest. Widzimy gwn aktywno zawierajc kategori LAUNCHER,
dziki czemu zostanie ona umieszczona na licie aplikacji urzdzenia. Widzimy nastpnie
oddzieln aktywno DetailsActivity ze zdefiniowan kategori DEFAULT. W ten sposb
moemy uruchomi t aktywno za pomoc kodu, nie zostanie ona jednak umieszczona na
licie aplikacji.

1054 Android 3. Tworzenie aplikacji

Trwao fragmentw
W trakcie testowania omawianej aplikacji nie naley zapomina o obracaniu urzdzenia (w emulatorze dokonamy tego za pomoc skrtu klawiaturowego Ctrl+F11). Zauwaymy, e wraz z obrotem urzdzenia obraca si bd rwnie fragmenty. Jeeli Czytelnik bdzie ledzi komunikaty
w oknie LogCat, zauway, e aplikacja wygeneruje ich bardzo duo. W szczeglnoci naley
zwrci uwag na te komunikaty wywietlane w momencie obracania urzdzenia, ktre dotycz
fragmentw; usuwana i odtwarzana jest nie tylko aktywno, lecz rwnie fragmenty.
Dotychczas napisalimy jedynie niewielk ilo kodu do obsugi fragmentu przechowujcego
szczegy. Kod ten suy do zachowywania biecej pozycji na licie tytuw w przypadku ponownego uruchomienia aktywnoci. W przypadku fragmentw przechowujcych szczegy nie
musielimy wprowadza kodu obsugujcego zmiany konfiguracji, poniewa nie ma takiej potrzeby. Android sam obsuy przechowywanie fragmentw znajdujcych si w menederze, ich
zachowywanie, a nastpnie odczytywanie w przypadku odtwarzania stanu aktywnoci. Czytelnik
powinien mie ju wiadomo, e fragmenty otrzymywane po zmianie konfiguracji najprawdopodobniej nie s tymi samymi fragmentami, ktre wczeniej znajdoway si w pamici. Fragmenty te zostay zrekonstruowane. System zachowa pakiet argumentw oraz informacje o typie
fragmentu, a w przypadku kadego fragmentu przechowujcego zapisane informacje o stanie zachowa rwnie pakiet z atrybutami zachowanego stanu, suce do pniejszego ich odtworzenia.
Komunikaty wywietlane w oknie LogCat informuj nas o fragmentach przechodzcych przez
cykl ycia w synchronizacji z cyklem ycia aktywnoci. Zauwaymy, e fragment przechowujcy
szczegy zostaje odtworzony, lecz system nie wywouje ponownie metody newInstance(). Zamiast tego Android korzysta po prostu z domylnego konstruktora, nastpnie docza do niego
pakiet argumentw i rozpoczyna wywoywanie metod zwrotnych danego fragmentu. Dlatego
tak wane jest, aby nie umieszcza adnego wymylnego kodu w metodzie newInstance(),
poniewa w momencie odtwarzania fragmentu metoda ta zostanie pominita.
Czytelnik powinien ju take doceni moliwo wielokrotnego uytkowania fragmentw
w rnych miejscach. Fragment przechowujcy tytuy jest wykorzystywany w dwch rnych
ukadach graficznych, jeli jednak przyjrzymy si jego kodowi, zauwaymy, e atrybuty umieszczone w tych plikach nie maj wikszego znaczenia. Moglibymy utworzy dwa zupenie rnice si od siebie ukady graficzne, a kod tego fragmentu wygldaby dokadnie tak samo.
To samo mona powiedzie o fragmencie przechowujcym szczegy. Zosta on wprowadzony
do gwnego ukadu graficznego trybu krajobrazowego oraz w aktywnoci przechowujcej
szczegy. Take i w tym przypadku ukady graficzne mog si od siebie znacznie rni, a kod
fragmentu przechowujcego szczegy nie ulegby zmianom. Rwnie kod aktywnoci przechowujcej szczegy by bardzo prosty.
Do tej pory analizowalimy dwa typy fragmentw: klas bazow Fragment oraz jej podklas
ListFragment. Przejdziemy teraz do kolejnego elementu klasy Fragment, jakim jest klasa
podrzdna DialogFragment.

Fragmenty wywietlajce okna dialogowe


W rozdziale 8. omwilimy mechanizm okien dialogowych w wersjach systemu starszych od
3.0. Wraz z wersj 3.0 Androida wprowadzono nowy sposb pracy z oknami dialogowymi,
oparty na fragmentach. Spodziewamy si, e omwiony w rozdziale 8. protok zarzdzanych
okien dialogowych zostanie wyparty przez rozwizanie omwione poniej.

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1055

W tym podrozdziale Czytelnik dowie si, w jaki sposb wykorzystywa fragmenty do wywietlania
prostego okna dialogowego oraz niestandardowego okna dialogowego, ukazujcego tekst zachty.

Podstawowe informacje o klasie DialogFragment


Zanim zademonstrujemy przykadowe aplikacje wywietlajce okno dialogowe zachty oraz
alert, chcielibymy najpierw zapozna Czytelnika z teoretycznymi podstawami stosowania
fragmentw wywietlajcych okna dialogowe. Funkcjonalnoci zwizane z oknami dialogowymi
w Androidzie 3.0 zostay zawarte w klasie DialogFragment. Stanowi ona podelement klasy
Fragment i w istocie posiada waciwoci fragmentu. Bdzie to zatem nasza klasa bazowa, suca do obsugi okien dialogowych. Gdy ju utworzymy okno dialogowe pochodzce z tej klasy,
na przykad:
public class MyDialogFragment extends DialogFragment { ... }

moemy wywietli taki fragment MyDialogFragment w postaci okna dialogowego przy uyciu
transakcji fragmentu. Na listingu 29.13 umiecilimy pseudokod obrazujcy ten proces.
Listing 29.13. Wywietlanie fragmentu przechowujcego dialog
JakasAktywnosc
{

//...pozostae funkcje aktywnoci


public void showDialog()
{

//konstruuje klas MyDialogFragment


MyDialogFragment mdf = MyDialogFragment.newInstance(arg1,arg2);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
mdf.show(ft,"znacznik-mojego-dialogu");
}

//...pozostae funkcje aktywnoci


}

Zgodnie z listingiem 29.13, aby wywietli fragment przechowujcy okna dialogowe, naley
wykona nastpujce czynnoci:
1. Utworzenie fragmentu wywietlajcego okna dialogowe.
2. Uruchomienie transakcji fragmentu.
3. Wywietlenie okna dialogowego za pomoc transakcji utworzonej na etapie 2.
Przyjrzyjmy si kademu z wymienionych etapw.

Utworzenie fragmentu wywietlajcego okna dialogowe


Podczas tworzenia fragmentu wywietlajcego okna dialogowe kierujemy si takimi zasadami
jak w przypadku pozostaych typw fragmentw. Zalecanym wzorcem jest stosowanie metody
fabrykujcej, takiej jak newInstance(). W jej wntrzu powinnimy wykorzysta domylny
konstruktor, a nastpnie doda pakiet z argumentami, zawierajcy przekazywane parametry.
Metoda ta nie powinna suy do innych zada, poniewa chcemy mie pewno, e niczego
nie pominiemy w procesie odtwarzania fragmentu z zachowanego stanu. W takim przypadku
Android wywoa domylny konstruktor i odtworzy za jego pomoc pakiet zawierajcy argumenty.

1056 Android 3. Tworzenie aplikacji


Przesanianie metody onCreateView
Podczas dziedziczenia fragmentu wywietlajcego okna dialogowe musimy przesoni jedn
lub dwie metody w celu wprowadzenia hierarchii widokw tego okna dialogowego. Pierwsz
moliwoci jest przesonicie metody onCreateView() i uzyskanie widoku. Drug opcj stanowi przesonicie metody onCreateDialog() i otrzymanie obiektu Dialog (takiego, jaki by
tworzony przez klas AlertDialog.Builder).
Na listingu 29.14 zaprezentowalimy przykad przesaniania metody onCreateView().
Listing 29.14. Przesanianie metody onCreateView() klasy DialogFragment
MyDialogFragment
{

...inne funkcje
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState)
{

//Tworzy widok poprzez rozwinicie wybranego ukadu graficznego


View v =
inflater.inflate(R.layout.prompt_dialog,container,false);

//Moemy zlokalizowa widok i wprowadzi wartoci


TextView tv = (TextView)v.findViewById(R.id.promptmessage);
tv.setText(this.getPrompt());

//Moemy wprowadzi metody zwrotne dla przyciskw


Button dismissBtn = (Button)v.findViewById(R.id.btn_dismiss);
dismissBtn.setOnClickListener(this);
Button saveBtn = (Button)v.findViewById(
R.id.btn_save);
saveBtn.setOnClickListener(this);
return v;
}

...inne funkcje
}

W kodzie z listingu 29.14 wczytujemy widok zdefiniowany przez ukad graficzny. Nastpnie
wyszukujemy dwa przyciski i okrelamy dla nich metody zwrotne. W bardzo podobny sposb
tworzylimy wczeniej fragment przechowujcy szczegy. W przeciwiestwie jednak do prezentowanych wczeniej fragmentw, omawiany typ zawiera jeszcze jeden mechanizm pozwalajcy na utworzenie hierarchii widokw.
Przesanianie metody onCreateDialog
Alternatyw dla umieszczenia widoku w metodzie onCreateView() jest przesonicie metody
onCreateDialog() i dostarczenie wystpienia okna dialogowego. Na listingu 29.15 zosta
zaprezentowany przykadowy kod takiego rozwizania.

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1057

Listing 29.15. Przesonicie metody onCreateDialog() klasy DialogFragment


MyDialogFragment
{

...inne funkcje
@Override
public Dialog onCreateDialog(Bundle icicle)
{
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle("Tytu mojego okna dialogowego");
b.setPositiveButton("OK", this);
b.setNegativeButton("Anuluj", this);
b.setMessage(this.getMessage());
return b.create();
}

...inne funkcje
}

W tym przykadzie wykorzystujemy konstruktor alertw do utworzenia przekazywanego okna


dialogowego. Jest to skuteczne rozwizanie w przypadku prostych okien dialogowych. Pierwsze
rozwizanie, polegajce na przesoniciu metody onCreateView(), jest rwnie proste i zapewnia o wiele wiksz swobod.

Wywietlanie fragmentu przechowujcego okna dialogowe


Po utworzeniu fragmentu wywietlajcego okno dialogowe wymagane bdzie wykorzystanie
transakcji fragmentu. Podobnie jak w przypadku pozostaych typw fragmentw, take i tutaj
operacje przeprowadzane s za porednictwem transakcji.
Metoda show() przyjmuje transakcj w postaci parametru wejciowego. Widzimy to na listingu 29.13. Za pomoc transakcji metoda ta dodaje okno dialogowe do aktywnoci, a nastpnie zatwierdza t transakcj. Metoda show() nie dodaje jednak transakcji do stosu drugoplanowego. Jeeli chcemy, moemy najpierw doda transakcj do stosu, a nastpnie przekaza j
metodzie show(). Metoda ta w przypadku fragmentu wywietlajcego okna dialogowego posiada
nastpujce sygnatury:
public int show(FragmentTransaction transaction, String tag)
public int show(FragmentManager manager, String tag)

Pierwsza metoda show() wywietla okno dialogowe poprzez dodanie tego fragmentu do przekazanej transakcji wraz z okrelonym znacznikiem. Przekazuje ona nastpnie identyfikator
przeprowadzanej transakcji.
Druga metoda show() automatyzuje proces otrzymywania transakcji z menedera transakcji.
Jest to metoda skrtowa. Jednak w przypadku korzystania z niej tracimy moliwo umieszczenia transakcji w stosie drugoplanowym. Jeeli chcemy uzyska kontrol nad tym aspektem,
musimy zastosowa metod o pierwszej z wymienionych sygnatur. Druga metoda okazuje si
przydatna, gdy chcemy wywietli jedynie okno dialogowe i nie mamy innego powodu, aby
w danym momencie przeprowadza transakcj fragmentu.

1058 Android 3. Tworzenie aplikacji


Bardzo mi implikacj okna dialogowego w postaci fragmentu jest fakt, i meneder fragmentw zarzdza stanami w podstawowym zakresie. Jeli na przykad urzdzenie zostanie obrcone w chwili wywietlania okna dialogowego, zostanie ono odtworzone cakowicie bez
naszego udziau.
Fragment wywietlajcy okna dialogowe zawiera rwnie metody pozwalajce na kontrolowanie ramki, w ktrej jest wywietlany widok okna dialogowego, w tym takie waciwoci, jak
jej tytu lub wygld. Wicej opcji znajdziemy w dokumentacji klasy DialogFragment; cze do
tej dokumentacji zamieszczono na kocu rozdziau.

Odwoanie fragmentu wywietlajcego okna dialogowe


Fragment wywietlajcy okna dialogowe mona odwoa na dwa sposoby. Pierwszym z nich jest
jawne wywoanie metody dismiss() w odpowiedzi na wcinicie przycisku lub jakie dziaanie
na widoku okna dialogowego, co zostao ukazane na listingu 29.16.
Listing 29.16. Wywoanie metody dismiss()
if (someview.getId() == R.id.btn_dismiss)
{

//Wykorzystajmy jakie metody zwrotne powiadamiajce klientw


//tego okna dialogowego o jego odwoaniu,
//a nastpnie wywoajmy omawian metod.
dismiss();
return;
}

Metoda dismiss() usunie ten fragment z menedera fragmentw, a nastpnie przeprowadzi


odpowiedni transakcj. Jeeli dany fragment znajduje si na stosie drugoplanowym, metoda
ta spowoduje po prostu jego wycofanie, a na jego miejsce wejdzie poprzedni stan transakcji
fragmentu. Bez wzgldu na to, czy dostpny jest stos drugoplanowy, czy nie, wywoanie metody
dismiss() poskutkuje wywoaniem standardowych metod zwrotnych usuwajcych fragment
wywietlajcy okna dialogowe, w tym rwnie onDismiss().
Naley zauway, e obecno metody onDismiss() wcale nie musi oznacza wywoania metody dismiss(). Wynika to z faktu, i metoda onDismiss() jest wywoywana rwnie podczas
zmiany konfiguracji urzdzenia i z tego powodu nie nadaje si do okrelania czynnoci, jakie
uytkownik przeprowadzi na oknie dialogowym. Jeeli okno dialogowe jest wywietlane w trakcie zmiany trybu wywietlania obrazu, we fragmencie zostanie wywoana metoda onDismiss(),
nawet jeli uytkownik nie wcisn adnego przycisku w tym oknie dialogowym. Zamiast tego
powinnimy prawdopodobnie zawsze polega na jawnych zdarzeniach klikni przycisku znajdujcego si w widoku okna dialogowego.
Jeeli uytkownik wcinie przycisk cofania w trakcie wywietlania fragmentu zawierajcego
okno dialogowe, spowoduje to wywoanie metody zwrotnej onCancel() w tym fragmencie.
Domylnie system pozbdzie si fragmentu i nie bdzie trzeba samodzielnie wywoywa
metody dismiss(). Jeeli jednak chcemy, aby aktywno wywoujca zostaa powiadomiona
o anulowaniu okna dialogowego, bdziemy musieli w tym celu wprowadzi odpowiedni logik
do metody onCancel(). Na tym polega rnica pomidzy metodami onDismiss() i onCancel()

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1059

we fragmentach wywietlajcych okna dialogowe. W przypadku metody onDismiss() cay czas


nie bdziemy mieli pewnoci, co spowodowao jej wywoanie. Moglimy rwnie odnotowa
fakt, e fragment ten nie posiada metody cancel(), jedynie dismiss(), lecz jak ju stwierdzilimy w momencie wcinicia przycisku cofania system samodzielnie zajmuje si procesem jego anulowania czy te wycofania.
Alternatywnym sposobem wycofania fragmentu wywietlajcego okno dialogowe jest wprowadzenie innego fragmentu tego typu. Mechanizm wycofania starego fragmentu i wprowadzenia
nowego rni si nieco od zwyczajnego wycofania biecego fragmentu. Na listingu 29.17 zamiecilimy stosowny przykad.
Listing 29.17. Konfigurowanie dialogu przeznaczonego do stosu drugoplanowego
if (someview.getId() == R.id.btn_invoke_another_dialog)
{
Activity act = getActivity();
FragmentManager fm = act.getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.remove(this);
ft.addToBackStack(null);

//Warto null symbolizuje brak nazwy dla transakcji przeprowadzanej w stosie drugoplanowym
HelpDialogFragment hdf =
HelpDialogFragment.newInstance(R.string.helptext);
hdf.show(ft, "NA POMOC");
return;
}

W obrbie jednej transakcji usuwamy biecy fragment i dodajemy nowy fragment wywietlajcy okna dialogowe. Wizualnie poprzednie okno dialogowe znika, a w jego miejscu
pojawia si nowe. Jeeli uytkownik wcinie przycisk cofania, nowe okno dialogowe zostanie wycofane i zostanie wywietlone poprzednie okno dialogowe, poniewa zachowalimy t transakcj w stosie drugoplanowym. Jest to bardzo przydatny sposb wywietlania
na przykad okna dialogowego pomocy.

Skutki wycofania okna dialogowego


Gdy dodajemy dowolny fragment do menedera fragmentw, meneder ten bdzie zarzdza
jego stanami. Oznacza to, e w trakcie zmiany konfiguracji urzdzenia (wynikajcego na przykad ze zmiany orientacji wywietlacza) aktywno, wraz z fragmentami, zostanie uruchomiona
ponownie. Zostao to zademonstrowane na przykadzie zawierajcym cytaty z dzie Szekspira.
Zmiana konfiguracji urzdzenia nie wpywa na okna dialogowe, poniewa s one rwnie zarzdzane przez meneder fragmentw. Jednak niejawne zachowanie metod show() i dismiss()
sprawia, e jeli nie zachowamy ostronoci, do atwo moemy zgubi dany fragment wywietlajcy okna dialogowe. Metoda show() automatycznie dodaje fragment do menedera;
z kolei metoda dismiss() automatycznie go stamtd usuwa. By moe uzyskamy bezporedni
wskanik fragmentu przed jego wywietleniem, nie bdziemy jednak mogli doda pniej tego
fragmentu do menedera za pomoc metody show(), poniewa fragment moe zosta dodany

1060 Android 3. Tworzenie aplikacji


do tego menedera tylko jednorazowo. Moe zaistnie potrzeba odczytania tego wskanika poprzez odtworzenie aktywnoci. Jeeli jednak chcemy pokaza, a nastpnie wycofa okno dialogowe, fragment zostanie niejawnie usunity z menedera fragmentw, co jest jednoznaczne
z uniemoliwieniem jego odtworzenia i ponownego wskazania (poniewa meneder nie bdzie
posiada informacji, e fragment ten istnieje).
Jeeli chcemy utrzyma stan okna dialogowego po jego wycofaniu, bdziemy musieli go w jaki
sposb przechowa na zewntrz albo w nadrzdnej aktywnoci, albo we fragmencie niezwizanym z oknami dialogowymi, ktry nie zostanie szybko usunity.

Przykadowa aplikacja wykorzystujca klas DialogFragment


Utworzymy teraz przykadow aplikacj, za pomoc ktrej przedstawimy trzy koncepcje fragmentu wywietlajcego okna dialogowe. Przyjrzymy si rwnie mechanizmowi komunikacji
pomidzy fragmentem a przechowujc go aktywnoci. Aby tego dokona, bdziemy potrzebowa piciu plikw:
MainActivity.java jest gwn aktywnoci aplikacji. Bdzie ona wywietlaa prosty
widok zawierajcy tekst pomocniczy oraz menu, za pomoc ktrego bd uruchamiane
okna dialogowe.
PromptDialogFragment.java stanowi przykad fragmentu wywietlajcego okna
dialogowe, w ktrym zostaje zdefiniowany osobny plik ukadu graficznego. Jest tu
moliwa interakcja z uytkownikiem. Dostpne s trzy przyciski: Zachowaj, Wycofaj
(na przykad sucy do anulowania) oraz Pomoc.
AlertDialogFragment.java to przykadowy fragment wywietlajcy okna dialogowe,
ktry wykorzystuje klas AlertBuilder do utworzenia okna dialogowego wewntrz
tego fragmentu. Mamy tu do czynienia z klasyczn metod tworzenia okna dialogowego;
moemy wykorzysta tu wiedz nabyt podczas tworzenia zwykych okien dialogowych.
HelpDialogFragment.java jest bardzo prostym fragmentem wywietlajcym
komunikat pomocy, umieszczony w zasobach aplikacji. Dana wiadomo zostaje
okrelona w momencie tworzenia okna dialogowego. Obiekt ten jest wywietlany
zarwno z poziomu gwnej aktywnoci, jak i fragmentu wywietlajcego okno
zachty.
OnDialogDoneListener.java zawiera w sobie interfejs wymagany przez aktywno
w celu otrzymywania komunikatw pochodzcych z fragmentw. Stosujc ten
interfejs, stwierdzamy, e fragmenty nie musz posiada wielu informacji na temat
aktywnoci wywoujcej, wystarczy im informacja, e aktywno ta implementuje
omawiany interfejs. W ten sposb wszystkie funkcje mog si znajdowa na
swoich miejscach. Z punktu widzenia aktywnoci jest to bardzo popularny sposb
otrzymywania komunikatw z fragmentw bez posiadania zbyt wielu informacji
na ich temat.
Nasza przykadowa aplikacja zawiera trzy ukady graficzne: dla gwnej aktywnoci, dla fragmentu wywietlajcego okno zachty oraz dla fragmentu wywietlajcego okno pomocy. Zauwamy, e nie potrzebujemy ukadu graficznego dla fragmentu wywietlajcego alert, poniewa
jego utworzeniem zajmie si klasa AlertBuilder. Po utworzeniu i uruchomieniu aplikacji zobaczymy ekran widoczny na rysunku 29.3.

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1061

Rysunek 29.3. Interfejs uytkownika przykadowej aplikacji fragment przechowujcy okno dialogowe

Przykadowe okno dialogowe klasa MainActivity


Przejdmy do kodu rdowego. Na listingu 29.18 znajduje si kod gwnej aktywnoci.
Listing 29.18. Gwna aktywno fragmentu wywietlajcego okna dialogowe
// Jest to plik MainActivity.java
import
import
import
import
import
import
import
import
import

android.app.Activity;
android.app.FragmentManager;
android.app.FragmentTransaction;
android.os.Bundle;
android.util.Log;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.widget.Toast;

public class MainActivity extends Activity


implements OnDialogDoneListener
{
public static final String LOGTAG = "DialogFragmentDemo";
public static final String ALERT_DIALOG_TAG = "ALERT_DIALOG_TAG";
public static final String HELP_DIALOG_TAG = "HELP_DIALOG_TAG";
public static final String PROMPT_DIALOG_TAG = "PROMPT_DIALOG_TAG";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager.enableDebugLogging(true);
}

1062 Android 3. Tworzenie aplikacji


@Override
public boolean onCreateOptionsMenu(Menu menu){
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId() == R.id.menu_show_alert_dialog)
{
this.testAlertDialog();
return true;
}
if (item.getItemId() == R.id.menu_show_prompt_dialog)
{
this.testPromptDialog();
return true;
}
if (item.getItemId() == R.id.menu_help)
{
this.testHelpDialog();
return true;
}
return true;
}
private void testPromptDialog()
{
FragmentTransaction ft = getFragmentManager().beginTransaction();
PromptDialogFragment pdf =
PromptDialogFragment.newInstance("Wprowad jakie dane");
pdf.show(ft, PROMPT_DIALOG_TAG);
}
private void testAlertDialog()
{
FragmentTransaction ft = getFragmentManager().beginTransaction();
AlertDialogFragment adf =
AlertDialogFragment.newInstance("Komunikat alertu");
adf.show(ft, ALERT_DIALOG_TAG);
}
private void testHelpDialog()
{
FragmentTransaction ft = getFragmentManager().beginTransaction();
HelpDialogFragment hdf =
HelpDialogFragment.newInstance(R.string.help_text);

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1063

hdf.show(ft, HELP_DIALOG_TAG);
}
public void onDialogDone(String tag, boolean cancelled,
CharSequence message) {
String s = tag + " reaguje na: " + message;
if(cancelled)
s = tag + " zostao anulowane przez uytkownika";
Toast.makeText(this, s, Toast.LENGTH_LONG).show();
Log.v(LOGTAG, s);
}
}

Kod gwnej aktywnoci jest niezwykle prosty. W metodzie onCreate() ustanawiamy widok
treci i wczamy debugowanie menedera fragmentw. Widzimy nastpnie dwie metody zwizane z konfigurowaniem opcji menu. Wybr poszczeglnych opcji menu powoduje wywoanie
rnych metod o prostej budowie. Kada z tych metod wykonuje praktycznie takie samo zadanie: pozyskuje transakcj fragmentu, a nastpnie tworzy i wywietla dany fragment. Zwrmy
uwag, e kady fragment posiada niepowtarzalny znacznik, ktry zostaje dostarczony metodzie
show(). Znacznik ten zostaje powizany z fragmentem w menederze, dziki czemu moemy pniej lokalizowa fragmenty. Fragmenty mog rwnie same okrela warto znacznika
za pomoc metody getTag() klasy Fragment.
Ostatni definicj metody w naszej gwnej aktywnoci jest onDialogDone(), ktra jest czci
implementowanego interfejsu OnDialogDoneListener. Jak wida, omawiana metoda zwrotna
zawiera znacznik wywoujcego fragmentu, warto logiczn wskazujc, czy fragment zosta anulowany, oraz komunikat. Dla naszych celw wystarczy wiedza, e komunikat zostaje wywietlony
w oknie LogCat; jest on rwnie prezentowany uytkownikowi za pomoc kontrolki Toast.

Przykadowe okno dialogowe interfejs OnDialogDoneListener


Skoro pokazalimy, w jaki sposb ustali moment zniknicia okna dialogowego, utworzymy
interfejs obiektu nasuchujcego implementowany przez obiekty, ktre wywouj okna dialogowe. Kod tego interfejsu zosta zaprezentowany na listingu 29.19.
Listing 29.19. Interfejs obiektu nasuchujcego
// Jest to plik OnDialogDoneListener.java
/*
* Interfejs standardowo implementowany przez aktywno,
* dziki czemu okno dialogowe moe przesya komunikaty
* o zdarzeniach.
*/
public interface OnDialogDoneListener {
public void onDialogDone(String tag, boolean cancelled, CharSequence message);
}

Jak wida, mamy do czynienia z bardzo prostym interfejsem. Wybralimy tylko jedn metod
zwrotn dla tego interfejsu. Metoda ta koniecznie musi by zaimplementowana przez aktywno. Nasze fragmenty nie musz posiada informacji o szczegach aktywnoci wywoujcej, a jedynie o tym, e aktywno ta musi implementowa interfejs OnDialogDoneListener.

1064 Android 3. Tworzenie aplikacji


Fragmenty mog wic za pomoc tej metody zwrotnej komunikowa si z aktywnoci wywoujc. W zalenoci od przeznaczenia fragmentu w interfejsie tym moe si znajdowa wiele
metod zwrotnych. Nasza przykadowa aplikacja rozdziela interfejs od definicji klas fragmentw. Aby uatwi sobie zarzdzanie kodem, moemy zamieci interfejs obiektu nasuchujcego
fragment wewntrz samej definicji klasy fragmentu, a tym samym zapewni sobie wiksz
kontrol nad synchronizacj obiektu nasuchujcego z fragmentem.

Przykadowe okno dialogowe klasa PromptDialogFragment


Przyjrzyjmy si teraz pierwszemu fragmentowi PromptDialogFragment, ktrego ukad graficzny oraz kod Java zostay razem umieszczone na listingu 29.20.
Listing 29.20. Ukad graficzny i kod Java klasy PromptDialogFragment
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/prompt_dialog.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:padding="4dip"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/promptmessage"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:text="Wprowad tekst"
android:layout_weight="1"
android:layout_gravity="center_vertical|center_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="top|center_horizontal" />
<EditText
android:id="@+id/inputtext"
android:layout_height="wrap_content"
android:layout_width="400dip"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:scrollHorizontally="true"
android:autoText="false"
android:capitalize="none"
android:gravity="fill_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button android:id="@+id/btn_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

android:text="Zachowaj">
</Button>
<Button android:id="@+id/btn_dismiss"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="Wycofaj">
</Button>
<Button android:id="@+id/btn_help"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="Pomoc">
</Button>
</LinearLayout>
</LinearLayout>

// Jest to plik PromptDialogFragment.java


import
import
import
import
import
import
import
import
import
import
import
import

android.app.Activity;
android.app.DialogFragment;
android.app.FragmentTransaction;
android.content.DialogInterface;
android.os.Bundle;
android.util.Log;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.widget.Button;
android.widget.EditText;
android.widget.TextView;

public class PromptDialogFragment


extends DialogFragment
implements View.OnClickListener
{
private EditText et;
public static PromptDialogFragment
newInstance(String prompt)
{
PromptDialogFragment pdf = new PromptDialogFragment();
Bundle bundle = new Bundle();
bundle.putString("zachta",prompt);
pdf.setArguments(bundle);
return pdf;
}
@Override
public void onAttach(Activity act) {

// Jeeli aktywno, do ktrej doczylimy, nie


// posiada zaimplementowanego interfejsu OnDialogDoneListener,

1065

1066 Android 3. Tworzenie aplikacji


// poniszy wiersz spowoduje wywietlenie wyjtku
// ClassCastException. Jest to najwczeniejszy etap,
// w ktrym moemy przetestowa zachowanie aktywnoci.
OnDialogDoneListener test = (OnDialogDoneListener)act;
super.onAttach(act);
}
@Override
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
this.setCancelable(true);
int style = DialogFragment.STYLE_NORMAL, theme = 0;
setStyle(style,theme);
}
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle icicle)
{
View v = inflater.inflate(R.layout.prompt_dialog, container,
false);
TextView tv = (TextView)v.findViewById(R.id.promptmessage);
tv.setText(getArguments().getString("zachta"));
Button dismissBtn = (Button)v.findViewById(R.id.btn_dismiss);
dismissBtn.setOnClickListener(this);
Button saveBtn = (Button)v.findViewById(R.id.btn_save);
saveBtn.setOnClickListener(this);
Button helpBtn = (Button)v.findViewById(R.id.btn_help);
helpBtn.setOnClickListener(this);
et = (EditText)v.findViewById(R.id.inputtext);
if(icicle != null)
et.setText(icicle.getCharSequence("wejcie"));
return v;
}
@Override
public void onSaveInstanceState(Bundle icicle) {
icicle.putCharSequence("wejcie", et.getText());
super.onPause();
}
@Override
public void onCancel(DialogInterface di) {
Log.v(MainActivity.LOGTAG, "w metodzie onCancel () fragmentu PDF");
super.onCancel (di);
}
@Override
public void onDismiss(DialogInterface di) {
Log.v(MainActivity.LOGTAG, "w metodzie onDismiss() fragmentu PDF");
super.onDismiss(di);

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1067

}
public void onClick(View v)
{
OnDialogDoneListener act = (OnDialogDoneListener)getActivity();
if (v.getId() == R.id.btn_save)
{
TextView tv =
(TextView)getView().findViewById(R.id.inputtext);
act.onDialogDone(this.getTag(), false, tv.getText());
dismiss();
return;
}
if (v.getId() == R.id.btn_dismiss)
{
act.onDialogDone(this.getTag(), true, null);
dismiss();
return;
}
if (v.getId() == R.id.btn_help)
{
FragmentTransaction ft =
getFragmentManager().beginTransaction();
ft.remove(this);

// W tym przypadku chcemy wywietli tekst pomocy


// i po zakoczeniu powrci do poprzedniego okna dialogowego.
ft.addToBackStack(null);

//Warto null oznacza brak nazwy dla transakcji stosu drugoplanowego.

}
}

HelpDialogFragment hdf =
HelpDialogFragment.newInstance(R.string.help1);
hdf.show(ft, MainActivity.HELP_DIALOG_TAG);
return;
}

Ukad graficzny okna dialogowego zachty nie rni si od wczeniej tworzonych ukadw graficznych. Widzimy w nim kontrolk TextView zawierajc tekst zachty, kontrolk EditText,
w ktrej uytkownik wprowadza wasne dane, oraz trzy przyciski, zapewniajce obsug, kolejno,
zachowania danych wejciowych, wycofania (na przykad anulowania) fragmentu wywietlajcego okna dialogowe oraz wywietlania okna dialogowego pomocy.
Kod klasy PromptDialogFragment na pocztku niczym si nie rni od innych wczeniej
utworzonych fragmentw. Znalaza si tu statyczna metoda newInstance() suca do tworzenia nowych obiektw, a w jej wntrzu wywoujemy domylny konstruktor i generujemy pakiet
argumentw, ktry zostaje nastpnie doczony do tego obiektu. Nastpnie Czytelnik zapewne
spostrzeg co nowego w metodzie onAttach(). Chodzi o to, aby si upewni, e aktywno
doczajca posiada zaimplementowany interfejs OnDialogDoneListener. Aby to sprawdzi,
rzutujemy aktywno przekazywan do interfejsu OnDialogDoneListener. Jeeli interfejs nie
jest zaimplementowany w aktywnoci, zostanie wywietlony wyjtek ClassCastException.
Moglibymy sprbowa obej ten wyjtek i wprowadzi bardziej eleganckie rozwizanie, zaley
nam jednak na utrzymaniu jak najmniejszej zoonoci kodu.

1068 Android 3. Tworzenie aplikacji


Nastpnie umiecilimy metod zwrotn onCreate(). Zgodnie z powszechnym trendem zwizanym z prac z fragmentami nie tworzymy tutaj interfejsu uytkownika, lecz moemy zdefiniowa styl okna dialogowego. Jest to rozwizanie specyficzne dla fragmentw wywietlajcych
okna dialogowe. Moemy samodzielnie ustali styl i motyw lub okreli jedynie styl i wprowadzi motyw o wartoci 0 (zero), aby pozostawi systemowi swobod w kwestii jego doboru.
W metodzie onCreateView() tworzymy hierarchi widokw danego fragmentu wywietlajcego okno dialogowe. Podobnie jak w przypadku pozostaych typw fragmentw, nie doczamy hierarchii widokw do przekazywanego pojemnika widokw (na przykad poprzez
ustawienie wartoci false w parametrze attachToRoot). Nastpnie konfigurujemy metody
zwrotne przyciskw i wstawiamy tekst zachty do okna dialogowego, ktre zostao pierwotnie
przekazane do metody newInstance(). Na kocu sprawdzamy, czy poprzez pakiet zachowanych stanw nie s przekazywane wartoci. Wynikaoby z tego, e fragment zosta odtworzony,
najprawdopodobniej w wyniku zmiany konfiguracji, oraz e uytkownik mg ju wprowadzi jaki tekst. Jeli tak jest, musimy zapeni kontrolk EditText informacjami wprowadzonymi przez uytkownika. Pamitajmy, e z powodu zmiany konfiguracji mamy do czynienia
z innym obiektem widoku w pamici, zatem musimy go zlokalizowa i ustawi odpowiedni
tekst. Kolejn metod zwrotn jest onSaveInstanceState(); to wanie w niej zapisujemy
w pakiecie dowolny biecy tekst wprowadzony przez uytkownika.
Metody zwrotne onCancel() i onDismiss() zaprezentowalimy jedynie z powodu moliwoci zapisywania informacji w oknie dziennika, wic bez problemu zauwaymy, kiedy zostan
one uruchomione w trakcie cyklu ycia fragmentu.
Ostatnia metoda we fragmencie wywietlajcym okno zachty jest przeznaczona do obsugi
przyciskw. Po raz kolejny uzyskujemy odniesienie do otaczajcej aktywnoci i rzutujemy j
na interfejs, ktry jest przez ni implementowany. Jeeli uytkownik wcisn przycisk Zapisz,
pobieramy wpisany tekst i wywoujemy metod zwrotn interfejsu onDialogDone(). Jak zostao wczeniej ukazane, metoda ta pobiera nazw znacznika fragmentu, warto logiczn
wskazujc, czy dany fragment zosta anulowany, oraz komunikat, ktry w tym przypadku
jest tekstem wprowadzonym przez uytkownika.
Nastpnie wywoujemy metod dismiss(), aby usun fragment wywietlajcy okna dialogowe. Pamitajmy, e metoda ta nie tylko wizualnie usuwa fragment sprzed oczu uytkownika,
lecz rwnie wycofuje go z menedera fragmentw, wic fragment ten staje si zupenie niedostpny. Jeeli zostanie wcinity przycisk Wycofaj, ponownie wywoujemy metod zwrotn
interfejsu, tym razem niezawierajc komunikatu, i wywoujemy metod dismiss(). Z kolei jeli uytkownik wcinie przycisk Pomoc, nie chcemy w rzeczywistoci utraci fragmentu wywietlajcego okno dialogowe, wic przeprowadzamy nieco odmienn operacj. Zostaa ona
wczeniej omwiona. Aby zapamita okno zachty, do ktrego bdzie mona wrci pniej, musimy utworzy transakcj fragmentu usuwajc to okno i dodajc okno pomocy za
pomoc metody show(); powinnimy je umieci w stosie drugoplanowym. Zwrmy take
uwag na sposb utworzenia fragmentu wywietlajcego okno pomocy za pomoc odniesienia do identyfikatora zasobu. Oznacza to, e fragment ten moe by uyty wraz z dowolnym tekstem umieszczonym w aplikacji.

Przykadowe okno dialogowe klasa HelpDialogFragment


Niebawem zaprezentujemy kod fragmentu wywietlajcego okno pomocy, najpierw jednak
opiszemy mechanizm jego dziaania. Utworzylimy transakcj fragmentu powodujc przejcie
od fragmentu wywietlajcego okno zachty do fragmentu przechowujcego okno pomocy

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1069

i umiecilimy j na stosie drugoplanowym. W wyniku tego fragment wywietlajcy okno zachty znika z pojemnika widokw, cigle jest jednak dostpny w menederze fragmentw oraz
z poziomu stosu drugoplanowego. Na jego miejscu pojawia si nowy fragment wywietlajcy
okno pomocy, w ktrym zawarto widoczny dla uytkownika tekst pomocy. W momencie wycofania omawianego fragmentu jego wpis zostanie usunity ze stosu, w wyniku czego zniknie
(zarwno sprzed oczu uytkownika, jak rwnie z poziomu menedera fragmentw) i zostanie
przywrcony fragment wywietlajcy okno zachty. Caa operacja jest w rzeczywistoci banalna
do przeprowadzenia. Kod zamieszczony na listingu 29.21 jest niezwykle prosty, a jednoczenie
niesamowicie skuteczny; dziaa bezbdnie nawet wtedy, gdy podczas wywietlania okna dialogowego zmieni si tryb wywietlania.
Listing 29.21. Ukad graficzny i kod Java klasy HelpDialogFragment
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik /res/layout/help_dialog.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:padding="4dip"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/helpmessage"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:text="Tekst pomocy"
android:layout_weight="1"
android:layout_gravity="center_vertical|center_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="top|center_horizontal" />
<Button android:id="@+id/btn_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="Zamknij">
</Button>
</LinearLayout>

// Jest to plik HelpDialogFragment.java


import
import
import
import
import
import
import

android.app.DialogFragment;
android.os.Bundle;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.widget.Button;
android.widget.TextView;

public class HelpDialogFragment


extends DialogFragment
implements View.OnClickListener
{

1070 Android 3. Tworzenie aplikacji


public static HelpDialogFragment
newInstance(int helpResId)
{
HelpDialogFragment hdf = new HelpDialogFragment();
Bundle bundle = new Bundle();
bundle.putInt("zasb pomocy", helpResId);
hdf.setArguments(bundle);
return hdf;
}
@Override
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
this.setCancelable(true);
int style = DialogFragment.STYLE_NORMAL, theme = 0;
setStyle(style,theme);
}
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle icicle)
{
View v = inflater.inflate(R.layout.help_dialog, container,
false);
TextView tv = (TextView)v.findViewById(R.id.helpmessage);
tv.setText(getActivity().getResources()
.getText(getArguments().getInt("zasb pomocy")));
Button closeBtn = (Button)v.findViewById(R.id.btn_close);
closeBtn.setOnClickListener(this);
return v;
}
public void onClick(View v)
{
dismiss();
}
}

Mamy tu do czynienia z kolejnym fragmentem wywietlajcym okno dialogowe, jeszcze prostszym od prezentowanego wczeniej. Zadaniem tego fragmentu jest wywietlanie tekstu pomocy.
Na ukad graficzny skada si kontrolka TextView i przycisk Zamknij. Kod Java powinien
wyglda ju znajomo dla Czytelnika. Znajdziemy w nim metody newInstance() suc do
utworzenia nowego fragmentu, ktry wywietla okno pomocy, onCreate() pozwalajc na
zdefiniowanie stylu i motywu okna dialogowego, a take onCreateView() generujc hierarchi widokw. W naszym przypadku potrzebny jest nam zasb typu string, ktrym posuymy
si do zapenienia kontrolki TextView, zatem uzyskujemy dostp do zasobw z poziomu aktywnoci i wybieramy identyfikator zasobu przekazany w metodzie newInstance(). Na kocu
metoda onCreateView() ustanawia procedur obsugi kliknicia przycisku Zamknij. W tym
przypadku w czasie zamykania okna system nie wykonuje niczego nadzwyczajnego.

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1071

Istniej dwa sposoby wywoywania tego fragmentu: z poziomu aktywnoci oraz poprzez fragment wywietlajcy okno zachty. Jeeli wybierzemy pierwsze rozwizanie, pniejsze wycofanie tego fragmentu poskutkuje jego usuniciem ze szczytu stosu i wywietleniem gwnej
aktywnoci znajdujcej si pod spodem. Jeeli wywietlimy ten fragment z poziomu fragmentu
wywietlajcego okno zachty, jego wycofanie spowoduje wycofanie transakcji fragmentu (poniewa fragment ten by czci transakcji umieszczonej w stosie drugoplanowym) i usunicie
okna pomocy wraz z jednoczesnym przywrceniem fragmentu wywietlajcego okno zachty.
W efekcie uytkownik ponownie ujrzy okno zachty.

Przykadowe okno dialogowe klasa AlertDialogFragment


Pozosta nam do zaprezentowania ostatni fragment wywietlajcy okno dialogowe, mianowicie
okno dialogowe alertu. Chocia moemy utworzy go podobnie jak w przypadku wczeniej
zaprezentowanego fragmentu wywietlajcego okno pomocy, istnieje alternatywne rozwizanie, pozwalajce na wykorzystanie starszej struktury klasy AlertBuilder. Rozwizanie to
okazywao si skuteczne w wielu poprzednich wersjach systemu Android. Listing 29.22 zawiera kod rdowy fragmentu wywietlajcego okno alertu.
Listing 29.22. Kod Java klasy AlertDialogFragment
import
import
import
import
import

android.app.AlertDialog;
android.app.Dialog;
android.app.DialogFragment;
android.content.DialogInterface;
android.os.Bundle;

public class AlertDialogFragment


extends DialogFragment
implements DialogInterface.OnClickListener
{
public static AlertDialogFragment
newInstance(String message)
{
AlertDialogFragment adf = new AlertDialogFragment();
Bundle bundle = new Bundle();
bundle.putString("komunikat alertu",message);
adf.setArguments(bundle);
return adf;
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setCancelable(true);
int style = DialogFragment.STYLE_NORMAL, theme = 0;
setStyle(style,theme);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
AlertDialog.Builder b =

1072 Android 3. Tworzenie aplikacji


new AlertDialog.Builder(getActivity());
b.setTitle("Uwaga!!");
b.setPositiveButton("OK", this);
b.setNegativeButton("Anuluj", this);
b.setMessage(this.getArguments().getString("komunikat alertu"));
return b.create();
}
public void onClick(DialogInterface dialog, int which)
{
OnDialogDoneListener act = (OnDialogDoneListener) getActivity();
boolean cancelled = false;
if (which == AlertDialog.BUTTON_NEGATIVE)
{
cancelled = true;
}
act.onDialogDone(getTag(), cancelled, "Alert odwoany");
}
}

W przypadku tego fragmentu nie potrzebujemy ukadu graficznego, poniewa klasa AlertBuilder
zapewnia jego utworzenie. Czytelnik zauway, e ten fragment jest tworzony tak jak pozostae, jednak zamiast metody zwrotnej onCreateView() stosujemy w tym przypadku metod
onCreateDialog(). Implementujemy albo metod onCreateView(), albo onCreateDialog(),
nigdy obydwie naraz. Element przekazywany przez metod onCreateDialog() nie jest widokiem, lecz oknem dialogowym. Od tego miejsca moemy zacz wykorzystywa informacje
przedstawione w rozdziale 8., aby utworzy okno dialogowe w standardowy sposb. Rnica
polega na tym, e w celu uzyskania dostpu do parametrw okna dialogowego powinnimy
wzi pod uwag pakiet parametrw. W naszej przykadowej aplikacji wykorzystujemy go do
zaprezentowania komunikatu alertu, nie ma jednak przeszkd, aby uzyska dostp do innych
parametrw pakietu.
Zwrmy take uwag, e w przypadku tego typu fragmentu wywietlajcego okno dialogowe
wymagana jest implementacja interfejsu DialogInterface.OnClickListener w klasie fragmentu, co oznacza, e nasz fragment musi implementowa metod zwrotn onClick(). Metoda
ta zostanie wywoana, jeeli uytkownik zacznie w jaki sposb wpywa na okno dialogowe. Po
raz wtry uzyskujemy odniesienie do uruchomionego okna dialogowego oraz wskazanie,
ktry przycisk zosta wcinity. Podobnie jak poprzednio, nie moemy polega na metodzie
onDismiss(), poniewa moe ona zosta wywoana wskutek zmiany konfiguracji urzdzenia.

Przykadowe okno dialogowe gwny ukad graficzny


Aby nasza aplikacja bya kompletna, musimy wstawi rwnie ukad graficzny gwnej aktywnoci. Odpowiedni kod zosta zaprezentowany na listingu 29.23.
Listing 29.23. Gwny ukad graficzny
<?xml version="1.0" encoding="utf-8"?>

<!-- /res/layout/main.xml -->


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1073

android:layout_height=" match_parent"
android:gravity="fill"
>
<TextView android:id="@+id/textViewId"
android:layout_width=" match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:text="@string/help_text"
android:textColor="@android:color/black"
android:textSize="25sp"
android:scrollbars="vertical"
android:scrollbarStyle="insideOverlay"
android:scrollbarSize="25dip"
android:scrollbarFadeDuration="0"
/>
</LinearLayout>

Po uruchomieniu aplikacji naley przetestowa wszystkie opcje w rnorodnych trybach wywietlania obrazu. Sprbujmy obrci urzdzenie w momencie wywietlania fragmentw.
Czytelnikowi powinno si spodoba, e okna dialogowe obracaj si wraz z reszt obrazu oraz
e nie trzeba zbytnio przejmowa si kodem odpowiedzialnym za zachowywanie i odczytywanie fragmentw w trakcie zmian konfiguracji.
Mamy nadziej, e kolejnym elementem, jaki Czytelnik doceni, jest atwo nawizywania komunikacji pomidzy fragmentami a aktywnoci. Oczywicie, aktywno zawiera odniesienia
(lub moe je uzyska) do wszystkich istniejcych fragmentw, posiada wic moliwo uzyskania dostpu do metod eksponowanych przez te fragmenty. Nie jest to jedyny sposb komunikowania si fragmentw z aktywnoci. Moemy zawsze zastosowa metody pobierajce wobec
menedera fragmentw w celu odczytania wystpienia zarzdzanego fragmentu, a nastpnie
odpowiednio rzutowa takie odniesienie i bezporednio wywoa metod wobec tego fragmentu.
Stopie odizolowania fragmentw od siebie za pomoc interfejsw oraz poprzez aktywnoci lub
utworzenia sieci zalenoci pomidzy fragmentami zaley od zoonoci aplikacji oraz stopnia
jej wykorzystania.

Inne formy komunikowania si z fragmentami


Zademonstrowalimy elegancki sposb komunikowania si fragmentw pomidzy sob, polegajcy na definiowaniu i wykorzystywaniu interfejsu sucego do implementacji metod zwrotnych z fragmentw wprost w aktywnoci wywoujcej. Nie jest to jedyny mechanizm komunikowania si fragmentw ze sob. Poniewa meneder fragmentw ma dostp do informacji na
temat wszystkich fragmentw doczonych do biecej aktywnoci, aktywno ta lub zawarty
w niej fragment mog zada takich informacji dotyczcych dowolnego innego fragmentu
przy uyciu uprzednio opisanych metod pobierajcych.
Po uzyskaniu odniesienia do fragmentu aktywno lub jej fragment mog go w odpowiedni
sposb rzutowa, a nastpnie spowodowa bezporednie wywoanie metod wobec tej aktywnoci lub jej fragmentu. W ten sposb fragmenty mog uzyska wicej informacji na temat innych
fragmentw, ni byoby to wymagane w zwykych przypadkach. Nie naley jednak zapomina,
e w tym przypadku aplikacja jest uruchomiona na urzdzeniu mobilnym, zatem niekiedy zastosowanie pewnych uproszcze jest uzasadnione. Wycinek kodu z listingu 29.24 ukazuje nam
bezporedni sposb komunikacji pomidzy dwoma fragmentami.

1074 Android 3. Tworzenie aplikacji


Listing 29.24. Bezporednia komunikacja pomidzy fragmentami
FragmentOther fragOther =
(FragmentOther)getFragmentManager().findFragmentByTag("other");
fragOther.callCustomMethod( arg1, arg2 );

Na listingu 29.24 nie wprowadzilimy adnego interfejsu. Omawiany fragment bezporednio


posiada informacje na temat klasy oraz dostpnych metod drugiego fragmentu. Takie rozwizanie nie jest kopotliwe, poniewa fragmenty te mog by czci tej samej aplikacji. Poza tym
fakt, e niektre fragmenty maj dostp do informacji na temat innych fragmentw, moe by
atwiejszy do zaakceptowania.

Stosowanie metod startActivity() i setTargetFragment()


Wspln cech fragmentw i aktywnoci jest moliwo uruchomienia aktywnoci za pomoc
fragmentu. Fragment zawiera metody startActivity() oraz startActivityForResult().
Dziaaj one podobnie jak w przypadku aktywnoci: w momencie przekazania wyniku zostanie
wywoana metoda onActivityResult() wobec fragmentu uruchamiajcego dan aktywno.
Istnieje jeszcze jeden mechanizm komunikacji, z ktrym Czytelnik powinien si zapozna. Gdy
dany fragment ma zosta uruchomiony przez inny fragment, wywoujcy fragment moe zosta
powizany z fragmentem wywoywanym. Zostao to zademonstrowane na listingu 29.25.
Listing 29.25. Konfiguracja mechanizmu komunikacji fragmentu wywoujcego z docelowym
fragmentem
mCalledFragment = new CalledFragment();
mCalledFragment.setTargetFragment(this, 0);
fm.beginTransaction().add(mCalledFragment, "work").commit();

Za pomoc tych kilku wierszy utworzylimy nowy obiekt CalledFragment, ustawilimy w biecym fragmencie wywoywany fragment jako fragment docelowy oraz za pomoc mechanizmu transakcji dodalimy ten wywoywany fragment do menedera fragmentw oraz do
aktywnoci. Po uruchomieniu wywoywanego fragmentu bdzie mg on wywoa metod
getTargetFragment(), ktra przekae odniesienie do fragmentu wywoujcego. Za pomoc tej
metody wywoywany fragment moe korzysta z metod fragmentu wywoujcego, a nawet uzyska bezporedni dostp do jego skadowych widoku. Na listingu 29.26 zademonstrowalimy
przykadowy kod, w ktrym wywoywany fragment wprowadza tekst bezporednio do interfejsu
uytkownika znajdujcego si we fragmencie wywoujcym.
Listing 29.26. Komunikacja fragmentu docelowego z fragmentem wywoujcym
TextView tv = (TextView)
getTargetFragment().getView().findViewById(R.id.text1);
tv.setText("Ustawiony z poziomu wywoywanego fragmentu");

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1075

Tworzenie niestandardowych animacji


za pomoc klasy ObjectAnimator
We wczeniejszej czci rozdziau zaprezentowalimy w zarysie sposb niestandardowego animowania fragmentw. Pokazalimy kod niestandardowej animacji, dziki ktrej fragment wywietlajcy szczegy znikn, a na jego miejscu pojawi si nowy fragment wywietlajcy szczegy. Stwierdzilimy rwnie, e w pakiecie Android SDK do dyspozycji pozostaje tylko kilka
standardowych animacji, jednak nie wszystkie dziaaj zgodnie z oczekiwaniami. W tym
podrozdziale pokaemy, w jaki sposb mona tworzy wasne, niestandardowe animacje,
dziki czemu Czytelnik bdzie mg wstawia interesujce przejcia pomidzy fragmentami.
Mechanizm implementowania niestandardowych animacji fragmentw zosta umieszczony
w klasie ObjectAnimator. W rzeczywistoci jest to oglna funkcja Androida, ktra moe by
stosowana nie tylko w przypadku fragmentw, lecz rwnie obiektw klasy View. W tym podrozdziale bdziemy zajmowa si wycznie animacj fragmentw, lecz omawiane tu zasady
w rwnym stopniu stosuje si rwnie do innych rodzajw obiektw. Animator obiektu pobiera
obiekt i animuje go od stanu pocztkowego do stanu kocowego w okrelonym przedziale czasowym. Przedzia ten jest definiowany w milisekundach. Istniej procedury okrelajce zachowanie obiektu w tym czasie. Procedury te nosz nazw interpolatorw.
Jeeli wyobrazimy sobie przejcie od stanu pocztkowego do stanu kocowego jako prost, interpolator bdzie wyznacza pooenie animacji na tej prostej w dowolnym momencie tego
danego czasu. Jedn z najprostszych procedur jest interpolator liniowy (ang. linear interpolator);
dzieli on nasz odcinek na rwne czci i przeskakuje po nich w takich samych przedziaach
czasowych. W wyniku tego obiekt porusza si z punktu pocztkowego do punktu kocowego
ze sta prdkoci, bez pocztkowego przypieszenia i kocowego hamowania.
Domylnym interpolatorem jest accelerate_decelerate, ktry wprowadza na pocztku
animacji pynne przypieszenie, a na jej kocu pynne hamowanie. Najciekawsza jest informacja, e interpolator moe przekroczy punkt kocowy na naszej prostej, a nastpnie cofn si. Na
tym polega dziaanie interpolatora przekraczajcego (ang. overshoot interpolator). Mamy rwnie
do czynienia z interpolatorem drgajcym (ang. bogunce interpolator), ktry porusza si z punktu
pocztkowego do punktu kocowego, jednak po dotarciu do punktu kocowego wraca kilkakrotnie do punktu pocztkowego, aby ostatecznie zatrzyma si w punkcie kocowym.
Interpolator wpywa na wymiary obiektu. W przypadku stosowanych wczeniej animatorw
oraz fade_out, tym wymiarem by parametr alfa fragmentu (tj. przezroczysto
obiektu). Animator fade_in zmienia warto parametru alfa fragmentu od wartoci (0) do
(1), z kolei animator fade_out modyfikowa warto parametru alfa drugiego fragmentu od
wartoci (1) do (0). Jeden fragment przechodzi ze stanu cakowitej przezroczystoci do stanu zupenej widocznoci, podczas gdy w drugim fragmencie nastpowa odwrotny proces.
fade_in

Poza wzrokiem uytkownika animator obiektu znajduje gwny widok fragmentu i regularnie
wywouje metod setAlpha(), za kadym razem nieznacznie zmieniajc warto parametru.
Czstotliwo powtrze wywoa zaley od interpolatora. Interpolator liniowy wprowadza
wywoania w rwnych odstpach czasowych. Interpolator accelerate_decelerate najpierw
ustanawia mniejsze wartoci okrelonego parametru na jednostk czasu i stopniowo je zwiksza,
co daje wraenie przypieszenia, natomiast pod koniec odwraca ten proces, przez co wydaje si,
e nastpuje spowolnienie animacji danego wymiaru obiektu.

1076 Android 3. Tworzenie aplikacji


Wymiarami moe by wiele spord wartoci, ktre mona pobra i ustawi wewntrz klasy
View. W rzeczywistoci do pracy z przetwarzanym widokiem animator obiektu wykorzystuje
mechanizm refleksji. Jeeli zechcemy zdefiniowa animacj obrotu, Android wywoa metod
setRotation() wobec danego obiektu (lub jego widoku). Animator pobiera wartoci pocztkow i kocow, a nastpnie wykorzystuje je do przetworzenia obiektu w danych granicach. Jeeli
nie zostanie zdefiniowana warto pocztkowa, zostanie wprowadzona metoda pobierajca, majca
na celu uzyskanie biecej wartoci obiektu. Zobaczmy, jak to si ma do naszych fragmentw.
FragmentTransaction definiujc niestandardow animacj
setCustomAnimations(), ktra pobiera dwa parametry identyfikatory zasobw:

Jedyn metod w klasie

jest

Pierwszy parametr okrela zasb animatora dla fragmentu wprowadzanego


do pojemnika widokw.
Drugi parametr definiuje zasb animatora dla fragmentu opuszczajcego
pojemnik widokw.

Obydwa animatory nie musz by ze sob nawet powizane, najlepiej jednak by byo, gdyby
wizualnie do siebie pasoway. Innymi sowy, jeeli jeden fragment zanika, drugi mgby stopniowo pojawi si na miejscu poprzednika. Jeeli jeden fragment wysuwa si z prawego brzegu
ekranu, drugi moe wsun si z lewej strony.
Zasoby animatora mona znale w folderze zestawu SDK, w katalogu zwizanym z waciw
platform, a dalej w podkatalogu /data/res/animator. To wanie tu znajdziemy uywane wczeniej pliki fade_in.xml i fade_out.xml. Moemy utworzy rwnie wasne zasoby. Jeeli zdecydujemy si na ten krok, najlepiej umieci taki plik w katalogu /res/animator naszego projektu.
Plik ten w razie potrzeby mona doda rcznie. Przyjrzyjmy si przykadowi umieszczonemu
na listingu 29.27, gdzie widzimy prosty lokalny plik animatora (slide_in_left.xml).
Listing 29.27. Niestandardowy animator powodujcy wysuwanie si obiektu z lewej strony ekranu
<?xml version="1.0" encoding="utf-8" ?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:valueFrom="-1280"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="x"
android:duration="2000" />

W tym pliku zasobw wykorzystano nowy znacznik, objectAnimator, wprowadzony w wersji


3.0 Androida. Podstawowa struktura pliku powinna jednak wyglda znajomo dla Czytelnika.
Mamy tu do czynienia z grup atrybutw typu android: okrelajcych czynno, jak chcemy
wykona. W przypadku animatora obiektw musimy zdefiniowa kilka elementw. Pierwszym
z nich jest interpolator. Lista dostpnych typw interpolatorw zostaa umieszczona w zasobie
android.R.interpolator. Dziki wiedzy o nazwach zasobw zorientujemy si, e atrybut
interpolatora stanowi odpowiednik pliku umieszczonego w katalogu zestawu SDK, w folderze
waciwej platformy, dokadniej za w katalogu /data/res/interpolator, a nazwa tego pliku to
accelerate_decelerate.xml.
Atrybut android:propertyName definiuje wymiar, jaki stanie si podstaw animacji. W tym
przypadku zamierzamy przeprowadzi animacj w kierunku osi X. Jeeli przyjrzymy si metodzie setX() klasy View, zauwaymy, e wprowadzanym parametrem jest warto zmienno-

Rozdzia 29 Koncepcja fragmentw oraz inne pojcia dotyczce tabletw

1077

przecinkowa. Z tego wanie powodu atrybut android:valueType posiada zdefiniowan warto


floatType. Warto atrybutu android:duration wynosi 2000, czyli 2 sekundy. Prawdopodobnie jest to zbyt dugi czas dla standardowej aplikacji, w tym przypadku jednak chcemy zdy zobaczy, co si dzieje w trakcie animacji. Dwa niewymienione jeszcze atrybuty, android:
valueFrom oraz android:valueTo, posiadaj wartoci odpowiednio: -1280 i 0. Zostay one
okrelone w taki sposb, poniewa dany fragment ma si znajdowa w punkcie 0 na kocu
animacji. Oznacza to, e po zakoczeniu animacji lewa krawd fragmentu bdzie si znajdowa przy lewej krawdzi pojemnika widoku. Poniewa chcemy, aby nasz fragment wysun
si z lewej strony ekranu, musimy zadeklarowa rozpoczcie animacji od tej strony, a warto
-1280 wydaje si wystarczajco dua. Jak zapewne Czytelnik si domyla, animator obiektu wysuwajcego si z prawej strony wygldaby niemal identycznie jak ten zaprezentowany na listingu 29.27, z t rnic, e atrybut android:valueFrom przyjby warto 0, a w atrybucie
android:valueTo wprowadzilibymy bardzo du warto dodatni, na przykad 1280.
Podczas korzystania z animatora obiektw zauwaymy, e wartoci wikszoci wymiarw posiadaj typ floatType, chocia czasami bdziemy wybiera rwnie typ intType. Wystarczy
spojrze na typ wartoci parametru wymaganego przez metod ustawiajc. To wanie dziki
niej animator obiektu posiada tak wielki potencja. Tak naprawd nie jest wane, skd pochodzi
metoda ustawiajca. Oznacza to, e moemy doda do obiektu wasny wymiar, a jego animacj
obsuy klasa animatora. Zadaniem programisty jest jedynie dostarczenie metody ustawiajcej
i wprowadzenie atrybutw do pliku zasobu. Reszt pracy wykona animator. Jedyn wad tego
systemu jest to, e w przypadku pominicia atrybutu valueFrom w pliku XML animator
obiektu wykorzysta metod pobierajc do okrelenia wartoci pocztkowej obiektu. Metoda
pobierajca musi wtedy przekaza waciwy typ wartoci danego wymiaru.
By moe Czytelnika zainteresuje rwnie moliwo jednoczesnego animowania kilku wymiarw. W tym celu wykorzystujemy znacznik <set> wok wikszej liczby znacznikw <object
Animator>. Na listingu 29.28 przedstawilimy plik zasobu animatora (slide_out_down.xml),
ktry przeprowadza animacj wzdu osi Y i jednoczenie modyfikuje parametr alfa obiektu.
Listing 29.28. Niestandardowy animator przeprowadzajcy animacj w osi X oraz w wymiarze alfa
<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:interpolator="@android:interpolator/accelerate_cubic"
android:valueFrom="0"
android:valueTo="1280"
android:valueType="floatType"
android:propertyName="y"
android:duration="2000" />
<objectAnimator
android:interpolator="@android:interpolator/accelerate_cubic"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="alpha"
android:duration="2000" />
</set>

1078 Android 3. Tworzenie aplikacji


Znacznik <set> jest odpowiednikiem klasy ObjectAnimator w Androidzie, jednak w strukturze
XML znacznik ten posiada tylko jeden atrybut android:ordering. Dopuszczalnymi wartociami tego atrybutu s domylna together, umoliwiajca rwnolege animowanie wymiarw,
oraz sequential, definiujca kolejno wystpowania animacji zgodnie z kolejnoci ich
umieszczenia w pliku XML.

Odnoniki
Poniej zamieszczamy odnoniki do zasobw zawierajcych dodatkowe informacje na tematy
przedstawione w tym rozdziale:
ftp://ftp.helion.pl/przyklady/and3ta.zip znajdziemy tu list wszystkich projektw
utworzonych na potrzeby niniejszej ksiki. Waciwe pliki s umieszczone w katalogu
o nazwie ProAndroid3_R29_Fragmenty. Zamiecilimy w nim take plik Czytaj.TXT,
stanowicy dokadn instrukcj importowania projektw do rodowiska Eclipse.
Doczylimy rwnie projekty ukazujce zastosowanie pakietu kompatybilnoci
fragmentw (Fragment Compatibilty SDK) w starszych wersjach systemu Android.
ApiDemos wrd przykadw umieszczonych w zestawie Android SDK znajdziemy
projekt ApiDemos. Umieszczono w nim kilka aplikacji wykorzystujcych koncepcj
fragmentw, ktre mog nam pomc w jej zrozumieniu.
http://developer.android.com/guide/topics/fundamentals/fragments.html artyku
z poradnika programisty, w ktrym omwiono koncepcj fragmentw.
http://android-developers.blogspot.com/2011/02/android-30-fragments-api.html
wpis stanowicy wstp do nauki posugiwania si fragmentami.
http://android-developers.blogspot.com/2011/02/animation-in-honeycomb.html
wpis wprowadzajcy do nowej struktury animacji oraz do zastosowa animatora
obiektw.

Podsumowanie
W niniejszym rozdziale zaprezentowalimy zupenie now klas Fragment, wprowadzon
w wersji 3.0 Androida, a take jej podklasy i klasy pokrewne, zwizane z menederem oraz
transakcjami. Fragmenty stanowi zupenie nowe, potne rozwizanie, pozwalajce na organizacj funkcjonalnoci oraz powizanych z ni interfejsw uytkownika. Chocia fragmenty
zostay zaprojektowane specjalnie dla tabletw, dostpne bd rwnie w przypadku urzdze
wyposaonych w niewielkie wywietlacze. Bd pomocne w rozdzielaniu logiki dziaania aplikacji na niewielkie, proste w uytkowaniu skadowe, ktre bd wykorzystywane, przenoszone
i zarzdzane na wczeniej niedostpne sposoby. Przedstawilimy jedn z ciekawszych nowych
funkcji Androida mowa tu o animatorze obiektw, ktry w prosty sposb moe zmodyfikowa przejcia pomidzy fragmentami.
W nastpnym rozdziale zajmiemy si kolejnym istotnym aspektem aplikacji tworzonych z myl
o tabletach, czyli klas ActionBar.

R OZDZIA

30
Analiza klasy ActionBar

Klasa ActionBar jest nowym interfejsem API wprowadzonym w wersji 3.0 Androida. Pozwala ona na modyfikowanie paska tytuowego aktywnoci. W starszych
wersjach systemu pasek tytuowy aktywnoci przechowywa wycznie jej nazw.
Wraz z rozwojem systemu Android przejmuje on coraz wicej wzorcw interfejsu
uytkownika wykorzystywanych w komputerach biurkowych. W aplikacji biurowej
mamy do czynienia z paskiem tytuowym, paskiem menu oraz z pewn liczb przyciskw na pasku narzdzi. Implementacja interfejsu ActionBar stanowi odzwierciedlenie takiej struktury paska tytuowego i menu, spotykanej w komputerach stacjonarnych.
Interfejs ActionBar opiera si zwaszcza na modelu paska tytuowego i paska menu
spotykanego w przegldarkach WWW. Zosta on zaprojektowany w taki sposb, aby
programici mogli uwzgldnia w aplikacjach schematy nawigacji znane wanie
z przegldarek.
Kluczowym zadaniem paska narzdzi jest zagwarantowanie uytkownikowi byskawicznego dostpu do najczciej wykorzystywanych narzdzi bez koniecznoci
przeszukiwania menu opcji lub menu kontekstowych.
We wspczesnej literaturze informatycznej dogodny dostp do dziaa
zwany jest czsto afordancj, co odnosi si do wygodnego odkrywania czy
te wykonywania dziaa. Na kocu rozdziau zamiecilimy kilka adresw
URL kierujcych do informacji na temat afordancji.

W trakcie lektury tego rozdziau Czytelnik zapozna si z nastpujcymi informacjami


o pasku dziaania:
Pasek dziaania jest czci aktywnoci i zachowuje si zgodnie z jej cyklem
ycia.
Pasek dziaania przyjmuje jedn z trzech postaci: paska zakadek, paska
wywietlajcego list oraz standardowego paska dziaania. W dalszej czci
rozdziau zaprezentujemy wygld i zachowanie kadego z tych trybw.
Sposb, w jaki obiekty nasuchujce zakadki umoliwiaj interakcj
z paskiem zakadek.

1080

Android 3. Tworzenie aplikacji

Sposb, w jaki adaptery obiektw typu Spinner oraz obiekty nasuchujce listy s
wykorzystywane do wspdziaania z paskiem wywietlajcym listy.
Mechanizm oddziaywania przycisku ekranu startowego oraz paska dziaania na
struktur menu.
Sposb umieszczania przyciskw wewntrz paska stanu oraz ich obsugiwanie.

Zademonstrujemy te pojcia poprzez utworzenie trzech rnych aktywnoci. Kada z nich posuy do zaprezentowania innego rodzaju paska dziaania. W ten sposb Czytelnik zyska moliwo przetestowania zachowania tej klasy w kadym z trzech trybw. Najpierw przyjrzyjmy
si jednak elementom paska dziaania widocznym dla uytkownika.

Anatomia klasy ActionBar


Na rysunku 30.1 zosta zaprezentowany typowy pasek dziaania, uruchomiony w trybie wywietlania zakadek.

Rysunek 30.1. Aktywno z paskiem zakadek

Jest to zrzut ekranu z dziaajcej przykadowej aplikacji, ktra zostaa omwiona w dalszej czci
tego rozdziau. Pasek dziaania widoczny na rysunku 30.1 skada si z piciu czci. S to (liczc
od lewej do prawej):
Obszar przycisku ekranu startowego. Przycisk widoczny w lewej grnej czci
paska dziaania jest czasami nazywany przyciskiem ekranu startowego (ang. home
icon). Przypomina on nieco kontekst nawigacji z przegldarek WWW, w ktrych
kliknicie przycisku strony startowej spowoduje jej uruchomienie. Przekonamy si
pniej, e kliknicie tego przycisku spowoduje wysanie metody zwrotnej do opcji
menu posiadajcej identyfikator android.R.id.home.
Obszar tytuu. W tym obszarze umieszczamy tytu aplikacji bd aktywnoci.

Rozdzia 30 Analiza klasy ActionBar

1081

Obszar zakadek. W obszarze zakadek znajduje si lista zdefiniowanych zakadek.


Zawarto tego obszaru moe by bardzo rnorodna. Jeeli pasek dziaania pracuje
w trybie wywietlania zakadek, bd tu umieszczane zakadki. Jeli natomiast
zdefiniowano tryb wywietlania listy, znajdziemy tutaj list rozwijalnych elementw.
W standardowym trybie obszar ten pozostaje pusty.
Obszar paska narzdzi. Tu za obszarem zakadek znajduje si obszar paska narzdzi,
zawierajcy niektre elementy menu dostpne w postaci przyciskw. W przykadowej
aplikacji zaprezentujemy, w jaki sposb mona tutaj umieszcza wybrane opcje menu.
Obszar menu. To ostatni obszar paska dziaania. Jest to pojedynczy, standardowy
przycisk wywietlajcy opcje menu. Po jego klikniciu zostanie rozwinite menu.
Menu to moe rnie wyglda lub wywietla si w rnych miejscach, zalenie
od rozmiaru urzdzenia.

Aktywno z rysunku 30.1 oprcz paska dziaania zawiera rwnie widok tekstowy debugowania, w ktrym s wywietlane informacje dotyczce wykonanych dziaa. Dziaania te mog by
wynikiem kliknicia ktrej z zakadek, przycisku ekranu startowego, opcji menu dziaania lub
opcji waciwego menu.
Zastanwmy si teraz nad sposobem implementacji trzech wymienionych rodzajw paska dziaania: paska zakadek, paska wywietlajcego list oraz standardowego paska dziaania. Skoro
zaprezentowalimy zrzut ekranu aplikacji z paskiem dziaania w trybie paska zakadek, zajmiemy si najpierw tym przypadkiem.

Aktywno paska dziaania wywietlajcego zakadki


Chocia zamierzamy utworzy trzy rne aktywnoci, z ktrych kada przechowuje inny rodzaj
paska dziaania, bd one posiaday wiele wsplnych cech.
Wszystkie zawieraj ten sam widok debugowania, dziki czemu moemy monitorowa
wykonywane przez nie dziaania.
Wszystkie posiadaj wsplny przycisk strony startowej.
Kada aktywno posiada tytu.
Wszystkie posiadaj te same przyciski w obszarze paska narzdzi.
Wszystkie posiadaj wsplne menu opcji.
Podstawowa rnica midzy trzema omawianymi trybami paska dziaania ley w sposobie jego
konfiguracji. W naszej przykadowej aplikacji umiecimy wsplny kod w klasie bazowej i pozwolimy kadej pochodnej aktywnoci (w tym rwnie aktywnoci paska zakadek) na skonfigurowanie paska dziaania.
Do trudno jest objani dziaanie tych wspdzielonych plikw bez kontekstu przynajmniej
jednej aktywnoci zawierajcej pasek dziaania. Zaprezentujemy wic najpierw w pierwszym
punkcie te wsplne pliki oraz sposb ich wykorzystania przez aktywno paska zakadek, a nastpnie dodamy do tego projektu aktywnoci przechowujce pozostae dwa rodzaje paskw menu.
Poniej zamiecilimy list plikw, ktre bd potrzebne do utworzenia tego wiczeniowego
projektu. Wymienilimy tutaj wszystkie wsplne pliki oraz pliki wymagane do wprowadzenia
paska zakadek. Widzimy na tej licie wiele plikw, poniewa umieszczamy wsplny kod w klasach bazowych. W ten sposb zmniejszymy liczb plikw dodawanych podczas tworzenia dwch
nastpnych przykadowych aplikacji. Przy nazwach poszczeglnych plikw podano rwnie numery odpowiednich listingw.

1082

Android 3. Tworzenie aplikacji

DebugActivity.java klasa bazowa aktywnoci generujca widok debugowania


zaprezentowany na rysunku 30.1 (listing 30.2).
BaseActionBarActivity.java wywodzi si z klasy DebugActivity i zawiera wspln
logik nawigacji (na przykad odpowiedzi na wspdzielone dziaania, w tym zmiany
pomidzy trybami wywietlania paska dziaania; listing 30.3).
IReportBack.java interfejs stanowicy nonik komunikacyjny pomidzy aktywnoci
debugowania a rnymi obiektami nasuchujcymi paski dziaania (listing 30.1).
BaseListener.java bazowa klasa obiektu nasuchujcego, wsppracujca z klas
DebugActivity oraz rnorodnymi dziaaniami wywoywanymi z poziomu paska
dziaania. Peni rol bazowej klasy dla obiektw nasuchujcych paska zakadek oraz
paska wywietlajcego list (listing 30.4).
TabNavigationActionBarActivity.java wywodzi si z klasy BaseActionBarActivity
i definiuje pasek zakadek. Zostaa tutaj umieszczona wikszo kodu odnoszca si
do paska zakadek (listing 30.6).
TabListener.java klasa ta jest potrzebna do dodawania zakadki do paska zakadek.
Tutaj rwnie generowane s odpowiedzi na kliknicia zakadek. W naszym przypadku
wywietlany jest po prostu komunikat debugowania poprzez klas BaseListener
(listing 30.5).
AndroidManifest.xml zostaj tu zdefiniowane wywoywane aktywnoci (listing 30.13).
layout/main.xml plik ukadu graficznego aktywnoci DebugActivity. Wszystkie
trzy klasy paska dziaa wywodz si z klasy DebugActivity, wic wspdziel one
rwnie ten ukad graficzny (listing 30.7).
menu/menu.xml zestaw elementw menu pozwalajcy na przetestowanie interakcji
z paskiem dziaania. Rwnie ten plik jest wspdzielony pomidzy wszystkimi
aktywnociami paska dziaania (listing 30.9).

Implementacja bazowych klas aktywnoci


Wiele bazowych klas wykorzystuje interfejs IReportBack. Przedstawilimy go ju w poprzednich rozdziaach. Jego przeznaczenie nie ulega tu zmianie. Prezentujemy go ponownie na listingu 30.1, aby Czytelnik nie musia go szuka w poprzednich rozdziaach.
Listing 30.1. IReportBack.java
//IReportBack.java
package com.androidbook.actionbar;
public interface IReportBack
{
public void reportBack(String tag, String message);
public void reportTransient(String tag, String message);
}

Klasa implementujca ten interfejs pobiera komunikat i wywietla go na ekranie. Moe to by


na przykad komunikat debugowania. Dokonujemy tego za pomoc metody reportBack().
Tak sam rol peni metoda reportTransient(), w tym przypadku jednak uytkownik widzi
komunikat wywietlony w kontrolce Toast.

Rozdzia 30 Analiza klasy ActionBar

1083

W naszym przykadzie klas implementujc interfejs IReportBack jest DebugActivity. Kod


rdowy tej aktywnoci zosta umieszczony na listingu 30.2.
Listing 30.2. Klasa DebugActivity zawierajca widok debugowania
//DebugActivity.java
package com.androidbook.actionbar;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
public abstract class DebugActivity
extends Activity
implements IReportBack
{

//Potrzebuj tego najpierw pochodne klasy


protected abstract boolean
onMenuItemSelected(MenuItem item);

//Zmienne prywatne, ustanowione przez konstruktor


private
private
private
private

static String tag=null;


int menuId = 0;
int layoutid = 0;
int debugTextViewId = 0;

public DebugActivity(int inMenuId,


int inLayoutId,
int inDebugTextViewId,
String inTag)
{
tag = inTag;
menuId = inMenuId;
layoutid = inLayoutId;
debugTextViewId = inDebugTextViewId;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(this.layoutid);

//Poniszy fragment jest potrzebny do przewijania


//widoku tekstowego
TextView tv = this.getTextView();
tv.setMovementMethod(
ScrollingMovementMethod.getInstance());
}
@Override
public boolean onCreateOptionsMenu(Menu menu){
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(menuId, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
appendMenuItemText(item);

1084

Android 3. Tworzenie aplikacji

if (item.getItemId() == R.id.menu_da_clear){
this.emptyText();
return true;
}
boolean b = onMenuItemSelected(item);
if (b == true)
{
return true;
}
return super.onOptionsItemSelected(item);
}
protected TextView getTextView(){
return
(TextView)this.findViewById(this.debugTextViewId);
}
protected void appendMenuItemText(MenuItem menuItem){
String title = menuItem.getTitle().toString();
appendText("MenuItem:" + title);
}
protected void emptyText(){
TextView tv = getTextView();
tv.setText("");
}
protected void appendText(String s){
TextView tv = getTextView();
tv.setText(s + "\n" + tv.getText());
Log.d(tag,s);
}
public void reportBack(String tag, String message)
{
this.appendText(tag + ":" + message);
Log.d(tag,message);
}
public void reportTransient(String tag, String message)
{
String s = tag + ":" + message;
Toast mToast =
Toast.makeText(this, s, Toast.LENGTH_SHORT);
mToast.show();
reportBack(tag,message);
Log.d(tag,message);
}
}//eof-class

Podstawowym zadaniem tej bazowej klasy jest wywietlenie aktywnoci zawierajcej widok
debugowania. Widok ten suy do prezentowania komunikatw przesyanych przez metod
reportBack(). Aktywno ta bdzie nadrzdna w stosunku do aktywnoci paskw dziaania.

Wprowadzenie jednolitego zachowania klas ActionBar


Mamy jeszcze wicej okazji do refaktoryzacji kodu pochodzcego z aktywnoci dziedziczcych
i umieszczenia go w nowej klasie bazowej, nazwanej BaseActionBarActivity.

Rozdzia 30 Analiza klasy ActionBar

1085

Gwnym zadaniem tej klasy jest zagwarantowanie wsplnego kodu, przetwarzanego w odpowiedzi na kliknicia elementw menu. Elementy te posu nam do zmiany trzech aktywnoci
reprezentujcych tryby wywietlania paska dziaania. Po zmianie trybu bdziemy mogli przetestowa kontrolujc go aktywno.
Aktywno BaseActionBarActivity widzimy na listingu 30.3.
Listing 30.3. Wsplna klasa bazowa dla aktywnoci przechowujcej paski dziaania
// BaseActionBarActivity.java
package com.androidbook.actionbar;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
public abstract class BaseActionBarActivity
extends DebugActivity
{
private String tag=null;
public BaseActionBarActivity(String inTag)
{
super(R.menu.menu,
R.layout.main,
R.id.textViewId,
inTag);
tag = inTag;
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
TextView tv = this.getTextView();
tv.setText(tag);
}
protected boolean onMenuItemSelected(MenuItem item)
{

//Reaguje na przycisk strony startowej


if (item.getItemId() == android.R.id.home) {
this.reportBack(tag,"Ikona startowa wcinita");
return true;
}

//Wsplne zachowanie suce do wywoywania rwnorzdnych aktywnoci


if (item.getItemId() == R.id.menu_invoke_tabnav){
if (getNavMode() ==
ActionBar.NAVIGATION_MODE_TABS)
{
this.reportBack(tag,
"Ju przebywamy w aktywnoci paska zakadek");
}
else {
this.invokeTabNav();
}
return true;
}
if (item.getItemId() == R.id.menu_invoke_listnav){

1086

Android 3. Tworzenie aplikacji

if (getNavMode() ==
ActionBar.NAVIGATION_MODE_LIST)
{
this.reportBack(tag,
"Ju przebywamy w aktywnoci paska wywietlajcego list");
}
else{
this.invokeListNav();
}
return true;
}
if (item.getItemId() == R.id.menu_invoke_standardnav){
if (getNavMode() ==
ActionBar.NAVIGATION_MODE_STANDARD)
{
this.reportBack(tag,
"Ju przebywamy w aktywnoci standardowego paska dziaania");
}
else{
this.invokeStandardNav();
}
return true;
}
return false;
}
private int getNavMode(){
ActionBar bar = this.getActionBar();
return bar.getNavigationMode();
}
private void invokeTabNav(){
Intent i = new Intent(this,
TabNavigationActionBarActivity.class);
startActivity(i);
}

//Odkomentujmy ponisze ciaa metod


//wraz z implementowaniem kolejnych aktywnoci
private void invokeListNav(){

//Intent i = new Intent(this,


// ListNavigationActionBarActivity.class);
//startActivity(i);
}
private void invokeStandardNav(){

//Intent i = new Intent(this,


// StandardNavigationActionBarActivity.class);
//startActivity(i);
}
}//eof-class

Jeeli przeanalizujemy kod zapewniajcy obsug elementw menu, zauwaymy, e sprawdza


on aktywno, ktra ma zosta przywoana w wyniku kliknicia elementu menu. Jeeli jest to
bieca aktywno, Android wywietla odpowiedni komunikat w widoku debugowania i pozostawia t aktywno uruchomion.

Rozdzia 30 Analiza klasy ActionBar

1087

Ta bazowa aktywno upraszcza rwnie kod pochodnych aktywnoci paskw dziaania, w tym
rwnie aktywnoci paska zakadek.

Implementacja obiektu nasuchujcego zdarze z zakadek


Zanim przejdziemy do pracy z paskiem zakadek, musimy najpierw wprowadzi obiekt nasuchujcy zakadki. Obiekt ten pozwala na reagowanie na kliknicia poszczeglnych zakadek.
Klasa tego obiektu wywodzi si z bazowego obiektu nasuchujcego, sucego do zapisywania komunikatw w dzienniku o dziaaniach na zakadkach. Na listingu 30.4 zaprezentowalimy bazowy
obiekt nasuchujcy, ktry wykorzystuje interfejs IReportBack do wywietlania komunikatw.
Listing 30.4. Wsplny obiekt nasuchujcy utworzony z myl o aktywnociach przechowujcych
paski dziaa
//BaseListener.java
package com.androidbook.actionbar;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
public class BaseListener
{
protected IReportBack mReportTo;
protected Context mContext;
public BaseListener(Context ctx, IReportBack target)
{
mReportTo = target;
mContext = ctx;
}
}

Omawiana klasa bazowa przechowuje odniesienie do implementacji interfejsu IReportBack


oraz do aktywnoci, ktra jest wykorzystywana w postaci kontekstu. W naszym przypadku to
aktywno DebugActivity implementuje ten interfejs oraz odgrywa rol kontekstu.
Skoro utworzylimy ju bazowy obiekt nasuchujcy, moemy zaj si obiektem nasuchujcym
zakadki. Odpowiedni kod umiecilimy na listingu 30.5.
Listing 30.5. Obiekt nasuchujcy zakadki w oczekiwaniu na dziaania
// TabListener.java
package com.androidbook.actionbar;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
public class TabListener extends BaseListener
implements ActionBar.TabListener
{
private static String tag = "tc>";
public TabListener(Context ctx,
IReportBack target)
{
super(ctx, target);
}

1088

Android 3. Tworzenie aplikacji

public void onTabReselected(Tab tab,


FragmentTransaction ft)
{
this.mReportTo.reportBack(tag,
"ponownie wybrana zakadka ontab:" + tab.getText());
}
public void onTabSelected(Tab tab,
FragmentTransaction ft)
{
this.mReportTo.reportBack(tag,
"wybrana zakadka ontab:" + tab.getText());
}
public void onTabUnselected(Tab tab,
FragmentTransaction ft)
{
this.mReportTo.reportBack(tag,
"usunito zaznaczenie zakadki ontab:" + tab.getText());
}
}

Obiekt nasuchujcy zakadki suy wycznie do przekazywania do widoku debugowania


(rysunek 30.1) informacji o wywoaniach nastpujcych z poziomu zakadek.

Implementacja aktywnoci przechowujcej pasek zakadek


Po wprowadzeniu obiektu nasuchujcego zakadki przechodzimy w kocu do tworzenia aktywnoci przechowujcej pasek zakadek. Jej kod zaprezentowano na listingu 30.6.
Listing 30.6. Aktywno paska zakadek
// TabNavigationActionBarActivity.java
package com.androidbook.actionbar;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
public class TabNavigationActionBarActivity
extends BaseActionBarActivity
{
private static String tag =
"Klasa ActionBarActivity do nawigacji za pomoc zakadek";
public TabNavigationActionBarActivity()
{
super(tag);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
workwithTabbedActionBar();
}
public void workwithTabbedActionBar()
{
ActionBar bar = this.getActionBar();
bar.setTitle(tag);

Rozdzia 30 Analiza klasy ActionBar

1089

bar.setNavigationMode(
ActionBar.NAVIGATION_MODE_TABS);
TabListener tl = new TabListener(this,this);
Tab tab1 = bar.newTab();
tab1.setText("Zakadka1");
tab1.setTabListener(tl);
bar.addTab(tab1);
Tab tab2 = bar.newTab();
tab2.setText("Zakadka2");
tab2.setTabListener(tl);
bar.addTab(tab2);
}
}//eof-class

W kolejnych podpunktach omwimy teraz kod skadajcy si na aktywno paska zakadek (listing 30.6). Zwrcimy Czytelnikowi uwag na kady aspekt pracy z paskiem zakadek. Rozpoczniemy od kodu uzyskujcego dostp do paska dziaa, ktry stanowi cz aktywnoci.

Uzyskanie instancji paska dziaania


Analizujc listing 30.6, zwrmy uwag, e kod kontrolujcy pasek dziaania wcale nie jest
skomplikowany. Dostp do paska dziaania danej aktywnoci uzyskujemy poprzez wywoanie
metody getActionBar(). Przypomnijmy wygld caego wiersza:
ActionBar bar = this.getActionBar();

Zgodnie z zaprezentowanym fragmentem kodu pasek dziaania stanowi waciwo aktywnoci


i nie przekracza jej granic. Innymi sowy, nie moemy wykorzysta paska dziaania do kontrolowania wielu aktywnoci lub wpywania na nie.

Tryby nawigacji paska dziaania


Po otrzymaniu instancji paska dziaa aktywnoci ustanawiamy jego tryb wywietlania jako
ActionBar.NAVIGATION_MODE_TABS. Poniej prezentujemy cay wiersz kodu:
bar.setNavigationMode(
ActionBar.NAVIGATION_MODE_TABS);

Dwa pozostae tryby to:


ActionBar.NAVIGATION_MODE_LIST
ActionBar.NAVIGATION_MODE_STANDARD

Po skonfigurowaniu trybu wywietlania zakadek widzimy wiele zwizanych z nimi metod wewntrz klasy ActionBar, z ktrych moemy korzysta. W kodzie z listingu 30.6 wykorzystalimy te metody do dodania dwch zakadek do paska dziaa. Do inicjalizacji tych zakadek uylimy z kolei obiektu nasuchujcego zakadki, zdefiniowanego na listingu 30.5.
Poniej umiecilimy krtki wycinek kodu z listingu 30.6 ukazujcy sposb dodawania zakadki
do paska zada:
Tab tab1 = bar.newTab();
tab1.setText("Zakadka1");

1090

Android 3. Tworzenie aplikacji

tab1.setTabListener(tl);
bar.addTab(tab1);

Jeeli zapomnimy wywoa metod setTabListener() wobec zakadki dodawanej do paska dziaania, zostanie wywietlony komunikat o bdzie wskazujcy na brak obiektu nasuchujcego.

Przewijalny ukad graficzny zawierajcy widok


debugowania
Po kadym klikniciu zakadki w pasku dziaa obiekty nasuchujce zakadki wysyaj komunikaty do widoku debugowania. Na listingu 30.7 zosta zaprezentowany ukad graficzny klasy
DebugActivity, w ktrej znajduje si widok debugowania.
Listing 30.7. Plik ukadu graficznego aktywnoci debugowania
<?xml version="1.0" encoding="utf-8"?>

<!-- /res/layout/main.xml -->


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="fill"
>
<TextView android:id="@+id/textViewId"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/white"
android:text="Pocztkowa wiadomo tekstowa"
android:textColor="@android:color/black"
android:textSize="25sp"
android:scrollbars="vertical"
android:scrollbarStyle="insideOverlay"
android:scrollbarSize="25dip"
android:scrollbarFadeDuration="0"
/>
</LinearLayout>

Warto zwrci uwag na kilka elementw tego ukadu graficznego. Wprowadzamy biay kolor
ta widoku tekstowego. W ten sposb zrzuty ekranu bd nieco wyraniejsze. Rwnie w podobnym celu zwikszylimy rozmiar czcionki.
Konfigurujemy take widok tekstowy w taki sposb, aby mona go byo przewija. Chocia zazwyczaj do takich zastosowa uywane s widoki ScrollView, widok tekstowy sam w sobie posiada dostpn funkcj przewijania. Oprcz uruchomienia funkcji przewijania w pliku XML
musimy take wywoa metod setMovementMethod() wobec tego widoku tekstowego tak
jak zostao pokazane na listingu 30.8.
Listing 30.8. Wprowadzenie funkcji przewijania w widoku tekstowym
TextView tv = this.getTextView();
tv.setMovementMethod(
ScrollingMovementMethod.getInstance());

Rozdzia 30 Analiza klasy ActionBar

1091

Jest to wycinek kodu pobrany z klasy DebugActivity (listing 30.2).


W trakcie przewijania widoku tekstowego stwierdzimy rwnie, e pasek przewijania pojawia
si, a nastpnie zanika. Nie jest to zbyt wygodny wskanik iloci tekstu znajdujcego si poza
ekranem. Moemy wyczy zanikanie tego paska poprzez ustalenie wartoci 0 dla jego parametru android:scrollbarFadeDuration. Parametr ten jest widoczny na listingu 30.7.

Pasek dziaania a interakcja z menu


Chcemy take zaprezentowa w tym przykadzie mechanizm interakcji paska dziaania z elementami menu. Musimy wic w tym celu skonfigurowa rwnie plik menu. Prezentujemy go
na listingu 30.9.
Listing 30.9. Plik menu utworzony na potrzeby omawianego projektu
<!-- /res/menu/menu.xml -->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Ta grupa korzysta z domylnej kategorii. -->


<group android:id="@+id/menuGroup_Main">
<item android:id="@+id/menu_action_icon1"
android:title="Ikona dziaania1"
android:icon="@drawable/creep001"
android:showAsAction="ifRoom"/>
<item android:id="@+id/menu_action_icon2"
android:title="Ikona dziaania2"
android:icon="@drawable/creep002"
android:showAsAction="ifRoom"/>
<item android:id="@+id/menu_icon_test"
android:title="Ikona testowa"
android:icon="@drawable/creep003"/>
<item android:id="@+id/menu_invoke_listnav"
android:title="Wywoaj pasek listy"
/>
<item android:id="@+id/menu_invoke_standardnav"
android:title="Wywoaj pasek standardowy"
/>
<item android:id="@+id/menu_invoke_tabnav"
android:title="Wywoaj pasek zakadek"
/>
<item android:id="@+id/menu_da_clear"
android:title="wyczy" />
</group>
</menu>

W pliku menu z listingu 30.9 wykorzystalimy trzy ikony (creep001, 002 i 003)
pochodzce ze strony www.androidicons.com. Zgodnie z informacjami
zawartymi na tej stronie ikony te s objte licencj Creative Commons 3.0.

W kolejnym podpunkcie przyjrzymy si nieco dokadniej utworzonemu wanie menu.

1092

Android 3. Tworzenie aplikacji

Wywietlanie menu
Urzdzenia pracujce pod kontrol systemu Android w wersjach 2.3 i wczeniejszych czsto byy
wyposaone we wbudowany przycisk menu. Emulator dziaajcy w trybie dla wersji 3.0 systemu
nie symuluje fizycznych przyciskw powrotu do menu startowego, cofania lub menu, mog by
one jednak wci dostpne w niektrych urzdzeniach.
Jak widzimy na rysunku 30.2, przyciski ekranu startowego i cofania s teraz przyciskami programowymi, umieszczonymi u dou ekranu. Jednak przycisk menu jest wywietlany w kontekcie aplikacji, a dokadniej jako cz paska dziaa, i jest umieszczony w prawym grnym
rogu ekranu.

Rysunek 30.2. Aktywno wywietlajca pasek zakadek i rozwinite menu

Rysunek 30.2 przedstawia rozwinite menu.


Naley zwrci uwag, e pasek menu nie musi wywietla ikon reprezentujcych elementy
menu. Nie naley liczy na to, e w kadym przypadku wywietlane ikony bd reprezentoway
elementy menu.

Elementy menu jako dziaania


Jak ju wspomnielimy na pocztku tego rozdziau, cz elementw menu moemy zdefiniowa w taki sposb, aby byy wywietlane bezporednio w pasku dziaania. Elementy te s reprezentowane przez znacznik showAsAction. Widzimy go na listingu 30.9. Dla przypomnienia
prezentujmy ten fragment kodu na listingu 30.10.
Listing 30.10. Atrybut elementu menu showAsAction
android:showAsAction="ifRoom"

Rozdzia 30 Analiza klasy ActionBar

1093

Pozostaymi wartociami tego znacznika mog by:


always
never
withText

Taki sam efekt moemy osign za pomoc klasy MenuItem:


menuItem.setShowAsAction(int actionEnum)

Dostpne s nastpujce wartoci atrybutu actionEnum:


SHOW_AS_ACTION_ALWAYS
SHOW_AS_ACTION_IF_ROOM
SHOW_AS_ACTION_NEVER
SHOW_AS_ACTION_WITH_TEXT

Poniewa dziaania te s jedynie elementami menu, tak te si zachowuj i wywouj metod


zwrotn onOptionsItemSelected() klasy danej aktywnoci.
Zauwamy na koniec, e w tym przykadzie wykorzystujemy spor liczb ikon. Moemy je zastpi wasnymi ikonami lub pobra odpowiedni projekt, do ktrego cze zostao zamieszczone
na kocu rozdziau.

Plik manifest Androida


Na listingu 30.11 zostaa umieszczona zawarto pliku manifestu dla dotychczas utworzonej
czci projektu.
Listing 30.11. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.actionbar"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon"
android:label="Demonstracja klasy ActionBar">
<activity android:name=".TabNavigationActionBarActivity"
android:label="Demonstracja paska dziaania: TabNav">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="11" />
</manifest>

Warto parametru minSdkVersion wynosi 11 i odpowiada wersji 3.0 systemu.

Badanie aktywnoci przechowujcej pasek zakadek


Po skompilowaniu utworzonych plikw i uruchomieniu aplikacji ujrzymy, widoczny na rysunku
30.1, pasek zakadek. Nastpnie po klikniciu znajdujcej si po prawej stronie ikony menu
zostanie rozwinite menu aplikacji, co zostao zilustrowane na rysunku 30.2.

1094

Android 3. Tworzenie aplikacji

Aplikacja zostaa zaprojektowana w taki sposb, aby kade dziaanie na pasku dziaania zostao
zapisane w widoku debugowania. Podczas testowania aplikacji Czytelnik moe sprawdzi, co
si stanie po wykonaniu nastpujcych czynnoci:
Jeeli klikniemy przycisk strony startowej, ujrzymy w widoku tekstowym komunikat,
e wcinito przycisk ekranu startowego.
Jeeli klikniemy zakadk Zakadka1, pojawi si informacja, e Zakadka1 zostaa
ponownie wybrana.
Jeeli klikniemy zakadk Zakadka2, zostan wywietlone dwa komunikaty.
Pierwszy z nich informuje, e Zakadka1 schodzi z pierwszego planu, a z drugiego
dowiadujemy si o klikniciu Zakadka2. Komunikaty te zostaj dostarczone przez
zaprezentowane na listingu 30.5 obiekty nasuchujce zakadki.
Jeeli klikniemy widoczne po prawej stronie przyciski dziaania, zauwaymy, e zostan
wywoane powizane z nimi elementy menu, a tym samym zostan wstawione kolejne
komunikaty w widoku tekstowym.
Po rozwiniciu menu zobaczymy elementy menu suce do wywoywania innych
aktywnoci, ktre pozwalaj na zaprezentowanie pozostaych trybw wywietlania
paska dziaania. Musimy jednak z tym poczeka do zakoczenia procesu tworzenia
projektu. Do tego czasu klikanie tych elementw spowoduje jedynie wywietlanie
komunikatw w widoku tekstowym.
Na tym zakoczymy implementacj nie tylko aktywnoci wywietlajcej pasek zakadek, lecz
rwnie konfiguracj podstawowej struktury, dziki ktrej utworzenie pozostaych aktywnoci
okae si znacznie atwiejsze. Przejdmy teraz do paska dziaa zdefiniowanego w trybie wywietlania listy.

Aktywno paska dziaania w trybie wywietlania listy


Nasze bazowe klasy wykonuj wiksz cz pracy, zatem implementacja i przetestowanie aktywnoci przechowujcej pasek dziaania w trybie wywietlania listy nie powinny przysporzy
nam najmniejszych problemw. Do tego celu bd nam potrzebne nastpujce pliki:
SimpleSpinnerArrayAdapter.java klasa ta jest wymagana do skonfigurowania
paska wywietlajcego list wraz z obiektem nasuchujcym. Zawarte s w niej
wiersze wymagane przez rozwijaln list (listing 30.12).
ListListener.java klasa ta peni rol obiektu nasuchujcego aktywnoci, ktra
przechowuje testowany pasek dziaa. Musi ona zosta przekazana paskowi
dziaania w momencie przejcia do trybu wywietlania listy (listing 30.13).
ListNavigationActionBarActivity.java to wanie tutaj zaimplementujemy
aktywno paska dziaania wywietlajcego list (listing 30.14).
Po utworzeniu tych trzech plikw bdziemy musieli zaktualizowa kolejne dwa:
BaseActionBarActivity.java musimy usun znaki komentarza z kodu, ktry
wywouje aktywno przechowujc omawiany pasek dziaania (listing 30.3).
AndroidManifest.xml zdefiniujemy now aktywno przechowujc pasek
dziaania wywietlany w trybie listy (listing 30.11).

Rozdzia 30 Analiza klasy ActionBar

1095

Utworzenie klasy SpinnerAdapter


Aby zainicjalizowa pasek dziaania w trybie wywietlania listy, potrzebne s nam dwa elementy:
Adapter obiektu typu Spinner, ktry bdzie zapenia list tekstem.
Obiekt nasuchujcy list, wywoujcy metod zwrotn po wybraniu dowolnego
elementu listy.
Na listingu 30.12 widzimy klas SimpleSpinnerArrayAdapter, ktra implementuje interfejs
SpinnerAdapter. Jak ju wspomnielimy, zadaniem tej klasy jest wywietlenie listy elementw.
Listing 30.12. Utworzenie adaptera obiektu typu Spinner dla paska dziaa wywietlajcego list
//SimpleSpinnerArrayAdapter.java
package com.androidbook.actionbar;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
public class SimpleSpinnerArrayAdapter
extends ArrayAdapter<String>
implements SpinnerAdapter
{
public SimpleSpinnerArrayAdapter(Context ctx)
{
super(ctx,
android.R.layout.simple_spinner_item,
new String[]{"raz","dwa"});
this.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
}
public View getDropDownView(
int position, View convertView, ViewGroup parent)
{
return super.getDropDownView(
position, convertView, parent);
}
}

Nie istnieje klasa bezporednio implementujca wymagany przez t list interfejs SpinnerAdapter.
Utworzylimy zatem pochodn klasy ArrayAdapter i wprowadzilimy do niej prost implementacj wspomnianego interfejsu. Na kocu rozdziau zamiecilimy adres URL do dalszych
materiaw powiconych tego typu adapterom. Przejdmy teraz do obiektu nasuchujcego listy.

Utworzenie obiektu nasuchujcego listy


Jest to prosta klasa implementujca interfejs ActionBar.OnNavigationListener. Kod tej klasy
zosta umieszczony na listingu 30.13.
Listing 30.13. Tworzenie obiektu nasuchujcego listy nawigacji
//ListListener.java
package com.androidbook.actionbar;

//

1096

Android 3. Tworzenie aplikacji

//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw


//
public class ListListener
extends BaseListener
implements ActionBar.OnNavigationListener
{
public ListListener(
Context ctx, IReportBack target)
{
super(ctx, target);
}
public boolean onNavigationItemSelected(
int itemPosition, long itemId)
{
this.mReportTo.reportBack(
"obiekt nasuchujcy listy","ItemPosition:" + itemPosition);
return true;
}
}

Podobnie jak w przypadku obiektu nasuchujcego zakadki z listingu 30.5, dziedziczymy tu po


klasie BaseListener, dziki czemu moemy zapisywa komunikaty w widoku debugowania
poprzez interfejs IReportBack.

Konfigurowanie paska dziaania w trybie wywietlania listy


Utworzylimy ju wszystkie elementy potrzebne do skonfigurowania paska dziaania. Spjrzmy
teraz na kod rdowy aktywnoci przechowujcej ten pasek dziaania w trybie wywietlania listy,
umieszczony na listingu 30.14. Klasa ta jest bardzo podobna do omawianej wczeniej aktywnoci
przechowujcej pasek zakadek.
Listing 30.14. Aktywno paska dziaania w trybie wywietlania listy
// ListNavigationActionBarActivity.java
package com.androidbook.actionbar;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
public class ListNavigationActionBarActivity
extends BaseActionBarActivity
{
private static String tag=
"Klasa ActionBarActivity";
public ListNavigationActionBarActivity()
{
super(tag);
}
@Override
public void onCreate(Bundle savedInstanceState)
{

Rozdzia 30 Analiza klasy ActionBar

1097

super.onCreate(savedInstanceState);
workwithListActionBar();
}
public void workwithListActionBar()
{
ActionBar bar = this.getActionBar();
bar.setTitle(tag);
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
bar.setListNavigationCallbacks(
new SimpleSpinnerArrayAdapter(this),
new ListListener(this,this));
}
}//eof-class

Istotna cz kodu na listingu 30.14 zostaa zaznaczona pogrubion czcionk. Sam kod nie jest
zbyt skomplikowany: w metodach zwrotnych paska dziaania konfigurujemy adapter obiektu
typu Spinner oraz obiekt nasuchujcy jako list.

Zmiany w klasie BaseActionBarActivity


Po utworzeniu aktywnoci przechowujcej pasek dziaa w trybie wywietlania listy (listing 30.14)
moemy cofn si i zmodyfikowa kod klasy BaseActionBarActivity w taki sposb, eby kliknicie odpowiedniego elementu menu wywoao klas ListNavigationActionBarActivity.
Po usuniciu znakw komentarza z odpowiedniego fragmentu funkcji z listingu 30.3 kod ten
bdzie wyglda dokadnie tak jak fragment zamieszczony na listingu 30.15.
Listing 30.15. Zmodyfikowany fragment kodu wywoywanie aktywnoci przechowujcej pasek
dziaania w trybie wywietlania listy
private void invokeListNav(){
Intent i = new Intent(this,
ListNavigationActionBarActivity.class);
startActivity(i);
}

Efektem usunicia znakw komentarza z tego fragmentu kodu bdzie powizanie elementu
menu. W konsekwencji bdzie mona wywoa omawian aktywno.

Zmiany w pliku AndroidManifest.xml


Zanim bdziemy mogli wywoa t aktywno, musimy j zarejestrowa w pliku AndroidManifest.
xml. Dodajmy kod widoczny na listingu 30.16 do manifestu z listingu 30.11, aby dokoczy
proces rejestrowania aktywnoci.
Listing 30.16. Rejestrowanie aktywnoci przechowujcej pasek dziaania w trybie wywietlania listy
<activity android:name=".ListNavigationActionBarActivity"
android:label="Demonstracja paska dziaania: ListNav">
</activity>

1098

Android 3. Tworzenie aplikacji

Badanie aktywnoci zawierajcej pasek dziaania


w trybie wywietlania listy
Po skompilowaniu dotychczas omwionych plikw (nowych plikw oraz zaktualizowanych wersji
plikw wymienionych na pocztku podrozdziau) oraz uruchomieniu programu ujrzymy pasek
dziaania, zaprezentowany na rysunku 30.3.

Rysunek 30.3. Aktywno przechowujca pasek dziaania w trybie wywietlania listy

Na rysunku 30.3 widzimy nierozwinit list, znajdujc si tu obok tytuu aktywnoci. W tym
samym miejscu s umieszczane zakadki, jeli pasek dziaania pracuje w trybie wywietlania zakadek. Jeeli klikniemy teraz element nazwany raz, lista zostanie rozwinita. Widzimy to na
rysunku 30.4.

Rysunek 30.4. Aktywno z widoczn rozwinit list

Rozdzia 30 Analiza klasy ActionBar

1099

Gdy porwnamy t aktywno do aktywnoci widocznej na rysunkach 30.1 i 30.2, stwierdzimy,


e s one do siebie bardzo podobne. Rnica midzy nimi polega na tym, e w pierwszym przypadku na pasku dziaania mamy do czynienia z zakadkami, a w drugim z list. Zadaniem tych
dwch aktywnoci jest ukazanie Czytelnikowi istotnego podobiestwa procesw projektowania
aplikacji dla Androida i stron WWW.
W przypadku witryny WWW moemy mie do czynienia z wieloma stronami, kada z nich
jednak wyglda podobnie do strony gwnej. W naszym przypadku takim odpowiednikiem
gwnej strony jest klasa bazowa.
Chocia wprowadzilimy wiele aktywnoci w celu zademonstrowania paskw dziaania, wydaje
si, e w wersji 3.0 systemu obiekty te lepiej si nadaj do organizowania fragmentw wewntrz
jednej aktywnoci. Jeeli jednak bdziemy musieli wprowadzi wiele aktywnoci, warto si zastanowi nad zademonstrowanym przez nas wzorcem stosowania klasy bazowej jako gwnej
strony.
Zachowanie aktywnoci zawierajcej pasek dziaa w trybie listy jest bardzo podobne do zachowania aktywnoci z paskiem dziaa w trybie paska zakadek. Rnica polega na efekcie
kliknicia elementu listy. Po kadym klikniciu elementu listy zostanie wywoany obiekt nasuchujcy listy, ktry z kolei wywietli komunikat w widoku debugowania.
Po utworzeniu tych dwch aktywnoci bdziemy mogli je zmienia za pomoc elementw menu.
Przejdmy teraz do prostszej aktywnoci przechowujcej standardowy pasek dziaania.

Aktywno przechowujca
standardowy pasek dziaania
W niniejszym podrozdziale zapoznamy si z funkcjonowaniem standardowego paska dziaania.
Wprowadzimy now aktywno, w ktrej ustanowimy standardowy tryb nawigacji paska dziaania. Nastpnie przyjrzymy si wygldowi i zachowaniu tego paska dziaania.
Podobnie jak miao to miejsce w przypadku klasy ListNavigationActionBarActivity, wikszo operacji jest wykonywana przez klasy bazowe, zatem implementacja i testowanie omawianej aktywnoci nie powinny nastrcza zbyt duych problemw. Aby zaimplementowa t
aktywno, potrzebny nam bdzie nastpujcy plik:
StandardNavigationActionBarActivity.java plik ten suy do skonfigurowania
standardowego trybu wywietlania paska dziaania (listing 30.17).
Po utworzeniu tego pliku musimy zmodyfikowa nastpujce dwa pliki:
BaseActionBarActivity.java naley usun znaki komentarza wywoania
aktywnoci wywietlajcej standardowy pasek dziaania po klikniciu odpowiedniego
elementu menu (zmiany zostay pokazane na listingu 30.18, a oryginalny kod
na listingu 30.3).
AndroidManifest.xml musimy zdefiniowa t now aktywno w pliku manifecie
(definicj tej aktywnoci widzimy na listingu 30.19, a naley j umieci w pliku
AndroidManifest.xml umieszczonym na listingu 30.11).
Zajmiemy si teraz kadym z wymienionych plikw.

1100

Android 3. Tworzenie aplikacji

Aktywno przechowujca standardowy pasek dziaania


W przypadku paskw zakadek i paskw dziaania w trybie listy korzystalimy z odpowiednich
obiektw nasuchujcych. Oprcz metod zwrotnych elementw menu nie wprowadzamy adnego innego obiektu nasuchujcego standardowego paska dziaania. Nie musimy konfigurowa
tych metod zwrotnych, poniewa s one automatycznie umieszczane przez rodowisko SDK.
W zwizku z tym konfiguracja standardowego paska dziaania jest niezwykle prosta.
Na listingu 30.17 znajdziemy kod rdowy aktywnoci wywietlajcej standardowy pasek
dziaania.
Listing 30.17. Aktywno przechowujca standardowy pasek dziaania
//StandardNavigationActionBarActivity.java
package com.androidbook.actionbar;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
public class StandardNavigationActionBarActivity
extends BaseActionBarActivity
{
private static String tag=
"Standardowa nawigacja za pomoca klasy ActionBarActivity";
public StandardNavigationActionBarActivity()
{
super(tag);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
workwithStandardActionBar();
}
public void workwithStandardActionBar()
{
ActionBar bar = this.getActionBar();
bar.setTitle(tag);
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);

//Sprawdzamy, co si stanie, jeli umiecimy zakadki


attachTabs(bar);
}
public void attachTabs(ActionBar bar)
{
TabListener tl = new TabListener(this,this);
Tab tab1 = bar.newTab();
tab1.setText("Zakadka1");
tab1.setTabListener(tl);
bar.addTab(tab1);
Tab tab2 = bar.newTab();
tab2.setText("Zakadka2");

Rozdzia 30 Analiza klasy ActionBar

1101

tab2.setTabListener(tl);
bar.addTab(tab2);
}
}//eof-class

Jedyn czynnoci, jak naley wykona w celu zdefiniowania standardowego paska dziaania,
jest wybr trybu nawigacji. Na listingu 30.17 wyrnilimy odpowiedni fragment kodu pogrubion czcionk.
Na listingu 30.17 wprowadzilimy rwnie kod generujcy zakadki, aby sprawdzi, co
si z nimi stanie w standardowym trybie nawigacji. Nasze testy wykazay, e obecno
tego fragmentu kodu nie generuje adnych bdw, ale jest on po prostu ignorowany.

Zanim dowiemy si, jak wyglda standardowy pasek dziaania, musimy wprowadzi dwie zmiany
do ju istniejcych plikw.

Zmiany w klasie BaseActionBarActivity


Po utworzeniu aktywnoci wywietlajcej standardowy pasek dziaania (listing 30.17) moemy
si cofn i zmodyfikowa klas BaseActionBarActivity (listing 30.3) w taki sposb, aby omawiana aktywno bya wywoywana przez odpowiedni element menu. Po usuniciu znakw komentarza funkcja widoczna na listingu 30.3 bdzie wygldaa tak jak pokazano na listingu 30.18.
Listing 30.18. Fragment, z ktrego naley usun znaki komentarza, aby umoliwi wywoywanie
aktywnoci przechowujcej standardowy pasek dziaania
private void invokeStandardNav(){
Intent i = new Intent(this,
StandardNavigationActionBarActivity.class);
startActivity(i);
}

Po usuniciu znakw komentarza z tego fragmentu element menu zostanie powizany z kodem w sposb umoliwiajcy wywoywanie klasy StandardNavigationActionBarActivity.

Zmiany w pliku AndroidManifest.xml


Zanim jednak bdzie mona wywoa t aktywno, trzeba zarejestrowa j w pliku manifecie.
Aby dokoczy proces rejestracji, dodajmy wiersze umieszczone na listingu 30.19 do pliku
AndroidManifest.xml, widocznego na listingu 30.11.
Listing 30.19. Rejestrowanie aktywnoci wywietlajcej standardowy pasek dziaania
<activity android:name=".StandardNavigationActionBarActivity "
android:label="Demonstracja paska dziaania: standardowa nawigacja">
</activity>

1102

Android 3. Tworzenie aplikacji

Badanie aktywnoci przechowujcej


standardowy pasek dziaania
Gdy skompilujemy dotychczas omwione pliki (wymienione na pocztku podrozdziau) i uruchomimy aplikacj, pierwsza widoczna aktywno bdzie zawieraa pasek zakadek (rysunek 30.1).
Po klikniciu ikony menu zobaczymy ekran zaprezentowany na rysunku 30.2. Z poziomu tego
menu moemy wybra opcj Wywoaj pasek standardowy, po czym pojawi si widoczna na
rysunku 30.5 aktywno wywietlajca standardowy pasek dziaania.

Rysunek 30.5. Aktywno zawierajca standardowy pasek dziaania

Pierwsz rzecz przykuwajc uwag Czytelnika bdzie zapewne brak obszaru, w ktrym wczeniej byy wywietlane zakadki oraz lista. Jeeli klikniemy teraz widoczne po prawej stronie
przyciski dziaania, informacje o ich wywoaniu zostan zapisane w widoku debugowania.
Kliknijmy teraz przycisk ekranu startowego. Rwnie w tym przypadku w widoku debugowania zostanie zapisana sygnatura wywoania. Po wykonaniu tych trzech klikni widok debugowania bdzie wyglda tak jak na rysunku 30.6.

Odnoniki
Ponisze adresy URL okazay si bardzo przydatne w trakcie wyszukiwania materiaw do niniejszego rozdziau. Zawieraj one rwnie ogrom dodatkowych informacji. Dodatkowo ostatnie
cze prowadzi do strony, z ktrej moemy pobra gotowe projekty.
Donald A. Norman, The Design of Everyday Things. Ksika ta rozpowszechnia
wspomnian wczeniej koncepcj wizualnej percepcji, zwan afordancj, w stosunku
do dziedziny HCI (ang. Human-Computer Interaction interakcja czowiek komputer).
Termin ten jest stosowany coraz czciej w literaturze powiconej interfejsom
uytkownika systemu Android. Omawiany w tym rozdziale pasek dziaania jest
zachwalany jako jedna z gwnych afordancji interfejsu uytkownika.

Rozdzia 30 Analiza klasy ActionBar

1103

Rysunek 30.6. Reagowanie na zdarzenia zachodzce w pasku dziaania

http://en.wikipedia.org/wiki/Affordance definicja afordancji w Wikipedii.


www.androidbook.com/item/3624 wyniki naszych bada zwizanych z paskiem
dziaania systemu Android. Znajdziemy tutaj dalsze odnoniki, przykadowe kody,
adresy do przykadowych aplikacji oraz rysunki reprezentujce rne tryby
wywietlania paska dziaania.
http://developer.android.com/reference/android/app/ActionBar.html dokumentacja
klasy ActionBar.
www.androidbook.com/item/3627 korzystanie z adaptera obiektu typu Spinner.
Aby ustanowi tryb wywietlania listy, musimy zrozumie mechanizm dziaania listy
rozwijalnej oraz obiektw typu Spinner. W tym krtkim artykule znajdziemy kilka
przykadw oraz dalszych odnonikw zwizanych ze stosowaniem obiektw typu
Spinner w Androidzie.
www.androidicons.com kilka ikon wykorzystywanych w naszym projekcie zostao
zapoyczonych z tej witryny. Ikony te s objte licencj Creative Commons 3.0.
www.androidbook.com/item/3302 przyjemne dla oka ukady graficzne. Zamiecilimy
pod tym adresem kilka przykadowych kodw rdowych oraz informacji
dotyczcych prostych ukadw graficznych.
http://developer.android.com/reference/android/view/MenuItem.html dokumentacja
klasy MenuItem. Znajdziemy tu informacje o doczaniu elementw menu jako
przyciskw do paska dziaania.
http://developer.android.com/guide/topics/resources/menu-resource.html znajdziemy
tu list elementw jzyka XML dostpnych w procesie definiowania elementw
menu jako przyciskw paska dziaania.
ftp://ftp.helion.pl/przyklady/and3ta.zip pod tym adresem znajdziemy projekty
testowe utworzone specjalnie na potrzeby ksiki. Katalog zawierajcy projekty
omawiane w niniejszym rozdziale nosi nazw ProAndroid3_R30_PasekDziaania.

1104

Android 3. Tworzenie aplikacji

Podsumowanie
Jak wida, paski dziaania nie stanowi wcale takiej tajemnicy. S one znanym paradygmatem,
stosowanym podczas tworzenia aplikacji biurowych. Pocztkujcym programistom zachowanie
klasy, ktra w zalenoci od wyboru jednego z trzech trybw zyskuje odmienne waciwoci,
moe si wyda problematyczne. Nigdy nie wiadomo, czy posiadany podzbir klas wykona zamierzone zadanie. Jednak rnica pomidzy tymi trybami jest tak maa, e ich umieszczenie
w obrbie jednej klasy naprawd nie byo zym pomysem.
Wydaje si, e jedn z intencji wprowadzenia paskw dziaania byo skierowanie procesu projektowania w stron modelu nawigacji sieciowej.
Wydaje si take, e projektanci systemu Android staraj si powiza pasek dziaania z fragmentami w celu osignicia podanej jednorodnoci interfejsu uytkownika. Jeeli istnieje
potrzeba wykorzystania w aplikacji kilku wymiennie uywanych aktywnoci, zalecane jest rozwaenie, czy taki efekt mona osign, wprowadzajc fragmenty, a nie dodatkowe aktywnoci.
Fragmenty maj wiele udogodnie, zwaszcza zwizanych z zarzdzaniem ich stanem w przypadku obracania urzdzenia, a tym samym zmiany jego konfiguracji. Aktywno przechowujca
fragmenty utrzymuje stan pomidzy zmianami konfiguracji. Fragmenty zostay omwione dokadniej w rozdziale 29.
W tym rozdziale zaprezentowalimy take jeden ze sposobw wprowadzania jednolitoci projektowej za pomoc bazowej aktywnoci. Podobny efekt mona rwnie osign, wprowadzajc delegacj zamiast dziedziczenia. Istnieje take moliwo zapoyczenia znanych wzorcw,
stosowanych do tworzenia gwnych stron witryn sieciowych, oraz sprawdzenia, jak w tej roli
spisuj si klasy rodowiska Android SDK.
Funkcja paska dziaania zostaa wprowadzona dopiero w wersji 3.0 Androida. Na biec chwil
nie wiadomo, czy zostan utworzone odpowiednie biblioteki przeznaczone dla starszych wersji
systemu.
Istniej rwnie pewne rozbienoci pomidzy dokumentacj a faktycznym stanem interfejsw
API. W dokumentacji znalazo si stwierdzenie, e istniej tylko trzy tryby wywietlania paska
dziaania, jednak w interfejsie mona znale dodatkowy tryb, zwany trybem rozwijalnego menu
(ang. dropdown navigation mode). W trakcie testowania zachowywa si on dokadnie tak samo
jak tryb wywietlania listy, z t jedn rnic, e znikn tytu aktywnoci.
Moemy rwnie za pomoc flag kontrolowa elementy wywietlane w pasku dziaania. Jest to
do proste rozwizanie, zatem wystarczy zajrze do dokumentacji omawianego interfejsu API.

R OZDZIA

31
Dodatkowe zagadnienia
zwizane z wersj 3.0 systemu

Po przeczytaniu trzydziestu dugich rozdziaw cigle istniej zagadnienia zwizane


z wersj 3.0 systemu, ktrych jeszcze nie zdylimy poruszy! W tym ostatnim
rozdziale zajmiemy si tematyk usprawnie wprowadzonych do funkcji widetw
ekranu startowego oraz nowego interfejsu funkcji przecigania.
W wersji 3.0 systemu wprowadzono znaczne usprawnienia funkcji widetw. Dziki
nim moemy teraz dodawa na ekranie startowym widety oparte na listach. Interfejs funkcji przecigania jest z kolei cakowit nowoci. Za jego pomoc utworzymy
bogate interfejsy uytkownika, przypominajce budow te spotykane w komputerach
osobistych. Obydwa te zagadnienia omwimy bardzo szczegowo.

Widety ekranu startowego oparte na listach


W rozdziale 22. omwilimy mechanizm dziaania widetw w wersji 2.3 Androida
i starszych. W wersji 3.0 systemu zostay wprowadzone znaczne modyfikacje tej
funkcji; istnieje due prawdopodobiestwo, e zmiany te zostan rwnie wprowadzone w kolejnej wersji systemu, zoptymalizowanej take pod ktem smartfonw.
Wartociowym wstpem do tego rozdziau byoby powtrzenie sobie treci rozdziau 22., dziki czemu atwiej bdzie dostrzec rnice pomidzy star a now wersj widetw. Jednak w tym rozdziale zamiecilimy caociowy opis funkcji widetw, zatem Czytelnik moe si zapozna z informacjami o nich nawet bez
uprzedniego przeczytania rozdziau 22.
Jak wiemy ze wspomnianego rozdziau 22., rdzeniem widetw ekranu startowego
s widoki zdalne. Widet ekranu startowego jest w istocie widokiem zdalnym rysowanym na ekranie startowym. Widok zdalny jest cakowicie odosobniony od mieszczcych si w nim danych, podobnie jak strona WWW jest oddzielona od serwera.

1106

Android 3. Tworzenie aplikacji

W rozdziale 22. zamiecilimy list ukadw graficznych i widetw, ktre mog stanowi cz
widoku zdalnego. Widoki zbiorcze, takie jak listy czy siatki, nie byy klasyfikowane jako elementy skadowe widetw w wersji 2.3 systemu. Sytuacja ulega zmianie w wersji 3.0, dziki
czemu uzyskujemy wicej moliwoci na ekranie startowym. Wydanie 3.0 oferuje rwnie miniaturow architektur pozwalajc na asynchroniczne wczytywanie i wywietlanie danych
w tego typu zbiorczych widetach. W wykorzystaniu tej waciwoci przydadz nam si nowe
klasy i metody.
Zajmiemy si najpierw teoretycznym omwieniem tych zagadnie, a nastpnie, w celu utrwalenia przekazanej wiedzy, zademonstrujemy je na dziaajcych przykadach. Rozpocznijmy od
omwienia nowych widokw zdalnych, dostpnych w wersji 3.0 systemu.

Nowe widoki zdalne w wersji 3.0 systemu


W wersji 2.3 Androida dostpnych jest 13 ukadw graficznych i widetw stanowicych elementy skadowe widoku zdalnego:
AbsoluteLayout,
FrameLayout,
LinearLayout,
RelativeLayout,
AnalogClock,
Button,
Chronometer,
ImageButton,
ProgressBar,
ViewFlipper,
DateTimeView,
ImageView,
TextView.
Cz z tych ukadw graficznych i widokw (na przykad AbsoluteLayout) moga zosta wycofana z uycia, powinnimy wic najpierw to sprawdzi, zanim uyjemy ktrej z wymienionych klas w kodzie. By moe Czytelnik zapyta, dlaczego powysza lista jest taka istotna. Czy
wykorzystujemy bezporednio klasy tych widokw bd ukadw graficznych w procesie tworzenia widoku zdalnego?
Okazuje si, e klasa RemoteViews nie moe zosta utworzona poprzez jawne przekazanie
obiektw wymienionych powyej klas. Niedopuszczalne jest rwnie bezporednie doczenie
ktregokolwiek z tych obiektw do tej klasy. Zamiast tego obiekt RemoteViews tworzy si
poprzez przekazanie pliku ukadu graficznego do jego konstruktora. Powysza lista jest o tyle
istotna, e wzami takiego pliku mog by wycznie wymienione elementy.
Zaprezentowana poniej lista stanowi poszerzony zbir 16 elementw ukadw graficznych,
widetw i widokw dostpnych w wersji 3.0 Androida:
FrameLayout,
LinearLayout,
RelativeLayout,

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1107

AnalogClock,
Button,
Chronometer,
ImageButton,
ProgressBar,
ListView,
GridView,
StackView,
TextView,
DateTimeView,
ImageView,
AdapterViewFlipper,
ViewFlipper.

W kolejnych wersjach systemu do tej listy bd zapewne dodawane kolejne elementy. Gwnym czynnikiem decydujcym o tym, czy dany obiekt stanowi element skadowy widoku zdalnego, jest jego obecno w interfejsie RemoteViews.RemoteView.
Majc t wiedz, wykorzystajmy rodowisko Eclipse do sprawdzenia, ktre klasy uwzgldnione
w danym projekcie mog by czci widoku zdalnego. W tym celu:
1. Wprowad w kodzie rdowym instrukcj import odnoszc si do interfejsu
RemoteView.
2. Zaznacz nazw tego interfejsu.
3. Kliknij nazw interfejsu prawym przyciskiem myszy i przejd do zakadki References.
4. Wybierz odniesienia do tego interfejsu.
Zostanie wywietlona lista klas przechowywanych w interfejsie RemoteView.

Praca z listami stanowicymi cz widoku zdalnego


W rozdziale 22. zajmowalimy si zestawem klas sucych do tworzenia widetw ekranu startowego. Podstawowymi klasami s w tym przypadku AppWidgetProvider, AppWidgetManager,
RemoteViews oraz aktywno suca do konfiguracji klasy AppWidgetProvider za pomoc
pocztkowych parametrw.
Omwimy teraz w skrcie podstawow koncepcj dziaania widetw ekranu startowego (pozostae informacje przedstawione w tym podrozdziale stan si przez to nieco atwiejsze do zrozumienia). Klasa AppWidgetProvider jest odbiorc komunikatw wywoywanym co pewien
czas, ktry jest z gry zdefiniowany w pliku konfiguracyjnym. Odbiorca ten nastpnie wczytuje
wystpienie klasy RemoteViews na podstawie pliku ukadu graficznego. Obiekt RemoteViews
jest dalej przekazywany do odpowiedzialnej za jego wywietlanie klasy AppWidgetManager.
Moemy ewentualnie poinformowa system, e istnieje aktywno, ktra powinna zosta
wywoana przed pierwszym umieszczeniem widetu na ekranie startowym. Taka aktywno
konfiguracyjna suy do ustawienia parametrw pocztkowych widetu.

1108

Android 3. Tworzenie aplikacji

Moemy skonfigurowa rwnie zdarzenia onClick wobec zdalnych widokw widetu, umoliwiajc tym samym uruchomienie odpowiednich intencji opartych na zdarzeniach. Intencje te
mog wywoywa dowolne skadniki, w tym takie jak wysyanie komunikatw do odbiorcy
AppWidgetProvider.
W oglnej perspektywie s to jedyne czynnoci przeprowadzane w widetach ekranu startowego.
Reszt stanowi jedynie mechanizmy implementacji oraz rne wariacje omwionych podstawowych koncepcji.
Jednak a do wersji 2.3 Androida widoki bazujce na licie nie byy klasyfikowane jako elementy
widoku zdalnego i nie istnia skuteczny mechanizm wypeniania widokw zdalnych przypominajcych list. W wersji 3.0 systemu dodano nastpujce klasy, suce do obsugi widokw
zdalnych w postaci listy:
RemoteViewsFactory klasa ta umoliwia zapenienie widoku zdalnego w podobny
sposb, jak adaptery listy zapeniaj standardowe widoki listy. Jest to klasa otaczajca
adapter widoku listy, dziki czemu w sposb asynchroniczny dostarcza pojedyncze
widoki zdalne do widoku zdalnego przyjmujcego posta listy.
RemoteViewsService mamy tu do czynienia z usug odpowiedzialn za
przekazywanie klasy RemoteViewsFactory do obiektu RemoteViews. Zadaniem klasy
AppWidgetProvider jest poczenie takiej usugi ze zdalnym widokiem w postaci listy.
Dokonujemy tego poprzez doczenie intencji wywoujcej t usug do wspomnianego
widoku zdalnego. Usuga ta umoliwia przeduenie czasu istnienia procesu
przechowujcego klas AppWidgetProvider. W przeciwnym wypadku po przekazaniu
odbiorcy komunikatw proces ten zostanie odzyskany. W rozdziale 14. zostaa
omwiona symbiotyczna relacja pomidzy odbiorcami komunikatw
a dugoterminowymi usugami.
W celu zapewnienia obsugi widokw zdalnych w postaci listy w wersji 3.0 systemu znalazy si
nastpujce nowe metody interfejsu API:
RemoteViews.setPendingIntentTemplate() metoda ta pozwala na ustawienie
szablonu intencji oczekujcej wobec zdalnego widoku w celu reagowania na zdarzenia
klikni poszczeglnych elementw listy. Zajmiemy si koncepcj szablonu po
omwieniu pozostaych szczegw.
RemoteViews.setOnClickFillIntent() jest ona ustawiana wobec pojedynczych
elementw listy i cile wsppracuje z powysz metod.
Dziki cznemu wykorzystaniu tych dwch dodatkowych metod moemy reagowa na kliknicia poszczeglnych elementw listy zawartej w widoku zdalnym. Metody te zaprojektowano
w taki sposb, aby naleao ustawia jak najmniej intencji oczekujcych.
W trakcie omawiania przykadowej aplikacji przeanalizujemy dokadnie wymienione klasy
i metody. Poniej pokazalimy, w jaki sposb moemy je zastosowa podczas pracy z widokami
listy umieszczonymi w widecie ekranu startowego. Podczas przegldania poszczeglnych etapw warto zaglda do oglnego omwienia mechanizmu dziaania widetw ekranu startowego (zawarlimy je na pocztku podrozdziau):
1. Przygotuj zdalny ukad graficzny. Utwrz odpowiedni ukad graficzny, w ktrym
znajdzie si widok listy. Zdalny ukad graficzny nie rni si od standardowego
ukadu, lecz moemy w nim umieszcza jedynie widoki dozwolone dla widoku
zdalnego. Zasada jest taka sama jak w przypadku wszelkich innych widetw ekranu
startowego (co zostao wyranie powiedziane w rozdziale 22.).

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1109

2. Wczytaj zdalny ukad graficzny. W metodzie onUpdate() dostawcy widetu


wczytaj utworzony uprzednio skadowy ukad graficzny w postaci widoku zdalnego.
Ten etap pracy take nie rni si od tworzenia standardowego widetu. Pocz
nastpnie zdalny widok z jego usug, dziki czemu widok listy zostanie zapeniony
przez przekazan klas fabrykujc.
3. Skonfiguruj usug RemoteViewsService. Zlokalizuj zdalny widok za pomoc jego
identyfikatora i wprowad intencj, ktra bdzie wywoywaa usug widoku
zdalnego. Usuga ta nastpnie przekazuje klas RemoteViewsFactory do widoku
listy, dziki czemu zdalny widok zostaje zapeniony.
4. Skonfiguruj klas RemoteViewsFactory. Usuga widoku zdalnego musi przekaza
klas RemoteViewsFactory, ktra bdzie zapeniaa widok listy.
5. Skonfiguruj zdarzenia klikni. Czci procesu konfiguracji klasy AppWidgetProvider
jest ustawienie szablonu intencji oczekujcej onClick, dziki czemu aplikacja bdzie
odpowiadaa na t intencj. Musisz jednak jednoczenie skonfigurowa odpowiedzi
na pojedyncze kliknicia dla kadego elementu listy za pomoc klasy RemoteViewsFactory.
Wynika to z faktu, e elementy listy w widoku zdalnym s zapeniane przez t klas
fabrykujc.
6. Odpowiedz na zdarzenia klikni. Trzeba okreli obiekt, ktry bdzie odpowiada
na zdarzenia onClick ustanawiane wobec widokw listy. Odbiorc tych zdarze
moe zosta klasa AppWidgetProvider. Musisz przygotowa odbiorc komunikatw,
ktry bdzie otrzymywa i zdarzenia onClick pochodzce ze zdalnych widokw
i odpowiada na nie.
Przyjrzyjmy si implementacji kadego etapu w przykadowym kodzie.

Przygotowanie zdalnego ukadu graficznego


Jak zostao stwierdzone w poprzednim podpunkcie, ukad graficzny widoku zdalnego penicy
rol widetu ekranu startowego moe teraz przechowywa widok listy. Na listingu 31.1 zosta
zaprezentowany przykadowy ukad graficzny, moliwy do zastosowania w widoku zdalnym
i zawierajcy widok listy.
Listing 31.1. Plik zdalnego ukadu graficznego zawierajcy kontrolk ListView
<?xml version="1.0" encoding="utf-8"?>

<!-- /res/layout/test_list_widget_layout.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="150dp"
android:layout_height="match_parent"
android:background="@drawable/box1">
<TextView
android:id="@+id/listwidget_header_textview_id"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:text="Widok nagwka"
android:background="@drawable/box1"
android:gravity="center"
android:layout_weight="0"/>
<FrameLayout

1110

Android 3. Tworzenie aplikacji

android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_gravity="center">
<ListView android:id="@+id/listwidget_list_view_id"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/listwidget_empty_view_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone"
android:textColor="#ffffff"
android:text="Widok zawierajcy puste rekordy"
android:textSize="20sp" />
</FrameLayout>
<TextView
android:id="@+id/listwidget_footer_textview_id"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:text="Widok stopki"
android:background="@drawable/box1"
android:gravity="center"
android:layout_weight="0"/>
</LinearLayout>

Na listingu 31.1 kady wze XML reprezentuje poprawny widok zdalny. Omawiany ukad graficzny jest wywietlany w taki sposb, e w postaci widetu ekranu startowego bdzie si prezentowa tak jak na rysunku 31.1.

Rysunek 31.1. Ekran startowy zawierajcy widet z widokiem listy

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1111

Na wzorzec ukadu graficznego z rysunku 31.1 skada si prosty nagwek, cz gwna i stopka.
Nagwek i stopka posiadaj sta wysoko; w tym przypadku zostaa ona ustalona na 30 dp.
Chcemy ponadto, by wysoko czci gwnej bya taka, aby zajmowaa pozosta cz dostpnej wysokoci widetu. Osigniemy to poprzez wprowadzenie wartoci 0 w atrybutach
android:layout_weight nagwka i stopki. Dla czci gwnej wprowadzamy warto 1 atrybutu android:layout_weight oraz match_parent w android:layout_height.
Wze <framelayout> stanowicy cz gwn widetu wymaga krtkiego objanienia. Ze
wszystkich elementw potomnych wybiera on tylko jeden jako widok. W tym przypadku bdzie to <listview>, jeeli lista bdzie zawieraa jakie dane. W przypadku pustego widoku listy
bdzie stosowany pusty widok tekstowy. Konfigurujemy to w klasie RemoteViewsFactory.
W tym ukadzie znajdziemy take graficzny niestandardowy obiekt rysowany (@drawable/box1),
sucy do zaokrglenia rogw. Listing 31.2 zawiera tre pliku box1.xml, ktry naley umieci
w podkatalogu /res/drawable.
Listing 31.2. Plik res/drawable/box1.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="4dp" android:color="#888888" />
<padding android:left="2dp" android:top="2dp"
android:right="2dp" android:bottom="2dp" />
<corners android:radius="4dp" />
</shape>

Skoro ju mamy przykadowy ukad graficzny widetu ekranu startowego, zastanwmy si,
w jaki sposb moemy go wczyta do widoku zdalnego.

Wczytanie zdalnego ukadu graficznego


W przypadku widetu ekranu startowego widok zdalny zostaje wczytany i wywietlony za pomoc metody zwrotnej onUpdate() klasy AppWidgetProvider. Zaprezentowalimy takie rozwizanie na listingu 31.3.
Listing 31.3. Wczytywanie zdalnego ukadu graficznego w metodzie onUpdate()
public void onUpdate(Context context,
AppWidgetManager appWidgetManager,
int[] appWidgetIds)
{
int N = appWidgetIds.length;
for (int i=0; i<N; i++)
{
int appWidgetId = appWidgetIds[i];
RemoteViews rv =
new RemoteViews(context.getPackageName(),
R.layout.test_list_widget_layout);
rv.setEmptyView(R.id.listwidget_list_view_id,
R.id.listwidget_empty_view_id);

//Aktualizujemy wystpienie tego widetu

1112

Android 3. Tworzenie aplikacji

appWidgetManager.updateAppWidget(appWidgetId, rv);
}
super.onUpdate(context,appWidgetManager, appWidgetIds);
}

Zwrmy uwag, e obiekt RemoteViews zosta skonstruowany za pomoc identyfikatora pliku


ukadu graficznego, ktry definiuje cay widet. Jest to ten sam ukad graficzny co zaprezentowany na listingu 31.1. Pobieramy nastpnie wynikowy obiekt RemoteViews i ustanawiamy pusty
widok dla okrelonego zasobu listy (definiowanego za pomoc identyfikatora) wewntrz tego
ukadu graficznego.
Na listingu 31.3 plik ukadu graficznego posiada identyfikator:
R.layout.test_list_widget_layout

Zasb widoku listy wewntrz tego ukadu graficznego posiada identyfikator:


R.id.listwidget_list_view_id

Pusty widok definiowany dla tego zasobu posiada identyfikator:


R.id.listwidget_empty_view_id

Na listingu 31.4 identyfikatory te su do konstruowania zdalnego widoku oraz ustawienia


pustego widoku dla jednego z widokw listy.
Listing 31.4. Wczytywanie zdalnych widokw
RemoteViews rv =
new RemoteViews(context.getPackageName(),
R.layout.test_list_widget_layout);
rv.setEmptyView(R.id.bdw_list_view_id,
R.id.empty_view_id);

Konfigurowanie usugi RemoteViewsService


Do tej pory udao nam si wczyta zdalne widoki w metodzie onUpdate() klasy AppWidget
Musimy teraz powiza zdalny widok listy z usug, ktra bdzie moga przekaza adapter zapeniajcy list danymi.

Provider.

Dlaczego to musi by usuga? Dlaczego nie powizalimy klasy fabrykujcej bezporednio z widokiem listy?
Przyczyna jest taka, e klasa AppWidgetProvider jest odbiorc komunikatw, zatem metoda
onUpdate() dostawcy widetw jest ograniczona czasowo przez tego odbiorc. Aby unikn
potencjalnych problemw, do zapeniania widoku listy w Androidzie 3.0 przeznaczono oddzieln usug, wywodzc si z pakietu android.widget.RemoteViewsService. Usuga ta jest
odpowiedzialna za przekazywanie adaptera wykonujcego operacj zapeniania listy. Adapter
musi nalee do typu RemoteViewsService.RemoteViewsFactory. Jest to procedura, ktra
w schematyczny sposb dy do ostatecznego powizania widoku listy z klas fabrykujc.
Na listingu 31.5 prezentujemy przykadowy sposb pisania kodu usugi widoku zdalnego oraz
mechanizm przekazywania przez ni klasy fabrykujcej.

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1113

Listing 31.5. Przykad usugi RemoteViewsService


public class TestRemoteViewsService
extends android.widget.RemoteViewsService
{
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent)
{
return new TestRemoteViewsFactory(
this.getApplicationContext(), intent);
}
}

Zwrmy uwag na nastpujce cechy listingu 31.5:


Musimy dziedziczy po klasie RemoteViewsService.
Musimy zdefiniowa i otrzyma klas fabrykujc RemoteViewsFactory. Wkrtce
zajmiemy si tym zagadnieniem.
Skoro mamy do czynienia z usug, dziedziczona klasa RemoteViewsService (w naszym przypadku TestRemoteViewsService) musi zosta rwnie zadeklarowana w pliku manifecie.
Stosowny przykad zosta pokazany na listingu 31.6.
Listing 31.6. Deklarowanie usugi RemoteViewsService w pliku manifecie
<!-- Usuga stosowana w klasie RemoteViews w procesie tworzenia widetu zbiorczego -->
<service android:name=".TestRemoteViewsService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />

Po zdefiniowaniu takiej usugi zdalnych widokw moemy doczy j do zdalnego widoku listy
za pomoc kodu zamieszczonego na listingu 31.7 (przypominamy, e kod ten jest umieszczony
w metodzie onUpdate() klasy AppWidgetProvider).
Listing 31.7. Powizanie usugi RemoteViewsService z obiektem RemoteViewList
final Intent intent =
new Intent(context, TestRemoteViewsService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
appWidgetId);
intent.setData(
Uri.parse(
intent.toUri(Intent.URI_INTENT_SCHEME)));
rv.setRemoteAdapter(appWidgetId,
R.id.listwidget_list_view_id, intent);

W kodzie z listingu 31.7 najpierw tworzymy jawn intencj poprzez powizanie z ni klasy
RemoteViewsService. Nastpnie wstawiamy do tej intencji dodatkowe dane, pozwalajce na
zidentyfikowanie widetu, dla ktrego bdzie wywoywana usuga. Kolejnym etapem jest
przeprowadzenie tej dziwnej operacji odnoszenia si intencji do samej siebie wczytania
porcji danych do intencji za pomoc identyfikatora tej intencji. W ten sposb intencja staje si

1114

Android 3. Tworzenie aplikacji

niepowtarzalna, poniewa dodatkowe informacje s doczane do jej danych. Bez tych dodatkowych danych intencje nie s niepowtarzalne. Gdy ju to uzyskamy, moemy doczy intencj
do zdalnego widoku listy poprzez wywoanie metody setRemoteAdapter() i przekazanie jej
identyfikatora widoku listy.

Konfigurowanie klasy RemoteViewsFactory


Chocia do procesu zapeniania listy wskazalimy usug RemoteViewsService, ostatecznie to
zadanie realizuje klasa RemoteViewsFactory. Aby zapeni widok listy, najpierw trzeba zaimplementowa ten interfejs (w rozdziale 6. znajdziemy informacje dotyczce kontrolek listy oraz
adapterw listy).
Na listingu 31.8 widzimy sygnatury metod nalecych do klasy, ktra implementuje ten interfejs fabrykujcy.
Listing 31.8. Kontrakt klasy RemoteViewsFactory
class TestRemoteViewsFactory
implements RemoteViewsService.RemoteViewsFactory
{
public TestRemoteViewsFactory(Context context, Intent intent);
public void onCreate();
public void onDestroy();
public int getCount();
public RemoteViews getViewAt(int position);
private void loadItemOnClickExtras(RemoteViews rv, int position);
public RemoteViews getLoadingView();
public int getViewTypeCount();
public long getItemId(int position);
public boolean hasStableIds();
public void onDataSetChanged();
}

Zajmijmy si omwieniem tych metod oraz zwizanych z nimi wymaga. Zaczniemy od konstruktora.
Konstruktor klasy RemoteViewsFactory
Konstruktor przyjmuje tutaj dwa argumenty (by moe Czytelnik bdzie mia do czynienia
z inn klas fabrykujc przyjmujc inne argumenty). W przypadku widetw klasa ta jest
tworzona przez usug RemoteViewsService (co zostao pokazane na listingu 31.5), zatem
kontekst stanowi tu dostawca widetu, ktry sam w sobie jest odbiorc komunikatw.
Drugim argumentem konstruktora jest intencja. Jest to ta sama intencj, ktra bya wykorzystana w procesie wywoania usugi widoku zdalnego. Po utworzeniu tej intencji (listing 31.7)
i doczeniu jej do zdalnego widoku pozostawia ona zazwyczaj dodatkow warto stanowic
identyfikator widetu.
Obydwie te wartoci (kontekst oraz intencja) mog by utrzymywane w konstruktorze jako
zmienne lokalne, dziki czemu kolejne metody bd mogy z nich korzysta. Przydatne zwaszcza okazuje si pobranie identyfikatora widetu z intencji i zachowanie go w formie zmiennej
lokalnej.

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1115

Metoda zwrotna onCreate()


Sygnatur metody onCreate() jest:
Public void onCreate()

Zgodnie z wzorcem wystpujcym wrd wielu skadnikw Androida klasa RemoteViews


Factory zawiera metody onCreate() oraz onDestroy(). Dokumentacja sugeruje, e metoda
onCreate() jest wywoywana przez kliencki widok zdalny w momencie pierwszego utworzenia
klasy. Zgodnie z dalszymi informacjami wspomniana klasa fabrykujca moe by wspdzielona
przez wiele adapterw widokw zdalnych, w zalenoci od przekazanej intencji.
Jednak wzorzec ten nie odnosi si cakowicie do klasy RemoteViewsFactory, poniewa w przeciwiestwie do skadowej aktywnoci lub skadowej usugi proces tworzenia tej klasy pozostaje cakowicie pod kontrol programisty. Programista moe przeprowadzi proces inicjalizacji
w samym konstruktorze. Nie zostao w dokumentacji jasno stwierdzone, czy ten fabrykujcy
obiekt jest przechowywany w pamici przez szkielet interfejsu widetw w powizaniu z intencj
wywoujc usug zdalnego widoku. Komunikaty dziennika wskazuj, e metoda onCreate()
jest z pewnoci wywoywana. Mamy zatem moliwo inicjalizacji rwnie tutaj, a nie wycznie
w konstruktorze.
Metoda zwrotna onDestroy()
Sygnatura metody onDestroy() to:
Public void onDestroy()

Metoda ta stanowi dopenienie metody onCreate(). Zgodnie z dokumentacj zostaje ona wywoana w momencie odczenia ostatniego adaptera widoku zdalnego powizanego z danym
obiektem (lub klas fabrykujc). Nie jest jednak do koca jasne, kiedy ta metoda zostaje wywoana; nie zauwaylimy jej wywoania ani po usuniciu pojedynczego widetu z ekranu
startowego, ani po usuniciu ostatniego widetu.
Metoda zwrotna getCount()
Sygnatur metody getCount() jest:
public int getCount()

Metoda getCount() przekazuje cakowit liczb elementw z widoku listy. Metoda ta bardzo przypomina analogiczn metod spotykan w adapterach listy, ktre zostay omwione
w rozdziale 6.
Metoda zwrotna getViewAt()
Metoda getViewAt() posiada nastpujc sygnatur:
public RemoteViews getViewAt(int position)

Zadaniem tej metody jest przekazanie zdalnego widoku zgodnego z pozycj w widoku listy.
Zazwyczaj w tej metodzie wczytujemy dostosowany ukad graficzny dla tego widoku zdalnego
na danej pozycji, a nastpnie wprowadzamy w tym widoku wartoci, gdzie pozycja jest wskanikiem wczytywania odpowiednich danych. Na listingu 31.9 znajduje si przykad wczytywania
pojedynczego ukadu graficznego dla elementu listy.

1116

Android 3. Tworzenie aplikacji

Listing 31.9. Wczytywanie ukadu graficznego dla pojedynczego elementu listy


RemoteViews rv =
new RemoteViews(
this.mContext.getPackageName(),
R.layout.list_item_layout);

Ukad graficzny, do ktrego odnosimy si na listingu 31.9, moe by zdefiniowany tak, jak
zostao to zaprezentowane na listingu 31.10.
Listing 31.10. Ukad graficzny utworzony na potrzeby pojedynczego elementu listy
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textview_widget_list_item_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Tymczasowy tekst"
/>

Po wczytaniu zdalnego widoku (listing 31.9) moemy przekaza go do wywoujcego widoku


listy, aby zosta narysowany. Moemy tu rwnie ustanowi reakcj tego konkretnego widoku
listy na zdarzenie kliknicia.
Metoda zwrotna getLoadingView()
Sygnatura metody getLoadingView() jest nastpujca:
public RemoteViews getLoadingView()

Metoda ta przekazuje niestandardowy widok wczytywania, ktry pojawia si pomidzy wywoaniem metody getViewAt(position) a jej powrotem. Aby skorzysta z domylnego widoku
wczytywania, wprowadzamy tu warto null.
Metoda zwrotna getViewTypeCount()
Sygnatura metody getViewTypeCount() jest nastpujca:
public int getViewTypeCount()

Jeeli zdalny widok listy posiada tylko jeden typ widoku potomnego, metoda ta powrci
z wartoci 1. Jeeli bdzie dostpna wiksza liczba typw widokw, przekae ona warto rwn
liczbie dostpnych typw.
Metoda zwrotna getItemId()
Sygnatura metody getItemId() to:
public long getItemId(int position)

Metoda ta przekazuje waciwy identyfikator elementu znajdujcego si na danej pozycji w widoku listy. Metoda ta bardzo przypomina analogiczn metod spotykan w adapterach listy,
ktre zostay omwione w rozdziale 6.

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1117

Metoda zwrotna hasStableIds()


Sygnatur metody hasStableIds() jest:
public boolean hasStableIds()

Metoda ta powinna powraca z wartoci true, w przypadku gdy identyfikator obiektu z metody getItemId() wskazuje ten sam obiekt. Metoda ta bardzo przypomina analogiczn metod
spotykan w adapterach listy, ktre zostay omwione w rozdziale 6.
Metoda zwrotna onDataSetChanged()
Sygnatura metody onDataSetChanged() jest nastpujca:
public void onDataSetChanged()

Metoda ta jest wywoywana, gdy klasa AppWidgetManager otrzyma informacj o zmianie wprowadzonej w widecie, ktry przechowuje zdalny widok listy. Wywoanie menedera widetw
dotrze w kocu do klasy fabrykujcej w postaci metody onDataSetChanged(). W odpowiedzi musimy skonfigurowa wykorzystywane dane, dziki czemu pozostae metody zwrotne,
takie jak getViewAt() albo getCount(), bd mogy zareagowa na nowe informacje. Zgodnie
z dokumentacj dozwolone s w tym przypadku dugotrwae operacje suce do konfigurowania danych.
Na tym zakoczymy dyskusj dotyczc uwidocznienia widoku listy w widecie. Zajmijmy si
teraz zagadnieniem doczenia zdarze kliknicia do widoku listy, a nawet do jego widokw
potomnych.

Konfigurowanie zdarze onClick


Konfigurowanie zdarze onClick dla zdalnych widokw listy jest procesem dwuetapowym.
Najpierw rejestrujemy zdarzenie onClick wobec widoku listy w metodzie onUpdate() dostawcy
widetw. Nastpnie przeprowadzamy t sam czynno wobec kadego elementu potomnego
tej listy w metodzie getViewAt() klasy fabrykujcej.
Na pocztku pokaemy, w jaki sposb rejestrowa zdarzenia klikni w gwnym widoku listy.
Po skonfigurowaniu takiego zdarzenia w widoku zdalnym musimy spowodowa uruchomienie
intencji po klikniciu widoku zdalnego. Poniewa dostawca widetu jest odbiorc komunikatw, moe sta si on obiektem docelowym tej intencji. Musimy nastpnie wprowadzi odpowiednie zastrzeenia w tym dostawcy widetu, co pozwoli na zmodyfikowanie metody zwrotnej
onReceive() w taki sposb, e bdzie moliwa obsuga tej intencji.
Fragment kodu z listingu 31.11 pokazuje, w jaki sposb konfigurujemy intencj zdarzenia
onClick, gdzie obiektem docelowym jest dostawca widetu.

Listing 31.11. Utworzenie intencji umoliwiajcej samoistne wywoanie dostawcy widetu


Intent onListClickIntent =
new Intent(context,TestListWidgetProvider.class);

Zwrmy uwag na sposb konfiguracji nazwy klasy dostawcy widetu, ktra staje si docelowym skadnikiem intencji. Intencja ta zostanie dostarczona do dostawcy widetu. Dostawca ten
odpowiada jednak rwnie na inne intencje pochodzce z innych dziaa zwizanych z widetem. Aby odrni t intencj od pozostaych, musimy ustawi dla niej jawne dziaanie. Na
listingu 31.12 zosta ukazany przykad.

1118

Android 3. Tworzenie aplikacji

Listing 31.12. Definiowanie unikatowego dziaania w dostawcy widetu dla zdarzenia kliknicia
onListClickIntent.setAction(
TestListWidgetProvider.ACTION_LIST_CLICK);

Oczywicie, dziaanie TestListWidgetProvider.ACTION_LIST_CLICK jest niestandardowe i najlepiej je definiowa jako cz dostawcy widetu TestListWidgetProvider.
Poniewa kliknicia mog wystpowa wrd wielu wystpie tego widetu, musimy wczyta
identyfikator okrelonego widetu jako dodatkowe dane intencji wywoujcej. Odpowiednie
rozwizanie prezentujemy na listingu 31.13.
Listing 31.13. Wczytywanie identyfikatora widetu do intencji zdarzenia onClick
onListClickIntent.putExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

Intencja jest ju niemal gotowa do ustanowienia wobec zdarze onClick w zdalnym widoku listy.
Musimy przeprowadzi w niej jeszcze jedn czynno. Gdy intencje maj pojawi si dopiero
pniej, staj si intencjami oczekujcymi. W rozdziaach 5. (dotyczcym intencji) oraz 15.
(w ktrym omwilimy menedery alarmw) Czytelnik znajdzie wicej informacji na temat
intencji oczekujcych.
Intencja oczekujca nie uwzgldnia adnych definiowanych w niej dodatkowych danych, chyba
e informacje te decyduj o jej unikatowoci. Te dodatkowe dane intencji nie s jednak brane
pod uwag, jeeli dana intencja jest unikatowa. Aby rozwiza ten problem, musimy wprowadzi w intencji metod toUri().
Metoda ta pobiera wszystkie dodatkowe dane intencji i generuje dugi cig znakw reprezentujcy intencj. Na kocu tego cigu znakw znajd si dane dodatkowe intencji. Wprowadzenie tego cigu znakw jako porcji danych jest rwnoznaczne z nadaniem intencji unikatowoci. Wynika to z faktu, e te dane zostan pobrane przez intencj gwnie w celu jej odrnienia
od innych intencji. Na listingu 31.14 widzimy przykad definiowania niepowtarzalnej intencji
za pomoc metody toUri().
Listing 31.14. Zastosowanie metody toUri()
onListClickIntent.setData(
Uri.parse(
onListClickIntent.toUri(Intent.URI_INTENT_SCHEME)));

Po zdefiniowaniu unikatowoci intencji uzyskujemy dostp do niezbdnej intencji oczekujcej,


co zostao zademonstrowane na listingu 31.15.
Listing 31.15. Utworzenie intencji oczekujcej na komunikat
PendingIntent onListClickPendingIntent =
PendingIntent.getBroadcast(context, 0,
onListClickIntent,
PendingIntent.FLAG_UPDATE_CURRENT);

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1119

Na listingu 31.15 flaga FLAG_UPDATE_CURRENT oznacza, e po znalezieniu podobnej intencji zostan zaktualizowane jedynie jej dodatkowe dane. Nieco pniej, kiedy bdziemy omawia sposb wykorzystywania intencji oczekujcej przez widoki zdalne, wyjanimy dokadniej, dlaczego
wspomniany proces moe si okaza konieczny.
Po utworzeniu intencji oczekujcej, na przykad takiej, jak zaprezentowalimy na listingu
31.15, moemy ustanowi reakcj na kliknicia widoku listy. Wykorzystujemy tutaj metod
setPendingIntentTemplate() do ustanowienia relacji pomidzy intencj oczekujc a widokiem
listy. Na listingu 31.16 widzimy przykad zastosowania metody setPendingIntentTemplate().
Listing 31.16. Zastosowanie metody setPendingIntentTemplate()
RemoteViews rv;
rv.setPendingIntentTemplate(R.id.listwidget_list_view_id,
onListClickPendingIntent);

Pierwszym argumentem na listingu 31.16 jest identyfikator widoku listy umieszczonego


w gwnym ukadzie graficznym (listing 31.1). Drugi argument stanowi intencja oczekujca,
ktr przygotowywalimy w kodzie na listingach od 31.11 do 31.14. Zwrmy uwag, e w kodzie z listingu 31.16 wywoujemy szablon intencji oczekujcej. Skd si wzi wyraz szablon
(ang. template)?
Zgodnie z informacjami zawartymi w dokumentacji zestawu SDK twrcy systemu Android nie
zalecaj tworzenia osobnych intencji oczekujcych dla kadego wiersza listy. Zamiast tego naley raczej utworzy intencj oczekujc dla caej listy, a nastpnie w odpowiedzi na kliknicia poszczeglnych elementw listy po prostu przesania dodatkowe dane tej intencji. Zadanie to jest atwiejsze, jeli utworzymy jedn intencj oczekujc na poziomie listy, a nastpnie
przelemy j ponownie wraz z rnymi danymi dodatkowymi. Dlatego wanie intencja oczekujca z listingu 31.15 posiada flag definiujc aktualizowanie jej dodatkowych danych.
Przyjrzyjmy si teraz, w jaki sposb dodatkowe dane s dostarczane z poszczeglnych elementw zdalnego widoku listy. Jak mona si byo tego spodziewa, operacja ta jest przeprowadzana
w tym samym miejscu, w ktrym s tworzone te elementy widoku zdalnego, a dokadniej w metodzie getViewAt() klasy fabrykujcej (listing 31.9). Na listingu 31.17 przedstawilimy mechanizm doczania intencji zawierajcych dodatkowe dane do elementu listy po jego klikniciu.
Listing 31.17. Doczenie intencji zawierajcej dodatkowe dane do elementu listy po jego klikniciu
//Wczytuje zdalny widok elementu listy
RemoteViews listItemRv;

//Pobiera zupenie now intencj


Intent ei = new Intent();

//Dodaje do intencji zdefiniowane przez nas dodatkowe dane


ei.putExtra("com.androidbook.widgets.unikatowe_dane_dodatkowe_w_postaci_ciagu_znakow",
"Pozycja kliknitego elementu:" + position);

//Dodatkowe dane zostaj ustawione wobec zdalnego widoku listy


listItemRv.setOnClickFillInIntent(R.id.textview_widget_list_item_id, ei);

1120

Android 3. Tworzenie aplikacji

Kluczow metod na listingu 31.17 jest setOnClickFillIntent(). Umoliwia nam ona dostarczanie nowej intencji zawierajcej zdefiniowane dane dodatkowe, ktre zostan wczytane.
Dane te zostan pobrane przez wewntrzn struktur i naoone na szablon intencji oczekujcej,
ktry zosta skonfigurowany jako cz zdarzenia onClick.
Na listingu 31.17 pobralimy jedynie tekst z biecego wiersza, nieco go zmodyfikowalimy
i wstawilimy w postaci dodatkowych danych. Dziki widocznemu na tym listingu kodowi po
klikniciu elementu listy bdcej czci widetu pojawi si intencja, ktra zostanie przesana
wraz z dodatkowymi danymi do odbiorcy komunikatw. Przyjrzyjmy si wic, w jaki sposb
naley przygotowa takiego odbiorc, aby odczyta dane zdefiniowane dla kadego elementu listy.

Odpowied na zdarzenia onClick


W szablonie intencji oczekujcej, ktry zdefiniowano dla widoku listy (listing 31.16), zauwaymy dwie nastpujce kwestie:
Przywoywanym skadnikiem jest sam dostawca widetu.
Przyporzdkowano okrelone dziaanie, specyficzne dla tego dostawcy widetu.
Dostawca widetu musi w odpowiedzi:
1. Zadeklarowa w postaci cigu znakw rozpoznawalne dziaanie.
2. Przesoni metod onReceive() i zareagowa na dziaanie wymienione w punkcie 1.
Na listingu 31.18 ukazalimy sposb definiowania unikatowego dziaania w postaci staej reprezentowanej przez cig znakw.
Listing 31.18. Definicja niestandardowego dziaania
public static final String ACTION_LIST_CLICK =
"com.androidbook.homewidgets.listclick";

Na listingu 31.19 widzimy mechanizm przesaniania metody onReceive(). Pokazalimy sposb testowania dziaania intencji oraz wywoania metody dealwithListAction(). Na kocu
tej metody musimy wywoa metod onReceive() bazowej klasy dla wszystkich pozostaych
dziaa. Jeeli tego nie zrobimy, sam widet nie bdzie odbiera adnych dziaa.
Listing 31.19. Przesanianie metody onReceive()
@Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction()
.equals(TestListWidgetProvider.ACTION_LIST_CLICK))
{

//Nie jest to jedno z dziaa widetu


//Jest to specyficzne dziaanie, ktre zostao tutaj skierowane
}

dealwithListAction(context,intent);
return;

//Upewnijmy si, e zostanie wywoana


super.onReceive(context, intent);
}

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1121

Na listingu 31.20 prezentujemy metod dealwithListAction(), w ktrej odczytujemy dodatkowe dane, zaadowane do intencji w kodzie z listingu 31.17.
Listing 31.20. Odpowiadanie na zdarzenie onClick elementu listy
public void dealwithListAction(Context context, Intent intent)
{
String clickedItemText =
intent.getStringExtra(
TestListWidgetProvider.EXTRA_LIST_ITEM_TEXT);
if (clickedItemText == null)
{
clickedItemText = "Bd";
}
clickedItemText =
clickedItemText
+ "Zosta kliknity element:"
+ clickedItemText;
Toast t =
Toast.makeText(context,clickedItemText,Toast.LENGTH_LONG);
t.show();
}

Na listingu 31.20 odczytalimy dodatkowe dane za pomoc uprzednio utworzonej staej i wprowadzilimy obiekt typu Toast. Metoda ta jest przetwarzana w gwnym wtku, musimy wic
upewni si, e nie bd w niej przeprowadzane dugotrwae operacje (aspekt ten zosta dokadnie omwiony w rozdziale 14., dotyczcym dugoterminowych usug).
Zakoczymy w ten sposb cz teoretyczn dotyczc nowych funkcji wprowadzonych w widetach zawierajcych widoki listy. Przetestujmy je teraz w dziaajcej, przykadowej aplikacji.
Wiksza cz zaprezentowanego dotychczas kodu pochodzi wanie z tego projektu, zatem jego
zrozumienie nie powinno stanowi problemu.

Dziaajcy przykad
testowy widet ekranu startowego oparty na licie
Prezentowany w tym punkcie przykadowy widet posuy do zilustrowania omawianych koncepcji dotyczcych widetw opartych na listach. Po utworzeniu tego projektu ujrzymy widet
wywietlajcy list, ktry bdzie mona przeciga po ekranie startowym. W czasie takiego
przecigania zobaczymy 20 elementw listy wypenionych przykadowym tekstem. Po klikniciu
jednego z tych elementw na ekranie startowym zostanie wywietlony obiekt typu Toast,
zawierajcy tekst z wybranego pola listy.
Poniej wymienilimy wszystkie potrzebne pliki:
TestListWidgetProvider.java jest gwn klas; mamy tu do czynienia z testowym
dostawc widetw, ktry implementuje widet zawierajcy (pord innych widokw)
widok listy (listing 31.21).
TestRemoteViewsFactory.java jest klas zawierajc zbir elementw, ktre
po wczytaniu przez dostawc widetw zostan wywietlone w widoku listy
(listing 31.22).

1122

Android 3. Tworzenie aplikacji

TestRemoteViewsService.java stanowi usug widoku zdalnego, ktra tworzy


wystpienie klasy TestRemoteViewsFactory (listing 31.23).
Layout/test_list_widget_layout.xml to gwny ukad graficzny caego widetu wczytanego
przez dostawc widetw (listing 31.1).
Layout/list_item_layout.xml jest plikiem ukadu graficznego zdefiniowanego dla widoku
pojedynczego elementu listy. Ukad ten jest wczytywany przez klas fabrykujc
(listing 31.10).
Drawable/box1.xml stanowi prost klas pomocnicz, pozwalajc na zaokrglenie
rogw w gwnym ukadzie graficznym widetu (listing 31.2).
Xml/test_list_appwidget_provider.xml to plik metadanych, sucy do definiowania
widetu w Androidzie (listing 31.24).
AndroidManifest.xml, czyli plik konfiguracyjny aplikacji, w ktrym definiujemy
dostawc widetw oraz usug zdalnego widoku (listing 31.25).

Utworzenie testowego dostawcy widetw


Proces tworzenia widetu ekranu startowego rozpoczynamy od utworzenia dostawcy widetw
dziedziczcego po klasie AppWidgetProvider oraz przeadowania jego metody onUpdate()
w celu dostarczenia widetowi widoku. Proces ten zosta dokadnie objaniony w rozdziale 22.
W tym przykadzie nasz dostawca zosta nazwany TestListWidgetProvider. Na listingu 31.21
zosta umieszczony jego kod rdowy opatrzony komentarzami.
Listing 31.21. TestListWidgetProvider.java
package com.androidbook.homewidgets.listwidget;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
public class TestListWidgetProvider extends AppWidgetProvider
{
private static final String tag = "TestListWidgetProvider";
public static final String ACTION_LIST_CLICK =
"com.androidbook.homewidgets.listclick";
public static final String EXTRA_LIST_ITEM_TEXT =
"com.androidbook.homewidgets.list_item_text";
public void onUpdate(Context context,
AppWidgetManager appWidgetManager,
int[] appWidgetIds)
{
Log.d(tag, "Wywolana metoda onUpdate");
final int N = appWidgetIds.length;
Log.d(tag, "Liczba widzetow:" + N);
for (int i=0; i<N; i++)
{
int appWidgetId = appWidgetIds[i];
updateAppWidget(context, appWidgetManager, appWidgetId);

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

}
super.onUpdate(context,appWidgetManager, appWidgetIds);
}
public void onDeleted(Context context, int[] appWidgetIds)
{
Log.d(tag, "Wywolana metoda onDeleted");
super.onDeleted(context,appWidgetIds);
}
public void onEnabled(Context context)
{
Log.d(tag, "Wywolana metoda onEnabled");
super.onEnabled(context);
}
public void onDisabled(Context context)
{
Log.d(tag, "Wywolana metoda onDisabled");
super.onEnabled(context);
}
private void updateAppWidget(Context context,
AppWidgetManager appWidgetManager,
int appWidgetId)
{
Log.d(tag, "Wywolana metoda onUpdate dla widzetu:" + appWidgetId);
final RemoteViews rv =
new RemoteViews(context.getPackageName(),
R.layout.test_list_widget_layout);
rv.setEmptyView(R.id.listwidget_list_view_id,
R.id.listwidget_empty_view_id);

// Okrela usug dostarczajc dane do


// zbiorczego widetu. Zwrmy uwag, e musimy
// wstawi obiekt appWidgetId za pomoc danych,
// w przeciwnym wypadku zostanie zignorowany.
final Intent intent =
new Intent(context, TestRemoteViewsService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
appWidgetId);
intent.setData(
Uri.parse(
intent.toUri(Intent.URI_INTENT_SCHEME)));
rv.setRemoteAdapter(appWidgetId,
R.id.listwidget_list_view_id, intent);

//Konfiguruje metod zwrotn widoku listy.


//Wymagana jest unikatowa intencja oczekujca
//dla tego identyfikatora widetu. Intencja wysya do samej siebie wiadomo,
// ktra zostanie odebrana w metodzie onReceive.

1123

1124

Android 3. Tworzenie aplikacji

Intent onListClickIntent =
new Intent(context,TestListWidgetProvider.class);

//Ustanawia takie dziaanie, dziki ktremu odbiorca bdzie mg odrni je


//od pozostaych dziaa powizanych z widetami.
onListClickIntent.setAction(
TestListWidgetProvider.ACTION_LIST_CLICK);

//Odbiorca ten obsuguje wszystkie wystpienia widetu,


//zatem musimy wiedzie, dla ktrego wystpienia
//przeznaczony jest ten komunikat.
onListClickIntent.putExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

//Definiujemy unikatowo intencji w trakcie przygotowywania


//intencji oczekujcej.
//Metoda toUri wczytuje dodatkowe dane jako
//cz identyfikatora URI.
//Dane tej intencji nie s w ogle uywane, su jedynie
//do definiowania unikatowoci intencji oczekujcej.
//Spjrzmy na metod intent.filterEquals(), aby dowiedzie si,
//w jaki sposb intencje s ze sob porwnywane.
onListClickIntent.setData(
Uri.parse(
onListClickIntent.toUri(Intent.URI_INTENT_SCHEME)));

//Musimy pniej dostarczy t intencj w postaci komunikatu


//do tego samego odbiorcy po
//klikniciu zdalnego widoku.
final PendingIntent onListClickPendingIntent =
PendingIntent.getBroadcast(context, 0,
onListClickIntent,
PendingIntent.FLAG_UPDATE_CURRENT);

//Ustanawiamy szablon intencji oczekujcej dla


//elementu listy.
//Kady widok w licie bdzie musia definiowa wtedy
//wasny zestaw danych dodawanych do
//tego szablonu, a nastpnie przesya taki
//ostateczny szablon.
//Przyjrzyjmy si, w jaki sposb metoda RemoteViewsFactory()
//konfiguruje kady element w widoku listy.
//Przejrzyjmy take dokumentacj metody RemoteViews.setFillIntent().
rv.setPendingIntentTemplate(R.id.listwidget_list_view_id,
onListClickPendingIntent);

//Aktualizuje widet
appWidgetManager.updateAppWidget(appWidgetId, rv);
}
@Override

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1125

public void onReceive(Context context, Intent intent)


{
if (intent.getAction()
.equals(TestListWidgetProvider.ACTION_LIST_CLICK))
{

//Nie jest to jedno z dziaa widetu.


//Jest to specyficzne dziaanie, ktre zostao tutaj skierowane.
dealwithListAction(context,intent);
return;
}

//Upewnijmy si, e zostanie wywoana.


super.onReceive(context, intent);
}
public void dealwithListAction(Context context, Intent intent)
{
String clickedItemText =
intent.getStringExtra(
TestListWidgetProvider.EXTRA_LIST_ITEM_TEXT);
if (clickedItemText == null)
{
clickedItemText = "Bd";
}
clickedItemText =
clickedItemText
+ "Zosta wybrany element:"
+ clickedItemText;
Toast t =
Toast.makeText(context,clickedItemText,Toast.LENGTH_LONG);
t.show();
}
}//eof-class

Po teoretycznym wstpie z poprzedniego punktu Czytelnik powinien zrozumie wiele z zada


wykonywanych przez ten plik. Kod rdowy zosta rwnie bogato opatrzony komentarzami,
podsumowujcymi dotychczas przekazane informacje; poniej jednak prezentujemy krtkie
podsumowanie dziaania tej klasy:
1. Wczytujemy widok w metodzie onUpdate().
2. Lokalizujemy zdalny widok listy i podczamy go do klasy fabrykujcej za pomoc
usugi.
3. Ustanawiamy zdarzenia onClick zdalnego widoku za pomoc szablonu intencji
oczekujcej.
4. Przesaniamy metod onReceive() i przetwarzamy niestandardowe dziaanie
onClick.
Nie naley kompilowa tego pliku przed zaadowaniem do rodowiska Eclipse pozostaych plikw wymaganych do dziaania projektu, gdy znajduj si w nim odniesienia do tych pozostaych plikw.

1126

Android 3. Tworzenie aplikacji

Utworzenie klasy fabrykujcej


Na listingu 31.22 zosta zamieszczony kod rdowy klasy fabrykujcej, ktra wypenia widok
listy.
Listing 31.22. TestRemoteViews[BP3]Factory.java
package com.androidbook.homewidgets.listwidget;

//
//Za pomoc skrtu klawiaturowego Ctrl+Shift+O uzupenimy instrukcje importw
//
class TestRemoteViewsFactory
implements RemoteViewsService.RemoteViewsFactory
{
private Context mContext;
private int mAppWidgetId;
private static String tag="TRVF";
public TestRemoteViewsFactory(Context context, Intent intent)
{
mContext = context;
mAppWidgetId =
intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
Log.d(tag,"Utworzono klase fabrykujaca");
}

//Wywoana podczas pierwszego utworzenia klasy fabrykujcej.


//Jedna klasa fabrykujca moe by wspdzielona pomidzy wieloma
//obiektami RemoteViewsAdapter, w zalenoci od przekazanej intencji.
public void onCreate()
{
Log.d(tag,"Wywolano metode onCreate dla identyfikatora widzetu:" + mAppWidgetId);
}

//Wywoana w momencie odczenia ostatniego obiektu


//RemoteViewsAdapter powizanego z t klas fabrykujc.
public void onDestroy()
{
Log.d(tag,"Wywolano metode onDestroy dla identyfikatora widzetu:" +
mAppWidgetId);
}

//Cakowita liczba elementw listy.


public int getCount()
{
return 20;
}
public RemoteViews getViewAt(int position)
{
Log.d(tag,"Wywolano metode getView:" + position);
RemoteViews rv =
new RemoteViews(

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

this.mContext.getPackageName(),
R.layout.list_item_layout);
String itemText = "Element:" + position;
rv.setTextViewText(
R.id.textview_widget_list_item_id, itemText);
this.loadItemOnClickExtras(rv, position);
return rv;
}
private void loadItemOnClickExtras(RemoteViews rv, int position)
{
Intent ei = new Intent();
ei.putExtra(TestListWidgetProvider.EXTRA_LIST_ITEM_TEXT,
"Pozycja kliknietego obiektu:" + position);
rv.setOnClickFillInIntent(R.id.textview_widget_list_item_id, ei);
return;
}

//Za jej pomoc moemy wykorzysta niestandardowy widok wczytywania,


//ktry pojawia si pomidzy wywoaniem i powrotem metody
//getViewAt(int). W przypadku powrotu z wartoci null,
//wykorzystany zostanie domylny widok wczytywania.
public RemoteViews getLoadingView()
{
return null;
}

//Liczba rnych rodzajw widokw


//tworzcych t list.
public int getViewTypeCount()
{
return 1;
}

//Wewntrzny identyfikator obiektu


//na danej pozycji.
public long getItemId(int position)
{
return position;
}

//Zostanie wstawiona warto true, jeli ten sam identyfikator


//odnosi si zawsze do tego samego obiektu.
public boolean hasStableIds()
{
return true;
}

//Wywoywana w momencie uruchomienia metody notifyDataSetChanged()


//w adapterze widoku zdalnego. W ten sposb klasa RemoteViewsFactory
//moe reagowa na zmiany wprowadzone w danych poprzez aktualizowanie
//wszelkich wewntrznych odniesie.
//Uwaga: mona bezpiecznie przeprowadza zasobochonne zadania
//wewntrz tej metody, w synchroniczny sposb.
//W midzyczasie bd wywietlane stare informacje

1127

1128

Android 3. Tworzenie aplikacji

//wewntrz widetu.
public void onDataSetChanged()
{
Log.d(tag,"onDataSetChanged");
}
}

Wikszo powyszego kodu zostaa ju wczeniej omwiona. Klasa ta definiuje obecno dwudziestu wierszy. Ukad graficzny kadego wiersza zostaje wczytany z pliku, a zawarty w nim tekst
zostaje umiejscowiony w odpowiedniej pozycji. Nastpnie zostaje wczytany tekst z kadej pozycji
do intencji dziaania onClick. Ten wanie tekst zostaje umieszczony w kontrolce typu Toast.

Kod usugi zdalnego widoku


Na listingu 31.23 zaprezentowalimy kod rdowy usugi, ktra przekazuje klas fabrykujc
zdalnego widoku.
Listing 31.23. TestRemoteViewsService.java
package com.androidbook.homewidgets.listwidget;
import android.content.Intent;
public class TestRemoteViewsService
extends android.widget.RemoteViewsService
{
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent)
{
return new TestRemoteViewsFactory(
this.getApplicationContext(), intent);
}
}

Gwny plik ukadu graficznego widetu


Gwny plik ukadu graficznego definiujcy wygld widetu na stronie startowej powinien zosta
umieszczony w katalogu /res/layout/test_list_widget_layout.xml (przypominamy, e zawarto
tego pliku zostaa zaprezentowana na listingu 31.1). Do gwnego ukadu graficznego zaliczamy
rwnie funkcj zaokrglania rogw, ktra zostaa umieszczona w osobnym pliku. Plik ten znajduje si w katalogu /res/drawable/box1.xml, a jego definicj znajdziemy na listingu 31.2.
Ukad graficzny pojedynczych elementw listy
Plik ten definiuje wygld pojedynczego elementu stanowicego skadnik listy. Plik ten musi zosta
umieszczony w katalogu layout/list_item_layout.xml. Zosta on pokazany na listingu 31.10.

Metadane dostawcy widetw


Dostawca widetw wymaga doczenia pliku metadanych podczas jego definiowania w pliku
manifecie. Plik ten umieszcza si w katalogu /res/xml/test_list_appwidget_provider.xml. Prezentujemy jego tre na listingu 31.24.

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1129

Listing 31.24. Plik zawierajcy informacje o widecie


<!-- xml/test_list_widget_layout.xml -->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="222dp"
android:minHeight="222dp"
android:updatePeriodMillis="1000000"
android:initialLayout="@layout/test_list_widget_layout"
android:label="Testowy widet zawierajcy list"
>
</appwidget-provider>

W pliku metadanych ustalamy rozmiar widetu oraz definiowan w milisekundach czsto


wywoa metody onUpdate. Przypominamy, e plik tego typu zosta dokadniej omwiony
w rozdziale 22.

AndroidManifest.xml
Listing 31.25 zawiera kod pliku konfiguracyjnego aplikacji. Zaznaczylimy pogrubion czcionk
definicje dostawcy widetw oraz usugi zdalnego widoku.
Listing 31.25. Plik AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.homewidgets.listwidget"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon"
android:label="Testowy widet wywietlajcy list">

<!-**********************************************************************
* Dostawca testowego widetu wywietlajcego list
**********************************************************************
-->
<receiver android:name=".TestListWidgetProvider">
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/test_list_appwidget_provider" />
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
</receiver>

<!-- Usuga dostarczajca obiekty RemoteViews do widetu zbiorczego -->


<service android:name=".TestRemoteViewsService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />
</application>
<uses-sdk android:minSdkVersion="11" />
</manifest>

1130

Android 3. Tworzenie aplikacji

Testowanie widetu wywietlajcego list


Po utworzeniu i wdroeniu projektu w rodowisku Eclipse otrzymamy komunikat o jego
udanej instalacji. Poniewa nie wprowadzilimy do projektu adnej aktywnoci, ktr moglibymy uruchomi, domylnie nie zobaczymy niczego nowego w oknie emulatora.
Aby zainstalowa utworzony w tym przykadzie widet, musimy najpierw zajrze do listy
dostpnych widetw. Kliknicie ikony ekranu startowego spowoduje wywietlenie okna dostpnych widetw, co zostao zilustrowane na rysunku 31.2.

Rysunek 31.2. Lista widetw

Nasz widet nosi nazw Testowy widet wywietlajcy list, moe wic znajdowa si na samym
kocu listy, co oznacza, e musimy przewin ekran widetw w prawo. Widzimy to na rysunku 31.3.

Rysunek 31.3. Przewijanie ekranu w praw stron w poszukiwaniu utworzonego widetu

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1131

Moemy teraz przecign Testowy widet wywietlajcy list na dowoln stron ekranu startowego. Po rozpoznaniu operacji przecigania przez system klikamy przycisk ekranu startowego,
aby go wywietli. W midzyczasie ujrzymy podstawow form widetu, ktr zaprezentowalimy na rysunku 31.1. Jeeli klikniemy ktry z elementw listy, pojawi si kontrolka Toast zawierajca odpowiedni komunikat zwizany z tym elementem (rysunek 31.4).

Rysunek 31.4. Obiekt Toast generowany wskutek kliknicia elementu listy

Na tym zakoczymy omwienie usprawnie wprowadzonych do widetw w wersji 3.0 Androida. Przejdmy teraz do zupenie nowego interfejsu przecigania.

Funkcja przecigania
Przed pojawieniem si wersji 3.0 systemu nie istniaa moliwo bezporedniej obsugi funkcji
przecigania elementw. W rozdziale 25. pokazalimy, w jaki sposb przesuwa dany widok po
ekranie; dowiedzielimy si rwnie, e mona wykorzysta biece pooenie przeciganego
obiektu, aby ustali, czy pod nim znajduje si jaki inny element. Po otrzymaniu zdarzenia
MotionEvent oznaczajcego oderwanie palca od wywietlacza aplikacja moe rozpozna, czy
jest to rwnoznaczne z upuszczeniem obiektu. Chocia wprowadzenie takiej funkcji jest moliwe, zdecydowanie wygodniejszym rozwizaniem byaby bezporednia implementacja operacji
przecigania. Otrzymalimy j wanie w wersji 3.0 Androida.

Podstawowe informacje
o funkcji przecigania w wersji 3.0 Androida
Na najbardziej oglnym poziomie proces przecigania rozpoczyna si od zadeklarowania widoku,
w ktrym zosta on zainicjalizowany, nastpnie wszystkie zaangaowane w t operacj elementy
ledz zjawisko przesuwania obiektu, dopki nie zostanie wykryte zdarzenie upuszczenia. Jeeli
zdarzenie upuszczenia zostanie wykryte przez widok, ktry je odbierze, caa operacja przecigania zostanie zakoczona sukcesem. Jeeli aden widok nie odbierze zdarzenia upuszczenia

1132

Android 3. Tworzenie aplikacji

bd jeli widok odbierajcy nie moe go przyj, przeciganie okae si nieudane. Informacje
o procesie przecigania s przekazywane przez obiekt DragEvent, ktry jest przekazywany do
wszystkich dostpnych obiektw nasuchujcych pod tym ktem.
Wewntrz obiektu DragEvent znajdziemy deskryptory przechowujce mnstwo informacji,
ktre s zalene od inicjalizatora operacji przecigania. Na przykad obiekt DragEvent moe
zawiera odniesienia do samego inicjalizatora, informacji o stanie, danych tekstowych, identyfikatorw URI oraz zasadniczo wszelkich innych elementw, jakie chcielibymy przekaza za
pomoc sekwencji przecigania.
Moemy przekazywa informacje, ktre w rezultacie umoliwiaj dynamiczn komunikacj
pomidzy widokami, jednak pocztkowe dane zawarte w obiekcie DragEvent, ktre zostaj do
niego doczone w momencie utworzenia, nie ulegaj potem zmianom. Oprcz danych obiekt
DragEvent zawiera take warto dziaania, definiujc biece zachowanie w sekwencji przecigania, a take informacje o pooeniu, okrelajce lokalizacj przeciganego obiektu na ekranie.
Obiekt DragEvent moe okreli jedno z szeciu nastpujcych dziaa:
ACTION_DRAG_STARTED definiuje rozpoczcie nowej sekwencji przecigania.
ACTION_DRAG_ENTERED oznacza przecignicie obiektu do wntrza okrelonego widoku.
ACTION_DRAG_LOCATION okrela przecignicie obiektu do nowej lokalizacji na ekranie.
ACTION_DRAG_EXITED, gdy obiekt zostaje przecignity na zewntrz okrelonego
widoku.
ACTION_DROP wystpuje wtedy, gdy uytkownik puszcza przecigany obiekt.
Decyzja, czy nastpio faktyczne zjawisko upuszczenia, naley do odbiorcy tego
zdarzenia.
ACTION_DRAG_ENDED informuje wszystkie obiekty nasuchujce procesu przecigania
o zakoczeniu sekwencji przecigania. Metoda DragEvent.getResult() suy do
okrelenia, czy upuszczenie obiektu zostao zakoczone powodzeniem.
Moe si wydawa, e powinnimy skonfigurowa obiekt nasuchujcy sekwencji przecigania
w kadym widoku bdcym czci tego procesu. W rzeczywistoci moemy zdefiniowa obiekt
nasuchujcy wobec dowolnego elementu znajdujcego si w aplikacji i bdzie on otrzymywa
wszystkie zdarzenia sekwencji przecigania wystpujce we wszystkich widokach systemu. Moemy w ten sposb nieco skomplikowa sytuacj, poniewa obiekt nasuchujcy zdarzenia
przecigania nie musi by powizany ani z przeciganym obiektem, ani nawet z docelowym
miejscem. Obiekt nasuchujcy moe zarzdza ca koordynacj sekwencji przecigania.
Jeeli przyjrzymy si przykadowemu projektowi dostpnemu w zestawie Android SDK, zauwaymy, e w rzeczywistoci obiekt nasuchujcy zostaje powizany z kontrolk TextView, ktra
nie ma nic wsplnego z faktyczn sekwencj przecigania. Prezentowana przez nas aplikacja
posiada obiekty nasuchujce poczone z okrelonymi widokami. Kady z takich obiektw
nasuchujcych otrzymuje zdarzenie DragEvent, pojawiajce si w sekwencji przecigania.
Oznacza to, e dany widok moe otrzyma obiekt DragEvent i zignorowa go, poniewa
dotyczy zupenie innego widoku. Oznacza to take, e obiekt nasuchujcy musi posiada
jaki mechanizm rozrniania w kodzie, a obiekt DragEvent powinien posiada wystarczajc
ilo informacji do okrelania przeprowadzanych czynnoci.
Jeeli obiekt nasuchujcy otrzyma zdarzenie DragEvent informujce jedynie o przeciganiu
nieznanego elementu oraz o jego wsprzdnych (15, 57), to obiekt ten niewiele bdzie mg
zrobi. O wiele przydatniejszy bdzie obiekt DragEvent, ktry przekazuje dane definiujce prze-

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1133

cigany element, okrelajce jego pooenie w pozycji (15, 57), jak rwnie wskazujce, e mamy
do czynienia z operacj kopiowania. Dodatkowo ta informacja jest zawarta w odpowiednim identyfikatorze URI. Jeli zastosujemy taki obiekt DragEvent, to w momencie upuszczenia elementu
bdziemy posiada wystarczajco wiele informacji, aby przeprowadzi proces kopiowania.

Przykadowa aplikacja prezentujca funkcj przecigania


Tworzc nasz przykadow aplikacj, wykorzystamy podstawow koncepcj stosowan w wersji
3.0 Androida fragmenty. Pozwoli nam to udowodni midzy innymi, e operacja przecigania przekracza rwnie granice fragmentw. Utworzymy zbir kek z lewej strony ekranu,
a z prawej docelowy kwadrat. Po uchwyceniu kka za pomoc dugiego kliknicia zmieni
ono kolor, a w trakcie przecigania bdziemy widzie jego cie. Gdy kko zostanie przecignite nad kwadrat, zacznie on wieci. Jeeli upucimy teraz kko w obszarze kwadratu, pojawi
si komunikat, e doliczono kolejne upuszczenie do cakowitej puli zliczanych upuszcze.
Poza tym obszar kwadratu przestanie wieci, a oryginalne kko powrci do swojego pierwotnego koloru.

Lista plikw
Do utworzenia omawianej aplikacji bd potrzebne nastpujce pliki:
main.xml stanowi gwny ukad graficzny aplikacji, w ktrym zostay umieszczone
dwa fragmenty (listing 31.26).
palette.xml jest ukadem graficznym fragmentu zawierajcego kka umieszczone
po lewej stronie ekranu (listing 31.27).
dropzone.xml stanowi ukad graficzny fragmentu reprezentujcego docelowy obszar
po prawej stronie ekranu oraz komunikat o iloci upuszcze (listing 31.28).
MainActivity.java to najprostsza z moliwych aktywnoci. Ustanawia ona jedynie
gwny widok treci, a reszt operacji pozostawia fragmentom (listing 31.29).
Palette.java jest kodem rwnie nieskomplikowanego zbioru kek. Suy on jedynie
do rozwinicia ukadu graficznego palety (listing 31.30).
DropZone.java jest nieco bardziej zoonym kodem, poniewa implementujemy
w nim mechanizm upuszczania. Zostaje tu rozwinity ukad graficzny dropzone.xml,
a nastpnie zaimplementowany obiekt nasuchujcy (listing 31.31).
Dot.java stanowi klas niestandardowego widoku obiektw, ktre bd przecigane.
Tutaj przechowujemy mechanizm inicjalizacji sekwencji przecigania, obserwujemy
zdarzenia przecigania oraz rysujemy kka (listing 31.32).
attrs.xml definiuje dwa atrybuty XML wykorzystywane w ukadzie graficznym
palette.xml do opisu atrybutu kek (listing 31.33).
AndroidManifest.xml jest gwnym plikiem manifestem aplikacji (listing 31.34).
strings.xml zawiera cigi znakw wykorzystywane przez plik AndroidManifest.xml
(listing 31.35).

Tworzenie ukadu graficznego


przykadowej aplikacji ukazujcej funkcj przecigania
Zanim przejdziemy do kodw rdowych, zobaczmy na rysunku 31.5, jak prezentuje si nasza
aplikacja.

1134

Android 3. Tworzenie aplikacji

Rysunek 31.5. Interfejs aplikacji demonstrujcej dziaanie funkcji przecigania

Na listingu 31.26 zosta zaprezentowany gwny ukad graficzny tworzcy interfejs uytkownika,
widoczny na rysunku 31.5. Podobnie jak w przykadach omwionych w rozdziale 29., ukad ten
skada si z prostego, gwnego ukadu liniowego, w ktrym zostay poziomo rozmieszczone
dwa fragmenty. Pierwszy fragment bdzie przechowywa zbir kek, a drugi bdzie nasz stref
upuszczania obiektw.
Listing 31.26. Plik gwnego ukadu graficznego
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout/main.xml -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment class="com.androidbook.drag.drop.demo.Palette"
android:id="@+id/palette"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
<fragment class="com.androidbook.drag.drop.demo.DropZone"
android:id="@+id/dropzone"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>

Plik ukadu graficznego, ktry definiuje wygld fragmentu przechowujcego kka (listing 31.27),
jest nieco bardziej interesujcy. Poniewa fragment jest reprezentowany przez ten ukad graficzny, nie musimy wstawia w tym ukadzie graficznym znacznika fragmentu. Ukad graficzny

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1135

zostanie rozwinity i stanie si hierarchi widokw dla fragmentu przechowujcego kka. Same kka zostay zdefiniowane niestandardowo, a dwa z nich zostay rozmieszczone pionowo. Zwrmy uwag, e w definicji kek znajdziemy dwa atrybuty XML (dot:color oraz
dot:radius). Jak wida, atrybuty te okrelaj kolor oraz promie kka. Poruszymy ten temat
ponownie przy okazji omawiania pliku attrs.xml. Pozostae atrybuty stanowi standard definiowania widokw.
Listing 31.27. Plik ukadu graficznego palette.xml, definiujcy wygld kek
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout/palette.xml -->


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dot="http://schemas.android.com/apk/res/com.androidbook.drag.drop.demo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.androidbook.drag.drop.demo.Dot android:id="@+id/dot1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="30dp"
android:tag="Niebieskie kolko"
dot:color="#ff1111ff"
dot:radius="20dp"
/>
<com.androidbook.drag.drop.demo.Dot android:id="@+id/dot2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:tag="Biale kolko"
dot:color="#ffffffff"
dot:radius="40dp"
/>
</LinearLayout>

Ukad graficzny strefy upuszczania z listingu 31.28 jest rwnie bardzo atwy do zrozumienia.
Widzimy w nim zielony kwadrat oraz umieszczony obok niego komunikat tekstowy. W tym
wanie miejscu bdziemy upuszcza przecigane kka. Komunikat tekstowy bdzie informowa o liczbie upuszczonych kek.
Listing 31.28. Plik ukadu graficznego dropzone.xml
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/layout/dropzone.xml -->


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<View android:id="@+id/droptarget"
android:layout_width="75dp"
android:layout_height="75dp"

1136

Android 3. Tworzenie aplikacji

android:layout_gravity="center_vertical"
android:background="#00ff00" />
<TextView android:id="@+id/dropmessage"
android:text="0 kropek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="50dp"
android:textSize="17sp" />
</LinearLayout>

Jak widzimy na listingu 31.29, mamy do czynienia z najprostsz moliw aktywnoci. Ustanawia ona jedynie nadrzdny widok treci, ktry zostaje przyporzdkowany gwnemu ukadowi
graficznemu (zawierajcemu dwa fragmenty). Kod, ktry definiuje interesujce nas operacje,
zosta zamieszczony w pozostaych plikach.
Listing 31.29. Plik gwnej aktywnoci
package com.androidbook.drag.drop.demo;

// Jest to plik MainActivity.java


import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}

Kod z listingu 31.30 rwnie jest niezwykle prosty. Jest to kod fragmentu, a wic przesaniamy
w nim metod onCreateView() w taki sposb, aby ukad graficzny zosta rozwinity w widok,
ktry nastpnie zostaje przekazany.
Listing 31.30. Plik Palette.java
package com.androidbook.drag.drop.demo;

// Jest to plik Palette.java


import
import
import
import
import

android.app.Fragment;
android.os.Bundle;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;

public class Palette extends Fragment {


@Override

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1137

public View onCreateView(LayoutInflater inflater,


ViewGroup container, Bundle icicle) {
View v = inflater.inflate(R.layout.palette, container, false);
return v;
}
}

Odpowied na zdarzenie onDrag w strefie upuszczania


Do tego momentu skonfigurowalimy cay gwny ukad graficzny aplikacji. Na listingu 31.31
pokazalimy, w jaki sposb naley zorganizowa docelowy obszar, w ktrym bd upuszczane
obiekty.
Listing 31.31. Plik DropZone.java
package com.androidbook.drag.drop.demo;

// Jest to plik DropZone.java


import
import
import
import
import
import
import
import
import
import
import

android.animation.ObjectAnimator;
android.app.Fragment;
android.content.ClipData;
android.os.Bundle;
android.util.Log;
android.view.DragEvent;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.view.animation.CycleInterpolator;
android.widget.TextView;

public class DropZone extends Fragment {


private View dropTarget;
private TextView dropMessage;
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle icicle)
{
View v = inflater.inflate(R.layout.dropzone, container, false);
dropMessage = (TextView)v.findViewById(R.id.dropmessage);
dropTarget = (View)v.findViewById(R.id.droptarget);
dropTarget.setOnDragListener(new View.OnDragListener() {
private static final String DROPTAG = "DropTarget";
private int dropCount = 0;
private ObjectAnimator anim;
public boolean onDrag(View v, DragEvent event) {
int action = event.getAction();
boolean result = true;
switch(action) {
case DragEvent.ACTION_DRAG_STARTED:

1138

Android 3. Tworzenie aplikacji

Log.v(DROPTAG, "Sekwencja przeciagania rozpoczeta w dropTarget");


break;
case DragEvent.ACTION_DRAG_ENTERED:
Log.v(DROPTAG, "Sekwencja przeciagania wkroczyla do dropTarget");
anim = ObjectAnimator.ofFloat((Object)v, "alpha", 1f, 0.5f);
anim.setInterpolator(new CycleInterpolator(40));
anim.setDuration(30*1000); // 30 sekund
anim.start();
break;
case DragEvent.ACTION_DRAG_EXITED:
Log.v(DROPTAG, "Sekwencja przeciagania opuscila dropTarget");
if(anim != null) {
anim.end();
anim = null;
}
break;
case DragEvent.ACTION_DRAG_LOCATION:
Log.v(DROPTAG, "Sekwencja przeciagania kontynuuje w dropTarget: " +
event.getX() + ", " + event.getY());
break;
case DragEvent.ACTION_DROP:
Log.v(DROPTAG, "Upuszczenie obiektu w dropTarget");
if(anim != null) {
anim.end();
anim = null;
}
ClipData data = event.getClipData();
Log.v(DROPTAG, "Elementem danych jest " + data.getItemAt(0).getText());
dropCount++;
String message = dropCount + ". upuszczenie";
dropMessage.setText(message);
break;
case DragEvent.ACTION_DRAG_ENDED:
Log.v(DROPTAG, "Sekwencja przeciagania zakonczona w dropTarget");
if(anim != null) {
anim.end();
anim = null;
}
break;
default:
Log.v(DROPTAG, "inne dzialanie w strefie upuszczania: " + action);
result = false;
}
return result;
}
});
return v;
}
}

Docieramy w kocu to interesujcej czci kodu. Programujc stref upuszczania, musimy


utworzy rejon docelowy, do ktrego chcemy przeciga kka. Jak ju wczeniej zauwaylimy,
w ukadzie graficznym zdefiniowalimy zielony kwadrat, obok ktrego znalazo si pole tekstowe.

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1139

Poniewa strefa upuszczania rwnie jest fragmentem, przesaniamy metod onCreateView()


klasy DropZone. Pierwsz czynnoci jest rozwinicie ukadu graficznego strefy upuszczania,
a nastpnie wydobycie odniesienia widoku kwadratowego obszaru (dropTarget) oraz komunikatu tekstowego (dropMessage). Moemy wtedy skonfigurowa obiekt nasuchujcy sekwencji
przecigania nad ten obszar docelowy, dziki czemu bdziemy informowani o postpach procesu przecigania.
Obiekt nasuchujcy umieszczony w rejonie docelowym zawiera jedn metod zwrotn
onDrag(). Bdzie ona otrzymywaa odniesienie do widoku oraz obiekt DragEvent. Odniesienie to jest powizane z widokiem znajdujcym si w relacji z obiektem DragEvent. Zgodnie
z tym, co wczeniej wspomnielimy, obiekt nasuchujcy nie musi by koniecznie poczony
z widokiem, w ktrym bdzie nastpowa zdarzenie przecigania, zatem to metoda zwrotna
musi identyfikowa widok, w ktrym pojawi si sekwencja przecigania.
Jedn z pierwszych czynnoci, jak prawdopodobnie chcielibymy przeprowadzi w dowolnej
metodzie zwrotnej onDrag(), jest odczytanie dziaania pochodzcego z obiektu DragEvent. Poznamy w ten sposb stan sekwencji przecigania. Przewanie wystarczy nam odnotowanie
informacji, czy zdarzenie przecigania jest cigle w toku. Nie musimy na przykad nic robi
z dziaaniem ACTION_DRAG_LOCATION. Chcemy jednak zaimplementowa odpowiedni logik,
uruchamiajc si w przypadku przecignicia obiektu przez okrelone granice (dziaanie ACTION_
DRAG_ENTERED), ktra przestanie by aktywna po przecigniciu tego obiektu na zewntrz
danego rejonu (ACTION_DRAG_EXITED) lub po upuszczeniu tego obiektu (ACTION_DRAG_DROPPED).
Stosujemy tu omwion w rozdziale 29. klas ObjectAnimator, w tym jednak przypadku
suy nam ona do zdefiniowania cyklicznego interpolatora, modyfikujcego przezroczysto
obszaru docelowego. W ten sposb uzyskamy efekt pulsowania docelowego zielonego kwadratu.
Ma to by efekt graficzny, zachcajcy uytkownika do upuszczenia obiektu w tym rejonie.
Skoro w okrelonych warunkach wczamy animacj, musimy j rwnie wyczy w momencie
upuszczenia obiektu lub zakoczenia sekwencji przecigania. Teoretycznie nie musimy koczy
animacji w przypadku dziaania ACTION_DRAG_ENDED, rozsdniej jednak byoby to zrobi.
W przypadku tego konkretnego obiektu nasuchujcego bdziemy otrzymywa jedynie dziaania ACTION_DRAG_ENTERED i ACTION_DRAG_EXITED, jeli wystpi interakcja z powizanym
widokiem. Jak si rwnie przekonamy, zdarzenia ACTION_DRAG_LOCATION bd wystpowa
tylko w obrbie docelowego widoku.
Ostatnim naprawd interesujcym etapem jest samo dziaanie ACTION_DROP (zwrmy uwag,
e pominity zosta czon DRAG_). Jeeli dziaanie to nastpio w nasuchiwanym obszarze, to
znaczy, e uytkownik upuci kko w obszarze zielonego kwadratu. Poniewa przez cay czas
spodziewamy si, e obiekt zostanie upuszczony w tym obszarze, moemy po prostu odczyta
dane z pierwszego elementu, a nastpnie wywietli je w oknie LogCat. W przypadku uytkowej
aplikacji moemy zwrci wiksz uwag na obiekt ClipData, przechowywany w samym zdarzeniu przecigania. Za pomoc jego waciwoci moemy akceptowa lub odrzuca zdarzenie
upuszczenia.
Nadszed waciwy moment na okrelenie wyniku przecigania w metodzie onDrag(). W zalenoci od sytuacji moemy poinformowa system, e obsuylimy zdarzenie przecigania
(poprzez przekazanie wartoci true), lub moemy tego nie robi (warto false). Jeeli wewntrz obiektu zdarzenia przecigania nie ma wymaganych danych, zdecydowanie naley
przekaza warto false i poinformowa w ten sposb system, e zdarzenie nie zostao waciwie
obsuone.

1140

Android 3. Tworzenie aplikacji

Po umieszczeniu informacji dotyczcych zdarzenia przecigania w oknie LogCat zwikszamy


warto w liczniku zdarze upuszczania. Warto ta jest aktualizowana w interfejsie uytkownika i to tyle, jeli chodzi o klas DropZone.
Jeeli spojrzymy na ni z oglnej perspektywy, okae si, e wcale nie jest taka skomplikowana.
W rzeczywistoci nie umiecilimy tu kodu przetwarzajcego zdarzenia MotionEvent. Nie musielimy nawet wprowadza tu kodu sprawdzajcego, czy sekwencja przecigania ma miejsce.
Otrzymujemy jedynie odpowiednie wywoania metod zwrotnych wraz z postpem sekwencji przecigania.

Konfigurowanie widokw obiektw


stanowicych rda sekwencji przecigania
Zastanwmy si teraz, w jaki sposb zostay zorganizowane widoki zwizane z obiektami stanowicymi rda sekwencji przecigania. Na pocztku zachcamy do przejrzenia kodu zawartego na listingu 31.32.
Listing 31.32. Kod rdowy niestandardowego widoku kko
package com.androidbook.drag.drop.demo;

// Jest to plik Dot.java


import
import
import
import
import
import
import
import
import
import

android.content.ClipData;
android.content.Context;
android.content.res.TypedArray;
android.graphics.Canvas;
android.graphics.Color;
android.graphics.Paint;
android.util.AttributeSet;
android.util.Log;
android.view.DragEvent;
android.view.View;

public class Dot extends View


implements View.OnDragListener
{
private static final int DEFAULT_RADIUS = 20;
private static final int DEFAULT_COLOR = Color.WHITE;
private static final int SELECTED_COLOR = Color.MAGENTA;
protected static final String DOTTAG = "DragDot";
private Paint mNormalPaint;
private Paint mDraggingPaint;
private int mColor = DEFAULT_COLOR;
private int mRadius = DEFAULT_RADIUS;
private boolean inDrag;
public Dot(Context context, AttributeSet attrs) {
super(context, attrs);

// Stosujemy ustawienia atrybutw z pliku ukadu graficznego.


// Uwaga: mog one ulega zmianie pod wpywem zmiany konfiguracji,
// na przykad obrotu wywietlacza.
TypedArray myAttrs = context.obtainStyledAttributes(attrs,
R.styleable.Dot);

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

final int numAttrs = myAttrs.getIndexCount();


for (int i = 0; i < numAttrs; i++) {
int attr = myAttrs.getIndex(i);
switch (attr) {
case R.styleable.Dot_radius:
mRadius = myAttrs.getDimensionPixelSize(attr, DEFAULT_RADIUS);
break;
case R.styleable.Dot_color:
mColor = myAttrs.getColor(attr, DEFAULT_COLOR);
break;
}
}
myAttrs.recycle();

// Konfiguruje malowane kolory


mNormalPaint = new Paint();
mNormalPaint.setColor(mColor);
mNormalPaint.setAntiAlias(true);
mDraggingPaint = new Paint();
mDraggingPaint.setColor(SELECTED_COLOR);
mDraggingPaint.setAntiAlias(true);

// Rozpoczyna sekwencj przecigania, wywoan dugim klikniciem na kku.


}

setOnLongClickListener(lcListener);
setOnDragListener(this);

private static View.OnLongClickListener lcListener =


new View.OnLongClickListener() {
private boolean mDragInProgress;
public boolean onLongClick(View v) {
ClipData data =
ClipData.newPlainText("DragData", (String)v.getTag());
mDragInProgress =
v.startDrag(data, new View.DragShadowBuilder(v),
(Object)v, 0);
Log.v((String) v.getTag(),
"rozpoczeto proces przeciagania? " + mDragInProgress);
return true;
};

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
int size = 2*mRadius + getPaddingLeft() + getPaddingRight();
setMeasuredDimension(size, size);
}

// Mechanizm przecigania
public boolean onDrag(View v, DragEvent event) {
String dotTAG = (String) getTag();

// Martwimy si jedynie o zdarzenia przecigania, jeeli to kka s przecigane.

1141

1142

Android 3. Tworzenie aplikacji

if(event.getLocalState() != this) {
Log.v(dotTAG, "To zdarzenie przeciagania nie jest przeznaczone dla nas");
return false;
}
boolean result = true;

// Pobiera wymagane wartoci zdarzenia.


int action = event.getAction();
float x = event.getX();
float y = event.getY();
switch(action) {
case DragEvent.ACTION_DRAG_STARTED:
Log.v(dotTAG, "sekwencja przeciagania rozpoczeta. X: " + x + ", Y: " + y);
inDrag = true; // Wykorzystywana w poniszej metodzie draw() do zmiany koloru
break;
case DragEvent.ACTION_DRAG_LOCATION:
Log.v(dotTAG, "sekwencja przeciagania kontynuowana... Poz.: " + x + ", " + y);
break;
case DragEvent.ACTION_DRAG_ENTERED:
Log.v(dotTAG, "sekwencja przeciagania wkroczyla. Poz.: " + x + ", " + y);
break;
case DragEvent.ACTION_DRAG_EXITED:
Log.v(dotTAG, "sekwencja przeciagania opuscila. Poz.: " + x + ", " + y);
break;
case DragEvent.ACTION_DROP:
Log.v(dotTAG, "upuszczenie. Poz.: " + x + ", " + y);

// Przekazuje warto false, poniewa nie akceptujemy upuszczenia w rejonie startowym.


result = false;
break;
case DragEvent.ACTION_DRAG_ENDED:
Log.v(dotTAG, "przeciaganie zakonczone. Sukces? " + event.getResult());
inDrag = false; // Przywraca pierwotny kolor kka
break;
default:
Log.v(dotTAG, "inne dzialanie w sekwencji przeciagania: " + action);
result = false;
break;
}
return result;
}

// W tym miejscu rysujemy kko oraz zmieniamy jego kolor,


// jeli jest ono przecigane. Uwaga: zmiana koloru wpywa jedynie na
// waciwe kko, a nie na jego cie.
public void draw(Canvas canvas) {
float cx = this.getWidth()/2 + getLeftPaddingOffset();
float cy = this.getHeight()/2 + getTopPaddingOffset();
Paint paint = mNormalPaint;
if(inDrag)
paint = mDraggingPaint;
canvas.drawCircle(cx, cy, mRadius, paint);
invalidate();
}
}

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1143

Kod klasy Dot w duym stopniu przypomina zawarto klasy DropZone. Wynika to czciowo
z faktu, e rwnie tutaj otrzymujemy zdarzenia przecigania. Konstruktor tej klasy przetwarza
atrybuty definiujce waciwy promie i kolor kek, a nastpnie konfiguruje dwa obiekty nasuchujce, jeden wychwytujcy dugie kliknicia, a drugi zdarzenia przecigania.
Fragment kodu dotyczcy okrelania atrybutw jest szczeglnie interesujcy. Chcemy mc okreli waciwoci kka w pliku ukadu graficznego, jednak niestandardowe atrybuty XML wymagaj odpowiedniej konfiguracji w ktrym miejscu. W naszym przypadku przeprowadzamy
j w pliku attrs.xml, umieszczonym w katalogu /res/values. Definiujemy w tym pliku obiekt
nadawania stylu Dot, a take dwa atrybuty: color i radius. Na listingu 31.33 zostaa umieszczona zawarto pliku attrs.xml.
Listing 31.33. Plik attrs.xml definiujcy nowe atrybuty dla klasy Dot
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/values/attrs.xml -->


<resources>
<declare-styleable name="Dot">
<attr name="color" format="color" />
<attr name="radius" format="dimension" />
</declare-styleable>
</resources>

Jak wida, nazw obiektu mona uzyska ze znacznika. Nazwy (i typy) atrybutw s umieszczone w znacznikach podrzdnych. Poprzez okrelenie atrybutw w pliku attrs.xml moemy
wykorzysta je w pliku ukadu graficznego, a take w kodzie Java. W tym drugim przypadku przeszukujemy wszystkie rozpoznawalne atrybuty, a po znalezieniu atrybutw color lub radius
pobieramy ich warto i przypisujemy do kka. Jest to cakiem wygodny sposb definiowania
wygldu obiektw za pomoc atrybutw XML.
Podczas rysowania obiektu posuymy si dwoma kolorami. Jeden kolor przypiszemy do kka
znajdujcego si w stanie spoczynku. W momencie przecigania, kiedy chcemy w jaki sposb
wyrni kko, zmieniamy jego barw na karmazynow.
Obiekt nasuchujcy dugich klikni suy do okrelania pocztku sekwencji przecigania. Jedynym sposobem rozpoczcia procesu przecigania jest kliknicie kka i przytrzymanie go.
Uaktywnienie obiektu nasuchujcego dugie kliknicia spowoduje utworzenie nowego obiektu
ClipData za pomoc cigu znakw i znacznika kka. Znacznik ten jest nazw kka zdefiniowan w pliku ukadu graficznego. Istnieje kilka innych metod wprowadzenia danych do
obiektu ClipData, dlatego te zalecamy zapoznanie si z dokumentacj tego obiektu.
Nastpna metoda jest kluczowa dla naszej aplikacji startDrag(). To wanie tutaj Android
przejmie kontrol i rozpocznie sekwencj przecigania. Zwrmy uwag, e argumentem jest
uprzednio utworzony obiekt ClipData, nastpnie cie przeciganego obiektu, lokalny stan
obiektu, a na kocu warto zero.
Cie przeciganego obiektu jest obrazem wywietlanym w trakcie trwania sekwencji przecigania. W naszym przypadku zostaje on doczony na etapie przecigania, podczas gdy oryginalne
kko pozostaje na swoim miejscu. Domylnym zachowaniem klasy DragShadowBuilder jest
utworzenie cienia przypominajcego oryginalny obiekt, wic wystarczy, e j wywoamy i przekaemy do widoku. Moemy tutaj popuci wodze fantazji i utworzy dowolny widok cienia,
jeli jednak przesonimy t klas, bdziemy musieli jeszcze zaimplementowa kilka dodatkowych klas.

1144

Android 3. Tworzenie aplikacji

Metoda onMeasure() przekazuje Androidowi informacje o wymiarach wykorzystywanego niestandardowego widoku. Musimy poda systemowi te dane, aby widok ten pasowa do innych
elementw widocznych na ekranie.
Znajdziemy tu take metod zwrotn onDrag(). Jak ju wspomnielimy, kady dostosowany
obiekt nasuchujcy moe odbiera zdarzenia przecigania. Wszystkie te obiekty odbieraj na
przykad dziaania ACTION_DRAG_STARTED oraz ACTION_DRAG_ENDED. Gdy wic pojawi si takie
zdarzenie, musimy ostronie wykorzystywa zawarte w nim informacje. Poniewa w naszej aplikacji umiecilimy dwa kka, musimy uwaa, eby kada czynno bya przeprowadzana na
tym waciwym.
Gdy obydwa kka otrzymuj dziaanie ACTION_DRAG_STARTED, tylko jedno z nich powinno
zmieni kolor na karmazynowy. Aby okreli, ktre kko jest waciwe, naley porwna
przekazane obiekty lokalnego stanu z wasnym obiektem lokalnego stanu. Jeeli przyjrzymy si
fragmentowi, w ktrym definiowalimy obiekt lokalnego stanu, zauwaymy, e przekazalimy
do niego biecy widok. Zatem po otrzymaniu obiektu lokalnego stanu moemy go porwna
z naszym biecym stanem, aby okreli, czy znajduje si w widoku rozpoczynajcym sekwencj przecigania.
Jeeli obiekt znajduje si w innym widoku, w oknie LogCat zostanie wywietlony komunikat,
e sekwencja przecigania nie jest przeznaczona dla tego widoku. Nastpi rwnie przekazanie
wartoci false, ktra informuje system, e zdarzenie nie zostanie tu obsuone.
Jeeli zdarzenie przecigania trafi do waciwego widoku, naley pobra z tego zdarzenia pewne
wartoci i najczciej jedynie wywietli odpowiedni komunikat w oknie LogCat. Pierwszym
wyjtkiem jest dziaanie ACTION_DRAG_STARTED. Jeeli dziaanie to jest przeznaczone dla danego widoku, to znaczy, e kko jest czci sekwencji przecigania. Wprowadzamy wic
warto logiczn w zmiennej inDrag, dziki ktrej metoda draw() bdzie moga pniej wywietli kko w innym kolorze. Kolor jest zmieniony jedynie do czasu pojawienia si dziaania ACTION_DRAG_ENDED, po czym zostaje przywrcona pierwotna barwa kka.
Jeeli zostanie wywoane dziaanie ACTION_DROP wobec kka, to znaczy, e uytkownik prbuje
upuci to kko na inne kko, by moe w jego pocztkowym miejscu. Nie powinno mie to
adnych konsekwencji, wic w tym przypadku naley przekaza po prostu warto false.
Na koniec metoda draw() niestandardowego widoku definiuje rodek naszego okrgu (w naszym przypadku przeciganego kka), a nastpnie rysuje go za pomoc odpowiedniego
koloru. Dziki metodzie invalidate() system zostaje poinformowany o modyfikacji widoku
oraz koniecznoci ponownego narysowania interfejsu uytkownika. Za pomoc tej metody gwarantujemy bardzo szybkie zaktualizowanie interfejsu uytkownika o nowe elementy.
Na listingu 31.34 prezentujemy plik manifest tej aplikacji.
Listing 31.34. Plik AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik AndroidManifest.xml -->


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidbook.drag.drop.demo"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="11" />
<application android:icon="@drawable/icon"

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1145

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

Na listingu 31.35 pokazalimy zawarto pliku strings.xml, w ktrym umiecilimy cigi znakw
wykorzystywane przez plik manifest.
Listing 31.35. Plik strings.xml
<?xml version="1.0" encoding="utf-8"?>

<!-- Jest to plik res/values/strings.xml -->


<resources>
<string name="app_name">Fragmenty uywane w funkcji przecigania</string>
</resources>

W ten sposb uzyskalimy ju wszystkie pliki i informacje wymagane do skompilowania i wdroenia naszej przykadowej aplikacji wykorzystujcej funkcj przecigania.

Testowanie przykadowej aplikacji


wykorzystujcej funkcj przecigania
Poniej przedstawiamy przykadow zawarto okna LogCat, w ktrym s wywietlane informacje pojawiajce si po wczeniu naszej przykadowej aplikacji. Zwrmy uwag, e do
identyfikacji komunikatw dotyczcych niebieskiego kka wykorzystujemy znacznik
Niebieskie kolko, do okrelenia biaego kka stosujemy nazw Biale kolko, a zdarzenia
dotyczce strefy upuszczania s definiowane za pomoc nazwy DropTarget.
Biale kolko:
Niebieskie kolko:
Biale kolko:
DropTarget:
DropTarget:
DropTarget:
DropTarget:
DropTarget:
DropTarget:
DropTarget:
DropTarget:
DropTarget:
ViewRoot:
Biale kolko:
Niebieskie kolko:
DropTarget:

rozpoczeto proces przeciagania? true


To zdarzenie przeciagania nie jest przeznaczone
sekwencja przeciagania rozpoczeta. X: 53.0, Y:
Sekwencja przeciagania rozpoczeta w dropTarget
Sekwencja przeciagania wkroczyla do dropTarget
Sekwencja przeciagania kontynuuje w dropTarget:
Sekwencja przeciagania kontynuuje w dropTarget:
Sekwencja przeciagania kontynuuje w dropTarget:
Sekwencja przeciagania kontynuuje w dropTarget:
Sekwencja przeciagania kontynuuje w dropTarget:
Upuszczenie obiektu w dropTarget
Elementem danych jest Biale kolko
Reporting drop result: true
przeciaganie zakonczone. Sukces? true
To zdarzenie przeciagania nie jest przeznaczone
Sekwencja przeciagania zakonczona w dropTarget

dla nas
206.0

29.0,
48.0,
45.0,
41.0,
40.0,

36.0
39.0
39.0
39.0
39.0

dla nas

1146

Android 3. Tworzenie aplikacji

W tym konkretnym przypadku sekwencja przecigania zostaa rozpoczta przez biae kko. Po
uruchomieniu caej procedury przez dugie kliknicie otrzymujemy wiadomo rozpoczeto proces przeciagania?.
Zwrmy uwag, e w trzech nastpnych linijkach pojawia si informacja o odebraniu dziaania
ACTION_DRAG_STARTED. Okazuje si, e wywoanie metody zwrotnej nie byo przeznaczone dla
niebieskiego kka. Jego adresatem nie by rwnie obszar dropTarget.
Zauwamy nastpnie, e kolejne komunikaty opisuj zachowanie sekwencji przecigania w obszarze dropTarget, poczwszy od dziaania ACTION_DRAG_ENTERED. Oznacza to, e kko zostao
przecignite nad obszar zielonego kwadratu. Wsprzdne X i Y s mierzone wzgldem lewego
grnego rogu tego widoku. Zatem pierwszy wpis sekwencji przecigania przeprowadzanej
w zielonym obszarze posiada wsprzdne (x, y) = (29, 36), a upuszczenie nastpio w punkcie
(40, 39). Jak wida, widok docelowy potrafi wydoby nazw znacznika definiujcego biae kko
i umieci j w oknie LogCat.
Ponownie zwracamy uwag, e wszystkie obiekty nasuchujce odebray dziaanie ACTION_
DRAG_ENDED, ale jedynie biae kko pozwolio na wywietlenie wynikw za pomoc metody
getResults().
Zachcamy do eksperymentowania z t przykadow aplikacj. Przecignijmy jedno kko nad
drugie, a nawet na jego oryginaln wersj. Dodajmy jeszcze jedno kko w pliku palette.xml.
Zwrmy uwag, e po wyjciu z zielonego obszaru pojawia si odpowiedni komunikat w oknie
LogCat. Warto take odnotowa fakt, e jeeli upucimy kko gdziekolwiek poza zielonym obszarem, sekwencja przecigania zostanie uznana za nieudan.

Odnoniki

http://developer.android.com/sdk/android-3.0-highlights.html wymieniono tu
nowe funkcje wprowadzone w wersji 3.0 Androida, a take odnotowano
informacje o zmianach wprowadzonych w widetach ekranu startowego.
www.androidbook.com/item/3624 nasze notatki robocze, powstae na etapie
zbierania materiaw do niniejszego rozdziau, dotyczce widetw ekranu startowego
dostpnych w wersji 3.0 Androida. Znajdziemy tu odnoniki do interfejsw API,
fragmentw kodw, otwartych pyta oraz dalszych bada.
www.androidbook.com/item/3299 nasze notatki robocze, powstae na etapie
zbierania materiaw do niniejszego rozdziau, dotyczce widetw ekranu startowego
dostpnych w wersji 2.2 Androida. Znajdziemy tu odnoniki do interfejsw API,
fragmentw kodw, otwartych pyta oraz poprzednich bada.
www.androidbook.com/item/3637 nasze dodatki dotyczce klasy RemoteViews,
zaktualizowane o informacje zwizane z wersj 3.0 Androida, w tym rwnie
przykadowe kody, kolejne pytania, a take zewntrzne i wewntrzne odniesienia.
http://developer.android.com/guide/topics/appwidgets/index.html gwne rdo
informacji na temat poprzednich wersji widetw ekranu startowego. Zwrmy
uwag, e nie ma tu wzmianki na temat zmian wprowadzonych w wersji 3.0 Androida,
zapoznamy si tu jednak z podstawowymi mechanizmami.
http://developer.android.com/reference/android/appwidget/AppWidgetManager.html
dokumentacja bardzo istotnej klasy AppWidgetManager.

Rozdzia 31 Dodatkowe zagadnienia zwizane z wersj 3.0 systemu

1147

http://developer.android.com/reference/android/widget/RemoteViewsService.RemoteV
iewsFactory.html dokumentacja klasy RemoteViewsFactory.
http://developer.android.com/reference/android/widget/RemoteViews.html dokumentacja
klasy RemoteViews.
http://developer.android.com/reference/android/widget/RemoteViewsService.html
dokumentacja usugi RemoteViewsService.
ftp://ftp.helion.pl/przyklady/and3ta.zip cze, dziki ktremu moemy pobra
projekty utworzone na potrzeby niniejszej ksiki. Nazwy katalogw zawierajcych
te projekty to ProAndroid3_R31_WidetyLista oraz
ProAndroid3_R31_TestPrzecigania.

Podsumowanie
W rozdziale tym omwilimy dwa istotne usprawnienia wprowadzone w wersji 3.0 Androida:
widety ekranu startowego oparte na listach oraz interfejs przecigania.
Najpierw dokadnie przeanalizowalimy modyfikacje wprowadzone w widetach ekranu startowego. Pokazalimy, w jaki sposb wczytywa i zapenia zdalne widoki zawierajce listy poprzez
wykorzystanie usugi zdalnych widokw oraz klasy fabrykujcej. Dowiedzielimy si take, jak
moemy konfigurowa zdarzenia onClick oraz manipulowa sam klas AppWidgetProvider
w celu otrzymywania tych zdarze oraz reagowania na nie. Zaprezentowany przez nas materia
pozwala na implementowanie bogatych, uytecznych widetw ekranu startowego.
W podrozdziale powiconym funkcji przecigania zajlimy si wszelkimi dostpnymi nowinkami wykorzystywanymi w czasie sekwencji przecigania, dziki ktrym uytkownik ma do
dyspozycji jeszcze wiksze moliwoci, porwnywalne z osiganymi na komputerach stacjonarnych. Przekazalimy wiele informacji na temat rde, zdarze oraz obiektw docelowych
sekwencji przecigania.
Potencja kryjcy si w tych funkcjach jest ograniczony jedynie wyobrani Czytelnikw.

1148

Android 3. Tworzenie aplikacji

Skorowidz

3GPP, 3rd Generation Partnership Project, 625

A
AAC, Advance Audio Coding, 626
AAPR, Android Asset Packaging Tool, 69
abstrakcja, 472
adapter
ArrayAdapter, 203
ManateeAdapter, 218
ManateeAdapter, 218
niestandardowy, 218
SimpleCursorAdapter, 200
standardowy, 218
ADB, Android Debug Bridge, 83
ADP, Android Developer Phone, 1009
ADT, Android Development Tools, 38, 51
AGC, Automatic Gain Control, 625
agregacja, 970, 1001
akcelerometr, 924
mierzenie ktw, 930
pomiar grawitacji, 927
skadowa ruchu, 929
tryb krajobrazowy, 925
tryb wywietlania, 926
wsppraca z magnetometrami, 931
wsprzdne, 924
aktualizacja lokacji, 569
aktualizowanie danych, 138
aktualizowanie widoku, 742
aktywne foldery, 42
AllContactsLiveFolderCreatorActivity.java,
725
AndroidManifest.xml, 723
BetterCursorWrapper.java, 732
folder kontaktw, 721
lista, 720

MyContactsProvider.java, 726
MyCursor.java, 731
rejestrowanie identyfikatora URI, 730
testowanie, 733
tworzenie folderu, 722
aktywno, 39, 59, 428
ACTION_DIAL, 159
animacji widoku, 537
DetailsActivity, 1053
klienta nadawczego, 457
konfiguratora widetu, 739
ListActivity, 209, 210
LocalSearchEnabledActivity, 794, 795
MainActivity, 375
MultiViewTestHarness, 669
NotesList, 72
OpenGL20MultiViewTestHarness, 705
paska dziaania, 1096
klasa bazowa, 1085
pliki projektu, 1081
paska dziaania wywietlajcego zakadki,
1081
paska zakadek, 1088
PreferenceActivity, 304, 312
przechowujca pasek dziaania, 1098, 1099
RegularActivity, 778
SearchActivity, 791, 792, 795
TestHandlersDriverActivity, 446
TestOpenGLMainDriver, 670
ViewAnimationActivity, 537
wyczajca wyszukiwanie, 786
wyszukiwania
kod rdowy, 804
wyszukiwania globalnego, 770
wywietlajca pasek zakadek, 1092
wywoania wyszukiwania, 808

1150 Skorowidz
aktywno
wywoujca klas AsyncTask, 355
z paskiem zakadek, 1080
z rozwinit list, 1098
aktywny folder, live folder, 717
alarm powtarzalny, 504
anulowanie, 506
kompilowanie kodu, 506
alert odlegociowy, 578
aliasy kolumn w strukturach danych
kontaktu, 1000
AMDDA, Android Market Developer
Distribution Agreement, 1006
AMR, Adaptive Multi-Rate, 626
analiza baz danych, 121
analiza wbudowanych dostawcw treci, 120
Android Debug Bridge, 121
Android Developer Phone, 1009
Android Market, 319, 1005, 1022
alternatywy dla serwisu, 1023
katalog Moje zamwienia, 1022
Android SDK, 32, 56
android.app, 44
android.bluetooth, 44
android.content, 44
android.content.pm, 44
android.content.res, 44
android.database, 45
android.database.sqlite, 45
android.gesture, 45
android.graphics, 45
android.graphics.drawable, 45
android.graphics.drawable.shapes, 45
android.hardware, 45
android.location, 45
android.media, 45
android.net, 45
android.net.wifi, 45
android.opengl, 46
android.os, 46
android.preference, 46
android.provider, 46
android.sax, 46
android.speech, 46
android.speech.tts, 46
android.telephony, 46

android.telephony.cdma, 46
android.telephony.gsm, 46
android.text, 46
android.text.method, 47
android.text.style, 47
android.utils, 47
android.view, 47
android.view.animation, 47
android.view.inputmethod, 47
android.webkit, 47
android.widget, 47
animacja, 517
klatek kluczowych, 524
poklatkowa, 518
dodawanie animacji do aktywnoci, 520
lista klatek, 521
planowanie, 518
rodowisko testowe, 519, 522
tworzenie aktywnoci, 519
ukad graficzny, 519
rotacyjna, 524, 531
skali, 524
definiowanie w pliku XML, 528
translacyjna, 524, 531
typu alfa, 524, 530
ukadu graficznego, 523
kod aktywnoci, 527
rodowisko testowe, 525
tworzenie aktywnoci, 525
widok ListView, 525
w osi X oraz w wymiarze alfa, 1077
widoku, 533
aktywno, 534
dodawanie animacji, 536
kod aktywnoci, 535
kod rdowy aktywnoci, 537
metody preTranslate i postTranslate, 538
ukad graficzny, 534
widoku ListView, 540
animator niestandardowy, 1076
animowane przejcia, tweening, 42
animowanie obiektw, 691
animowanie trjkta, 676
animowanie widoku ListView, 528
ANR, Application Not Responding, 351, 427
API Google Maps, 52

Skorowidz

aplikacja
adb, 123
Android Debug Bridge, 121
Android Hierarchy, 57
BDayWidget, 747
Browser, 38
Contacts, 38
Downloads, 367
Gestures Builder, 898, 901
HelloAndroidApp, 65
Home, 38
Hierarchy Viewer, 242
J2EE, 68
keytool, 318, 319
Kontakty, 958
Lista czujnikw, 909
kod Java, 909
ukad graficzny, 909
wyniki, 911
MapViewDemo, 549, 889
Monitor akcelerometru, 918
Monitor czujnika owietlenia, 911
NfcDemo, 950
Notepad, 69
analiza kodu, 71
tworzenie, 70
uruchomienie, 69
wczytywanie, 70
obsugujca mapy, 548
obsugujca multimedia, 606
Package Browser, 409
Phone, 38
pocztowa, 593
przedstawiajca map wiata, 157
przegldarki stron, 157
rejestrujca, 631
SDK Manager, 54
SipDemo, 599
suca do przecigania obiektw, 876
suca do tumacze, 397
gwny plik aplikacji, 399
plik AIDL usugi tumacza, 399
plik AndroidManifest.xml, 403
plik usugi tumaczenia, 402
plik z cigami znakw, 399
plik z funkcj tumaczenia, 402
plik z tablicami, 399

1151

ukad graficzny, 398


StreetView, 890
Terminal, 56
TouchDemo1, 863
gwna aktywno, 866
interfejs uytkownika, 864
kod Java, 865
komunikaty, 867
ukad graficzny, 863
ukazujca zastosowanie fragmentw, 1034
umoliwiajca poczenie telefoniczne, 157
Ustawienia, 954
Ustawienia wyszukiwania, 775
wykrywajca gesty, 902
wywietlajca klawiatur, 157
zawierajca szczegowe mapy, 157
aplikacje Androida, 48
architektura REST, 119
argument
@avdname, 122
CHECK_VOICE_DATA_PASS, 845
fill_parent, 182
metody startSearch(), 839
minSdkVersion, 182
ARM, Advanced RISC Machine, 39
artefakty usugi i klienta, 395
atrybut android
gravity, 231
layout_gravity, 231
layout_span, 234
padding, 235
permission, 334
prompt, 215
readPermission, 334
text, 181
writePermission, 334
atrybut
FadeOffset, 901
id, 97
quantity, 102
queryAfterZeroResults, 822
REFERER, 396
ringtoneType, 309
searchSuggestIntentData, 822
showSilent, 309
suggestActionMsgColumn, 837

1152 Skorowidz
atrybuty
uprawnienia, 329
wza data, 160
AVD, Android Virtual Device, 51, 60, 65

B
badanie aktywnoci zawierajcej pasek
dziaania, 1098, 1102
badanie kontaktw zbiorczych, 980
elementy menu, 986
funkcje uytkowe, 980
gwna aktywno, 986
klasa bazowa, 981
kolumny kursora, 988
pliki projektu, 980
testowanie kontaktw, 982
badanie nieprzetworzonych kontaktw, 989
opcje menu, 990
pliki projektu, 989
pola kursora, 993
przegldanie danych, 994
testowanie danych kontaktu, 995
testowanie kontaktw, 990
baza danych contacts.db, 133
baza danych SQLite, 119, 125
bezpieczestwo
certyfikat cyfrowy, 318
definiowanie uprawnie, 334
klucz prywatny, 318
klucz publiczny, 318
kontrolowanie dostpu do zasobw, 334
magazyn kluczy, 318
podpisywanie aplikacji, 318
przekazywanie uprawnie, 333
biblioteka
FreeType, 37, 38
jzyka C, 37
OpenGL, 37
Skia, 38
Surface Manager, 38
WebKit, 37, 38
biblioteka gestw, 900
biblioteka OpenGL, 649
animowanie trjktw, 676
dodawanie trjktw, 675
glClear, 658
glColor, 659

glDrawElements, 656
glFrustum, 661
gluLookAt, 660
glVertexPointer, 654
glViewport, 663
koncepcja widzenia, 660
ksztaty, 678
objto widzenia, 661
OpenGLTestHarnessActivity, 667
podstawy rysowania, 654
RegularPolygon, 681
rozmiar ekranu, 663
rysowanie prostokta, 679
rzutowanie ortograficzne, 662
rzutowanie perspektywiczne, 662
tekstury, 678, 694
trjwymiarowa scena, 659
tworzenie trjktw, 676
wielobok foremny, 681
biblioteka OpenGL ES, 649
proste ksztaty, 654
biblioteka OpenGL ES 2.0, 704
aktywno sterujca, 706
dostp do jednostek cieniowania, 711
funkcje, 706
jednostki cieniujce, 708
jednostki cieniujce wierzchoki, 708
jednostki cieniujce fragmenty, 708
renderowanie, 707
trjkt, 711
rda, 715
biblioteki Apache HTTP, 48
biblioteki Java Androida, 32
biblioteki multimediw, 37
blokada przechodzenia w stan zatrzymania,
477, 486
bd kompilacji we wtyczce ADT, 111
bdy w aplikacji, 81
BSD, Berkeley Software Distribution, 37
bufor koloru, 658
bufor nio, 655, 658, 666

C
CA, certificate authority, 318
CAD, Computer Aided Design, 650
cal, 235
Centrum Oprogramowania Linuksa, 53

Skorowidz

certyfikat
Android Debug, 321
cyfrowy, 318
debugowania, 320
okres wanoci, 324
PKI, 412
podpisany samoistnie, 318
produktu, 1018
programistyczny, 320
testowy, 546
wasny, 318
X.509, 318
CHOICE_MODE_MULTIPLE, 211
CHOICE_MODE_NONE, 211
CHOICE_MODE_SINGLE, 211
cig znakw, 115
ciar, weight, 228
com.google.android.maps, 47
CRUB, Create, Read, Update, Delete, 77
cyfrowy podpis aplikacji, 322
cykl ycia
aktywnoci, 446, 447
aplikacji, 78
dostawcy treci, 448
odbiorcw komunikatw, 448
usugi, 448
czas ycia procesu, 446
czas ycia skadnika, 446
czstotliwo prbkowania, 628
czujnik
aktualizacje odczytw, 915
interpretacja danych, 921
pobieranie zdarze, 911
pozostawianie wczonego ekranu, 920
rodzaje czujnikw, 908
rozwizywanie problemw, 915
termometry, 922
trudne problemy, 914
wybr wartoci odwieania, 913
wykrywanie, 908
czujnik NCF, Patrz NFC, 907
czujniki
akcelerometry, Patrz akcelerometr, 924
cinienia, 923
grawitacji, 939
informacje o pooeniu, 932

1153

komunikacji bliskiego pola, 939


magnetometry, 930
orientacji w przestrzeni, 931
owietlenia, 921
przypieszenia liniowego, 939
wektora obrotu, 939
zblieniowe, 922
yroskopy, 923

D
Dalvik VM, 33, 36
dane, 159, 167
dane dodatkowe, 161
dane nieprzetworzonego kontaktu, 994
dane typu Bundle, 568
DATABASE_MODE_2LINES, 801
DATABASE_MODE_QUERIES, 801
DDMS, Dalvik Debug Monitor Server, 82
Debug, 82
debugger debug_layout_activity.xml, 977
debugowanie, 293, 1090
debugowanie aplikacji, 82
definicja
nieregularnej tabeli, 232
tabeli danych kontaktw, 968
tabeli kontaktw zbiorczych, 969
tabeli nieprzetworzonych kontaktw, 966
definiowanie
dziaania w dostawcy widetu, 1118
kontrolki GridView, 214
prostokta o zaokrglonych rogach, 109
wielokrotnoci, 102
zasobw typu Color, 105
zasobw typu color-drawable, 108
zasobw typu dimension, 106
zasobw typu string, 103
deklarowanie niestandardowego
uprawnienia, 328
deklinacja magnetyczna, 938
Developer Account, 1006
dip, 235
dugie kliknicie, 261
dugoterminowa usuga, 473
dugoterminowy odbiorca komunikatw, 476

1154 Skorowidz
dodawanie
animacji do widoku, 536
elementw do menu, 249
funkcji dotyku, 889
identyfikatorw do kontrolek, 181
kontaktu, 998
plikw do magazynu multimediw, 644
plikw dwikowych do silnika TTS, 854
pliku do dostawcy treci, 137
trjktw za pomoc indeksw, 675
typw do obiektu, 161
znacznikw, 553
dokumentacja pakietu SDK, 412
dostawca
BookProvider, 141
dodawanie ksiki, 150
usuwanie ksiki, 150
wywietlanie listy ksiek, 151
zliczanie ksiek, 151
Contacts, 131
GPS_PROVIDER, 577
lokalizacji, 567
MediaStore, 131
NETWORK_PROVIDER, 577
PASSIVE_PROVIDER, 577
pasywny, 567
pooenia, 568
propozycji, 798
aktywno wyszukiwania, 803
gwna aktywno, 810
manifest zawierajcy definicj, 802
pliki implementacji, 799
pole wyszukiwania lokalnego, 811
propozycja lokalna, 812
propozycje globalne, 813
uytkowanie, 810
wczanie, 812
wyniki wyszukiwania lokalnego, 811
zadania, 800
propozycji niestandardowy
badanie metadanych, 821
identyfikatory URI, 818
implementacja, 813
implementacja aktywnoci
wyszukiwania, 824
klasa SearchActivity, 825
korzystanie, 831

metadane wyszukiwania, 820


plik manifest, 830
przekazywanie kwerendy, 820
wyniki, 832
wywoanie SearchActivity, 827
zakoczenie aktywnoci, 829
SearchRecentSuggestionsProvider, 806
sieciowy, 567
SimpleSuggestionProvider, 804
metadane wyszukiwania, 807
SuggestUrlProvider, 814
implementacja klasy, 815
kod rdowy, 815
pliki projektu, 814
systemu GPS, 567
treci, 41, 59, 119
analiza wbudowanych dostawcw, 120
architektura, 126
baza danych, 139
dodawanie pliku, 137
identyfikator URI, 128
implementacja, 139
odczyt danych, 130
rejestrowanie dostawcy, 149
widetu, widget provider, 736
dostpno, 704
dp, 235
dynamiczne dane, 180
dziaanie, 159, 167
ACTION_DOWN, 862
ACTION_DRAG_ENDED, 1144
ACTION_DRAG_STARTED, 1144
ACTION_GET_CONTENT, 171
ACTION_MOVE, 862, 894
ACTION_NDEF_DISCOVERED, 942
ACTION_PICK, 169
ACTION_POINTER_DOWN, 894
ACTION_POINTER_UP, 894
ACTION_SEARCH, 828, 833
ACTION_TAG_DISCOVERED, 942
ACTION_TECH_DISCOVERED, 942
ACTION_UP, 862
ACTION_VIEW, 828, 833
CALL, 159
Intent.ACTION_CALL, 160
Intent.ACTION_DIAL, 160
VIEW, 825

Skorowidz

E
Eclipse, 38, 51
EditText, 184
edycja kontaktu, 960
ekran dotykowy, 861
ekran preferencji, 297
ekran startowy, 1110
eksploracja kont, 972
funkcje testujce, 973
gwna aktywno sterujca, 977
plik manifest, 978
plik menu, 973
spis plikw, 978
element Activity.managedQuery, 74
element Dot, 876
element nasuchujcy, 192
element SimpleCursorAdapter, 74
element TextView, 94
elementy skadowe Androida, 68
AndroidManifest.xml, 68
anim, 68
assets, 68
drawable, 68
layout, 68
menu, 68
raw, 68
res, 68
src, 68
values, 68
xml, 68
emulacja karty NFC, 949
emulacja rozruchu urzdzenia, 64
emulator Androida, 38
EULA, 1019
EXTRA_EMAIL., 162
EXTRA_SUBJECT, 162

F
filtr intencji, 160, 166, 943, 945
flaga
CONTEXT_IGNORE_SECURITY, 414
CONTEXT_INCLUDE_CODE, 414
CONTEXT_RESTRICTED oznacza, 414
Menu.FLAG_APPEND_TO_GROUP, 266
krawdzi, 869

1155

nietrwaoci, 485
Service.START_REDELIVER, 485
Service.START_STICKY, 485
trwaoci, 485
folder
android\AVD, 65, 66
assets, 69
drawable, 897
raw, 69
res, 69, 74
res/layout-land, 240
res/layout-port, 240
res/layout-square, 240
ukadu graficznego res/layout, 240
values, 74
foldery wiadomoci SMS, 592, 593
format dwiku 3GPP, 625
format VCF, 963
fragment, 40, 1026
aplikacja ukazujca cykl ycia, 1033
cykl ycia, 1028
formy komunikowania, 1073
komunikacja pomidzy fragmentami, 1074
metoda onAttach(), 1030
metoda onCreate(), 1030
metoda onCreateView(), 1031
metoda onDestroyView(), 1033
metoda onDetach(), 1033
metoda onInflate(), 1030
metoda onPause(), 1032
metoda onResume(), 1032
metoda onStart(), 1032
metoda onStop(), 1032
metoda setRetainInstance(), 1033
metoda zwrotna onActivityCreated(), 1032
przechowujcy dialog, 1055
przejcia i animacje, 1044
stosowanie, 1027
stosowanie odniesie, 1046
struktura, 1027
transakcja fragmentu, 1042
trwao, 1054
tworzenie hierarchii widokw, 1031
wycofanie okna dialogowego, 1059
wywietlanie okna dialogowego, 1054
wywietlanie nowej aktywnoci, 1051

1156 Skorowidz
fragment wywietlajcy okna dialogowe, 1060
gwna aktywno, 1061
gwny ukad graficzny, 1072
interfejs uytkownika, 1061
kod Java, 1064
pliki projektu, 1060
ukad graficzny, 1064
funkcja
debugowania, 84
getACursor(), 982
getDistinctPendingIntent(), 510
getEventsFromAnXMLFile, 111
getExternalStorageDirectory(), 607
getExtras, 161
Install New Software, 56
java.util.AttributeSet, 110
listContacts(), 988
ProGuard, 1018
przecigania, 876, 1131
gwna aktywno, 1136
interfejs aplikacji, 1134
lista plikw, 1133
podstawowe informacje, 1131
przykadowa aplikacja, 1133
testowanie aplikacji, 1145
tworzenie ukadu graficznego, 1133
ukad graficzny, 1134
putExtras, 161
setData(), 435
StrictMode, 85, 89
StrictMode w trybie debugowania, 86
testThread(), 437
TTS, 843
type-to-search, 797
wielodotykowoci, 879, 887
funkcje
daty, 760
kalendarza, 494
uytkowe, 980

G
geokodowanie w Androidzie, 559, 563
geokodowanie w oddzielnym wtku, 563
geolokalizacja, 559

gest ciskania, 891


kod Java, 892, 896
ScaleGestureDetector, 896
ukad graficzny, 896
gest zaznaczenia, 899
gesty, 891
aplikacja, 902
biblioteka, 900
kod Java aplikacji, 902
magazynie, 900
rejestrowanie, 905
struktura klas, 900
ukad graficzny aplikacji, 902
gesty niestandardowe, 898
gesty waciwe, 900, 904
glClear
zerowanie koloru, 658
glDrawElements
koncepcja pasa, 657
koncepcja wachlarza, 657
ksztat, 657
kwadrat, 657
linia, 657
pas linii, 657
ptle linii, 657
punkt, 657
trjkty, 657
glFrustum
bliski punkt, 662
daleki punkt, 662
objtoc widzenia, 662
ostrosup widzenia, 662
promie, 662
gluLookAt
orientacja aparatu, 661
punkt oczny, 660
punkt spogldania, 661
punkt widoku, 661
wektor gry, 661
wsprzdne wiata, 660
glVertexPointer
brya okalajca, 655
interfejs API, 656
objto okalajca, 655
wsprzdne wiata, 655

Skorowidz

glViewport
wziernik, 663
gbia w obrazie dwuwymiarowym, 539
Google Checkout, 1022
Google Maps, 546
Google Nexus One, 1009
Google Nexus S. Android Developer Phone, 1009
GPS, Global System Positioning, 545
GPU, Graphical Processing Unit, 649
grafika trjwymiarowa, 652
grawitacja, gravity, 228
grupa opcji, 193
grupy widokw, 39
GSM, Global System for Mobile
Communication, 38

H
handheld, 32
Hashimi Sayed Y., 23
hasa storepass i keypass, 321
hiperbola, 532
historia Androida, 34

I
IANA, Internet Assigned Numbers
Authority, 129
IDE, Integrated Development Environment, 38
identyfikator
Contacts.People.CONTENT_URI, 131
predefiniowany, 526
public static, 74
treci nieprzetworzonego kontaktu, 997
ukadu graficznego, 527
URI, 76, 128, 130, 332
klasa UriMatcher, 147
rozpoznawanie kolekcji, 140
rozpoznawanie URI, 147
wprowadzanie klauzuli WHERE, 134
wstawianie rekordw, 136
URI propozycji, 815
URI wyszukiwania, 815
URI wyszukiwania kontaktw, 989
zasobw, 97, 114
t1_1_en_port, 115
t1_enport, 114
t2, 115

1157

testport_port, 114
teststring_all, 114, 116
zasobw R.menu.moje_menu, 269
IDialogProtocol, 288
IETF, Internet Engineering Task Force, 597
ikony akustyczne, 855
implementacja
aktywnoci wyszukiwania, 824
bazowych klas aktywnoci, 1082
dugoterminowej usugi, 483, 486
dostawcw treci, 139
dostawcy treci BookProvider, 141
dostawcy widetu, 751
interfejsu AIDL, 379
interfejsu AnimationListener, 541
interfejsu Parcelable, 386
klasy AlarmManagerService, 514
klasy Renderer, 664
klasy ReportStatusHandler, 439
klasy WorkerThreadRunnable, 438
ksztatu RegularPolygon, 682, 683
metody delete, 147
metody getType(), 819
metody insert, 147
metody query, 146
metody update, 147
modeli widetw, 753
niestandardowego dostawcy propozycji, 814
obiektu nasuchujcego zdarze, 1087
owietlonego zielonego pokoju, 478
usugi lokalnej, 373
plik AndroidManifest.xml, 374
plik main.xml, 373
plik MainActivity.java, 373
usugi StockQuoteService2, 388
wtku roboczego, 438
instalacja narzdzi ADT, 57
instalowanie aktualizacji, 324
instancja paska dziaania, 1089
instancja widetu, 738
instrukcja awk, 123
instrukcja create, 125
instrukcja find, 123
instrukcja grep, 123
instrukcja insert, 137

1158 Skorowidz
intencja, intent, 40, 59, 155, 156
atrybuty dodatkowe, 161
dane, 159, 167
dziaanie, 159, 167
jawna nazwa klasy, 159
kategorie intencji, 168
mapa typu klucz warto, 159
MediaStore.ACTION_IMAGE_CAPTURE,
644
niejawna, 159
oczekujca, pending intent, 172
PendingIntent, 495
przydzielanie do ich skadnikw, 166
putExtras, 161
schemat danych, 167
cieka danych, 168
typ danych, 167
uprawnienia do danych, 168
VIEW, 160
interakcja aktywnoci wyszukiwania
lokalnego, 792
interakcja aktywnoci z przyciskiem
wyszukiwania, 777
interfejs
ActionBar, 1079
AJAX Language, 396
API, 43, 653
API Google Maps, 396
API multimediw, 619
aplikacji obsugujcej multimedia, 606
AttributeSet, 1030
ContactsContract, 967
createPackageContext(), 413
DDMS, 571
DialogInterface, 276
DialogInterface.OnClickListener, 1072
EGL, 651
glDrawElements, 654
GLSurfaceView.Renderer, 663
Google AJAX Language API, 396
Google Directions, 569
graficzny
pojemnik, kontener, 176
ukad graficzny, 176
widok, widet, kontrolka, 176
IDialogFinishedCallBack, 291

IDialogProtocol, 286, 288


JDBC, 127
kontaktw, 972
modelu widetu, 753
nasuchujcy AnimationListener, 541
obiektu nasuchujcego, 1063
OnCheckedChangeListener, 193
OnDialogDoneListener, 1063, 1067
onItemClickListener, 208
onLoadCompleteListener, 616
pomidzy OpenGL ES a Androidem, 663
Projection, 888
rejestratora wideo, 632
Renderer, 664
ResolveInfo, 164
SensorEventListener, 913
Shape, 682
Tumacz Google, 395, 397
UI, 175, Patrz interfejs uytkownika
UI Androida, 39
UI Emulator Control, 591
UI kontrolek DatePicker i TimePicker, 198
UI odtwarzacza plikw wideo, 620
UI widoku RingtonePreference, 308
uytkownika, 39
konstruowanie interfejsu za pomoc
kodu oraz jzyka XML, 180
programowanie za pomoc kodu, 177
projektowanie interfejsu, 176
tworzenie interfejsu w pliku XML, 179
uytkownika silnika TTS, 845
interpolator, 531, 532
interpolator liniowy, linear interpolator, 1075
interpolator accelerate_interpolator, 532
IPC, Inter-Process Communication, 37

J
J2EE, Java 2 Platform Enterprise Edition, 58
Java Development Kit, 52
Java Standard Edition, 32
jawna nazwa klasy, 159
jawne przywoanie aktywnoci, 447
JCP, Java Community Process, 651
JDK, Java Development Kit, 52, 53, 56
JDK, Java SE Development Kit, 51
jzyk AIDL, 368

Skorowidz

jzyk HTML, 91
jzyk XML, 69
jzyk XUL, 39
JIT, Just-In-Time Compiler, 36
JRE, Java Runtime Environment, 52
JSON, JavaScript Object Notation, 343
JVM, Java Virtual Machine, 32

K
kamera
zmiana ustawie, 672
karta SD, 601, 602
foldery, 605
rdo plikw audio, 612
katalog
/res, 203
/res/layout/, 95
/res/menu, 269
/res/values, 1143
/res/xml, 297
/tools, 242
assets, 111
DCIM, 604
drawable-port, 240
drawable-square, 240
frameworks/base, 49
HOME, 53
layout, 69
layout-en, 114
Moje zamwienia, 1022
par klucz warto, 136
rawable-land, 240
tools, 55, 122
tools/android, 54
katalogi alternatywnych zasobw, 113
katalogi na karcie SD
DIRECTORY_ALARMS, 605
DIRECTORY_DCIM, 605
DIRECTORY_DOWNLOADS, 605
DIRECTORY_MOVIES, 605
DIRECTORY_MUSIC, 605
DIRECTORY_NOTIFICATIONS, 605
DIRECTORY_PICTURES, 605
DIRECTORY_PODCASTS, 605
DIRECTORY_RINGTONES, 605

1159

katalogi zasobw, 112


kategoria LAUNCHER, 304, 1053
kategorie aktywnoci
CATEGORY_ALTERNATIVE, 165
CATEGORY_BROWSABLE, 165
CATEGORY_DEFAULT, 165
CATEGORY_EMBED, 165
CATEGORY_GADGET, 165
CATEGORY_HOME, 165
CATEGORY_LAUNCHER, 165
CATEGORY_PREFERENCE, 165
CATEGORY_SELECTED_ALTERNATIVE,
165
CATEGORY_TAB, 165
CATEGORY_TEST, 165
kategorie intencji, 163, 168
klasa
AbstractRenderer, 664
abstrakcyjna, 472
AccountsFunctionTester, 974
ActionBar, 1079, 1080, 1084
Activity, 214, 262
AdapterView, 200, 201
AlarmManager, 748
AlarmManagerService, 514
AlertBuilder, 1060
AlertDialogFragment, 1071
AllContactsLiveFolderCreatorActivity, 725
ALongRunningReceiver, 475
android.app.AlertDialog.Builder, 274
android.content.ContentProvider, 139
android.content.ContentResolver, 136
android.content.ContentValues, 136
android.graphics.Matrix, 537
android.location.Geocoder, 559
android.media.MediaPlayer, 601
android.os.Bundle, 161
android.os.Debug, 83
android.preference.ListPreference
atrybuty, 298
konfigurowanie projektu, 298
android.preference.PreferenceActivity, 297
android.util.Log, 81
android.view.LayoutInflater, 278
android.view.Menu, 247
android.view.SubMenu, 247

1160 Skorowidz
klasa
android.view.ViewGroup, 175
android.widget.ImageButton, 188
android.widget.ListAdapter, 213
android.widget.RadioButton, 193
AndroidHttpClient, 349, 350
AnimatedSimpleTriangleRenderer, 677
Animation, 541
AnimationDrawable, 521
app_name, 93
Application, 81
AppWidgetProvider, 742, 1107
ArrayAdapter, 202, 203, 208
AssetManager, 112
AsyncPlayer, 606, 617
AsyncTask, 351, 354, 357
AudioFormat, 629
AudioRecord, 626
AudioTrack, 618
BaseActionBarActivity, 1097, 1101
BaseAdapter, 218
BaseTester, 974
BDayWidgetModel, 752
BetterCursorWrapper, 732
BitmapFactory, 196
BookProviderMetaData, 139
BookTableMetaData, 140
Builder, 85
CamcorderProfile, 639
Camera, 42, 539
CameraProfile, 639
ClientCustPermMainActivity, 331
ComponentName, 162
ContactsContract.AggregationExceptions,
1001
ContentProvider, 141
ContentProviderOperation, 1003
ContentResolver, 137
ContentValues, 136, 137, 138
Context, 453
CustomHttpClient, 347
DatabaseHelper, 77
DebugActivity, 975, 1083
DeferWorkHandler, 436
DetailsFragment, 1037, 1050, 1052
DialogFragment, 1055, 1056, 1060

Dot, 1143
DownloadImageTask, 358
DownloadManager, 362
Drawable, 521
ES20AbstractRenderer, 711
Fragment, 1025, 1027
FragmentManager, 1045
FragmentTransaction, 1042, 1044, 1076
GenericManagedAlertDialog, 291
GenericPromptDialog, 292
Geocoder, 560, 565
GeomagneticField, 938
GeoPoint, 559
GestureDetector, 895
GestureOverlayView, 904
GLSurfaceView, 664, 667, 704
GridViewActivity, 214
GS20SimpleTriangleRenderer, 714
HelpDialogFragment, 1068
kod Java, 1069
ukad graficzny, 1069
HttpActivity, 358
HttpClient, 338
HttpGet, 338
HttpURLConnection, 349
ImageView, 196
Inflater, 221
Intent, 40, 162
IntentService, 469, 492
kod rdowy, 470
rozszerzanie na odbiorc
komunikatw, 472
ItemizedOverlay, 554, 557
JetPlayer, 606, 617
LayoutInflater, 278, 1031
LightedGreenRoom, 476
LinearLayout, 228
ListActivity, 593
ListFragment, 1047
ListPreference, 296
LocationManager, 567, 580, 583
MainActivity, 867, 1035, 1061
ManagedActivityDialog, 287, 288
ManagedDialogsActivity, 287, 290
ManateeAdapter, 221
MapActivity, 548, 550

Skorowidz

MapController, 551
MapView, 548, 888
Matrix, 539
MediaPlayer, 601, 610, 618
MediaRecorder, 622
MediaScanner, 647
MediaStore, 640, 646
MotionEvent, 861, 869, 873, 880
MyContactsProvider, 726
MyCursor, 731
MyFragment, 1029
MyLocationOverlay, 574
dostosowywanie, 577
zastosowanie, 574
MySMSMonitor, 590
NotePadProvider, 76
NotesList, 73
ObjectAnimator, 1075, 1139
OnGestureListener, 895
Overlay, 557
PaintDrawable,, 108
podstawowa
Activity, 427
BroadcastReceiver, 427
ContentProvider, 427
Service, 427
PolygonRenderer, 691
PrivActivity, 327
PromptDialogFragment, 1064, 1067
PromptListener, 279
RadioGroup, 194
RadioGroup.OnCheckedChangeListener,
193
RegularPolygon, 681
animowanie obiektw, 691
calcArrays, 689
Constructor, 689
getAngleArrays, 689
getIndexBuffer, 689
getVertexBuffer, 689
getXMultiplierArray, 689
getYMultiplierArray, 689
renderowanie kwadratu, 689
rysowanie koa, 693
RemoteViews, 741
RemoteViewsFactory, 1114
Renderer, 664

1161

ReportStatusHandler, 439
ScaleGestureDetector, 896
SearchActivity, 825, 828
SearchRecentSuggestionsProvider, 813, 840
SendAlarmOnceTester, 499
SensorManager, 908, 930, 936
Shakespeare, 1042
SimpleCursorAdapter, 200, 201, 202, 208
SimpleRectangleRenderer, 690
SimpleSuggestionProvider, 799
kod rdowy, 800
tryby bazodanowe, 801
SimpleTriangleRenderer, 665
SimpleTriangleRenderer2, 675, 676
SipAudioCall, 599
SipSession, 599
SmsManager, 588
SoundPool, 612, 617
maksymalna liczba prbek, 616
odtwarzanie dwiku, 613
parametr SRC_QUALITY, 616
strumie audio, 616
Spannable, 224
Spinner, 215
SpinnerAdapter, 1095
SQLiteQueryBuilder, 146, 149
static final ints, 93
StrictModeWrapper, 88
System.out.println, 81
TextToSpeech, 841
TexturedSquareRenderer, 698
ThreadGroup, 372
TitlesFragment, 1047
Toast, 293
debugowanie, 293
UriMatcher, 146, 147
Utils, 455
VelocityTracker, 874
ViewAnimation, 536
ViewGroup, 1043
widget.RadioGroup, 193
WorkerThreadRunnable, 438
XmlPullParser, 110
klasy
aktywnoci sterujcej, 975
android.view.View, 175

1162 Skorowidz
klasy
bazowe aktywnoci
ActionBar, 1084
AndroidManifest.xml, 1093
showAsAction, 1092
SpinnerAdapter, 1095
suce do obsugi widokw zdalnych, 1108
sterownika
DeferWorkHandler.java, 441
ReportStatusHandler.java, 441
Utils.java, 441
WorkerThreadRunnable.java, 441
zwizane z menu, 248
klatka kluczowa, 517
klauzula select, 141
klauzula WHERE, 134
klient usugi IStockQuoteService, 382
kliknicie, 187
klucz
API AJAX, 397
API MAP, 1018
interfejsu API mapy, 546
map-api, 321
prywatny, 318, 411
publiczny, 318, 411
KML, Keyhole Markup Language, 572
kod niestandardowy, 169
kod odbiorcy, 454
kod odbiorcy komunikatw, 465
kod usugi zdalnego widoku, 1128
kod rdowy Androida, 48
kod rdowy Git, 48
kody przyciskw dziaania, 835
kolumny kursora encji kontaktu, 997
kolumny kursora propozycji, 823
definiowanie, 824
Komatineni Satya, 23
komentarze w kodzie., 140
kompilacja, 501, 503, 506, 508, 511, 513
kompilator JIT, 36
kompilowanie jednostek cieniujcych, 709
kompilowanie kodu, 449
kompilowanie zasobw, 98
komunikat
ANR, 432, 450
testowy, 456
wysyanie komunikatu, 454
konfiguracja

alarmu, 493
kanaw, 629
strumieni audio, 855
uruchomieniowa, 63
urzdzenia pionowa, portrait, 240
urzdzenia pozioma, landscape, 240
urzdzenia tryb kwadratowy, square, 240
konfigurator widetw, 739
konfigurowanie
alertu odlegociowego, 579
ciaru, 230
klasy RemoteViewsFactory, 1114
konstruktora alertw, 278
menu za pomoc kodu, 255
obiektw nasuchujcych, 278
odbiorcy dla alarmu, 495
paska dziaania, 1096
powtarzalnego alarmu, 503
procedury obsugi kliknicia, 188
usugi RemoteViewsService, 1112
widokw obiektw, 1140
wirtualnego urzdzenia AVD, 64
zasad ThreadPolicy, 85
zasad VmPolicy funkcji, 86
zdarze onClick, 1117
rda danych, 611
konsola programisty, 1009
konstruktor alertw, 278
konstruktor klasy RemoteViewsFactory, 1114
kontakty
agregacja, 1001
analiza, 964
dodawanie kontaktu, 998
aktywno sterujca, 999
edytowanie niestandardowych danych, 961
edytowanie szczegw, 960
eksportowanie, 962
interfejs, 972
nieprzetworzone, 965, Patrz take badanie
nieprzetworzonych kontaktw
odczytywanie kontaktw zbiorczych, 971
pobieranie bazy kontaktw, 965
standardowe typy danych, 964
synchronizacja, 1002
tabela danych, 967
testowanie danych
aktywno sterujca, 996

Skorowidz

testowanie nieprzetworzonych kontaktw


aktywno sterujca, 992
typy danych, 964
umieszczanie zdjcia, 962
widok contact_entities_view, 971
widok view_contacts, 971
wywietlanie, 958
wywietlanie szczegw, 959
zbiorcze, Patrz take badanie kontaktw
zbiorczych
kontekst wyszukiwania, 838
kontener, container, 176
konto
dodawanie konta Google, 956
lista kont, 958
logowanie, 957
odczytywanie zawartoci, 958
tworzenie konta Google, 956
ustawienia kont i synchronizacji, 955
wstawianie kontaktw, 957
zarejestrowane na urzdzeniu, 957
konto programisty, Developer Account, 1006
kontrakt klasy RemoteViewsFactory, 1114
kontroler ukadu graficznego, 529
kontrolka, control, 176
AdapterView, 204
AnalogClock, 199
Button, 187
CheckBox, 190
Chronometer, 223
com.google.android.maps.MapView, 200
DatePicker, 197
DigitalClock, 199
EditText, 233
Gallery, 217
GridControl, 213
GridView, 213, 221
definiowanie kontrolki w pliku XML,
214
ImageButton, 188
ImageView, 195
ListView, 205
dodawanie elementw, 205
dodawanie innych kontrolek, 208
przyjmowanie danych, 207
reakcja na kliknicie, 206
wywietlanie wartoci, 205

1163

listy, 205
MapView, 200, 549
ProgressBar, 223
RadioButton, 192
RatingBar, 223
ScrollBar, 223
Spinner, 215
TableRow, 233
TimePicker, 197
ToggleButton, 190
kontrolki
Androida, 182
Androida 2.2, 467
AutoCompleteTextView, 185
daty i czasu, 197
AnalogClock, 199
DatePicker, 197
DigitalClock, 199
TimePicker, 197
ImageView, 196
przyciskw, 187
Button, 187
CheckBox, 190
ImageButton, 188
RadioButton, 192
ToggleButton, 190
kontrolki tekstu, 183
AutoCompleteTextView, 185
EditText, 184
MultiAutoCompleteTextView, 186
TextView, 183
TextView, 181
widoku, 741
koprocesor graficzny, 649
kreator New Android Project, 598
kryteria dopasowania, 166
kSOAP2, 343
kursor propozycji, 822
kursor systemu Android, 133
kwalifikatory konfiguracji, 113
kwalifikatory zasobw, 241
Gsto pikseli na ekranie, 241
Jzyk i region, 241
Klawiatura, 241
Orientacja ekranu, 241
Rodzaj tekstowych danych wejciowych, 241
Rozmiary ekranu, 241

1164 Skorowidz
kwalifikatory zasobw
Sterowanie przy braku klawiatury
dotykowej, 241
Szersze/wysze ekrany, 241
Typ ekranu dotykowego, 241
Wersja rodowiska SDK, 241
kwerenda wyszukiwania, 833

L
layout, Patrz ukad graficzny
lista
aktywnych folderw, 720
animowanych klatek, 521
baz danych znajduje si w katalogu, 123
kodw przyciskw dziaania, 835
kolumn, 823
kompletacyjna, 273
pakietw, 408
preferencji, 297, 302
propozycji, 768
technologii, 946
ukadw graficznych, widetw i widokw,
1106
widetw, 1130
widetw ekranu startowego, 738
ListActivity
odczytywanie danych, 212
listingi, 449, 489
lo, 56
localhost, 56
lokalizacja certyfikatu testowego, 547
lupa, 243
LVL, License Verification Library, 1017

M
M3G, 652
macierz jednostkowa, 542
macierz transformacji, 539, 541
MacLean Dave, 23
magazyn gestw, 900
magazyn kluczy, 318, 319, 320
magazyn MediaStore, 645
magazyn multimediw, 644
ManagedActivityDialog
klasa DialogRegistry, 289

mapa typu klucz warto, 159


mapy, 546
aplikacja, 548
nakadanie wasnej warstwy, 553
obsuga za pomoc dotyku, 888
usuga Google Maps, 546
mapy projekcji, 149
MD5, 546
mechanizm przechowywania i dostpu, 120
Pliki, 120
Preferencje, 120
Sie, 120
SQLite, 120
mechanizm refleksji, 87
mechanizm type-to-search, 797
meneder
FrameLayout, 237
LinearLayout, 228
RelativeLayout, 235
TableLayout, 231
meneder alarmw, 493, 449
aktywno do testowania ustawie, 500
alarm powtarzalny, 503
czas uruchomienia alarmu, 494
jednorazowe wysanie alarmu, 498
konfiguracja, 493
konfigurowanie odbiorcy, 495
pierwszestwo intencji, 512
praca z wieloma alarmami, 508
projekt, 497
testowanie scenariuszy, 501
trwao, 515
twierdzenia, 515
ukad graficzny, 502
ustawiania, 496
uzyskanie dostpu, 494
wersje alternatywne, 503
meneder LinearLayout, 607
meneder powiadomie, 463
meneder telefonii, 594
meneder ukadu graficznego, 227
FrameLayout, 228
LinearLayout, 228
RelativeLayout, 228
TableLayout, 228

Skorowidz

menu, 247
aktywnoci, widoki i menu kontekstowe, 262
aktywno, 254
aktywnoci SearchInvokerActivity, 790
alternatywne, 264
dodatkowe znaczniki, 271
dodawanie elementw, 255, 256
dodawanie podmenu, 260
drugorzdne, 256
dynamiczne, 268
grupy, 250
konfiguracja, 255
Konta i synchronizacja, 954
kontekstowe, 261, 263
modyfikowanie AndroidManifest.xml, 258
odpowied na kliknicie, 251, 257
opcji, 249
rejestrowanie widoku TextView, 263
rozszerzone, 259
standardowe, 255
systemowe, 261
rodowisko testowe, 253
tworzone za pomoc kodu Java, 268
ukad graficzny, 254
w postaci ikon, 259
wybr elementw, 251
wykorzystanie intencji, 252
zapenianie menu, 266
zdefiniowane w plikach XML, 268
zmiana danych, 268
metadane bazy danych, 139
metadane dostawcy widetw, 1128
metadane wyszukiwania, 793, 807
metoda
activity.onCreateContextMenu(), 262
addEarcon(), 855
addPreferencesFromResource(), 297
addSubMenu(), 261
animate(), 523
ArrayAdapter.createFromResource(), 217
callService(), 393
cancel(), 515
captureImage(), 644
commit(), 314
context.getSharedPreferences(), 757
createFromResource(), 203

1165

DefaultHttpClient(), 347
detectAll(), 87
detectDiskReads(), 87
dismiss(), 1058, 1068
Display.getOrientation(), 926
Display.getRotation(), 926
distanceTo(), 569
doInBackground(), 353, 354, 356
doSpeak(), 845
doUpdate(), 937
draw(), 877
enableDefaults(), 87
enqueue(), 364
fabrykujca, 1029
findLocation(), 565
findPreference(), 312
fromRawResource(), 903
GestureLibraries.fromFile(), 903
GET, 340
getAccuracy(), 569
getAction(), 867, 886
getActionIndex(), 887
getActionMasked(), 887
getCheckedItemIds(), 213
getCheckedItemPositions(), 211
getCheckedRadioButtonId(), 195
getCount(), 150, 221
getDefaultEngine(), 856
getEdgeFlags(), 869
getExternalStoragePublicDirectory(), 607
getHttpClient(), 347, 348
getHttpContent(), 349
getInterpolation(), 532
getIntrinsicHeight(), 557
getIntrinsicWidth(), 557
getItemId(), 251
getLastNonConfigurationInstance(), 357
getMinBufferSize(), 629
getOrientation(), 931, 937
getPathSegments(), 146
getPointerCount(), 880
getPrefsToSave(), 754
getRotationMatrix(), 931
getSharedPreferences(), 303, 314
getString(), 303
getTag(), 1063

1166 Skorowidz
metoda
getText(), 208
getType(), 146, 819
getViewTypeCount(), 221
glDrawElements, 657
glVertexPointer, 654
handleMessage, 436
hasAccuracy(), 569
HTTP GET, 405
HTTP POST, 405
initCamera(), 634
initRecorder(), 637
insert(), 78, 204
invalidate(), 1144
isCancelled(), 356
isChecked(), 192
isEnabled(), 222
isLocationDisplayed(), 552, 575
isRouteDisplayed(), 552
LayoutInflater(), 278
LayoutInflater.from(), 278
ListView, 74
Log.d, 111
makeText(), 294
MenuBuilder.addIntentOptions, 267
mIndexBuffer, 658
MotionEvent.getAction(), 887
moveToFirst(), 133
newInstance(), 350
notifyDataSetChanged(), 204
nstartDrag(), 1143
obtain(), 873
obtainMessage(), 435
onAccuracyChanged(), 913, 914
onActivityCreated(), 1032
onActivityResult(), 568
onCheckedChanged(), 195
onClick(), 192, 873
onCreate(), 73, 79, 88, 206, 805, 903
onCreateContextMenu(), 264
onCreateDialog(), 284
onCreateOptionsMenu, 249, 265
onCreateView(), 1031, 1056, 1068
onDestroy(), 80, 446
onEnabled(), 753
onInflate(), 1030

onInit(), 856
onItemClick(), 208
onListItemClick(), 75
onMeasure(), 1144
onNewIntent(), 803, 805, 811
testowanie, 806
onOptionsItemSelected, 251, 252, 270
onPause, 446
onPostExecute(), 353
onPreExecute(), 353, 361
onPrepareDialog(), 284
onPrepareOptionsMenu, 268
onProgressUpdate(), 353
onProviderDisabled(), 573
onReceive(), 591, 753, 1120
onRestart(), 80
onResume(), 79, 80
onRetainNonConfigurationInstance(), 357
onSaveInstanceState(), 1047
onSearchRequested(), 789, 838
onSensorChanged(), 913, 919, 929
onStart(), 79
onStartCommand, 485
onStop(), 80, 446
onTouchEvent(), 862, 863, 877, 893
onUpdate(), 743, 744, 1111
penaltyDeath(), 85
PendingIntent.getActivity(), 172, 173
permitDiskReads(), 87
playSilence(), 856, 858
populate(), 557
postInvalidate(), 576
postTranslate, 538
PreferenceManager.getDefaultShared
Preferences(this), 303
preTranslate, 538, 542
publishProgress(), 353
putFragment(), 1047
queueSound(), 616
recognize(), 905
recycle(), 873
registerListener(), 913
requestLocationUpdates(), 571, 573
respondToMenuItem(), 433
rotate, 539
scanFile(), 646

Skorowidz

scheduleDistinctIntents(), 511
scheduleSameIntentMultipleTimes(), 510
sendAlarmOnce(), 499
sendBroadcast(), 173, 453, 454
sendDataMessage(), 588
sendMessage(), 435
sendMessageDelayed(), 435
sendMultipartTextMessage(), 589
sendSmsMessage(), 587
setAdapter(), 214
setBounds(), 557
setChecked(), 193
setContentView(), 211, 214
setData(), 435
setDataSource(), 611, 612, 621
setEdgeFlags(), 869
setEngineByPackageName(), 856
setEntries(), 313
setGroupVisible, 250
setImageResource(), 189
setLatestEventInfo(), 466
setListAdapter(), 214
setMeasureAllChildren(), 239
setOnCheckedChangeListener(), 193
setOneShot(), 522
setOnTouchListener(), 888
setOnUtteranceCompletedListener(), 846
setOptionText(), 303
setPendingIntentTemplate(), 1119
setRepeating(), 505
setRetainInstance(), 1033
setTargetFragment(), 1074
showAllRawContacts(), 993
sleep(), 432, 439
sort(), 204
speak(), 846
startActivity(), 169, 1074
startActivity(intent), 173
startSearch(), 789, 839
appSearchData, 789
globalSearch, 789
initialQuery, 789
selectInitialQuery, 789
startService(), 173, 372, 429, 469
stop(), 441
stopSelf, 485

1167

surfaceCreated(), 635
testAccounts(), 975
toUri(), 1118
translate, 539
tv.getText(), 225
uiCallback.sendEmptyMessage(0), 565
Utils.logThreadSignature(), 436, 439
VelocityTracker.obtain(), 874
zoomToSpan(), 558
zwrotna getCount(), 1115
zwrotna getItemId(), 1116
zwrotna getLoadingView(), 1116
zwrotna getViewAt(), 1115
zwrotna getViewTypeCount(), 1116
zwrotna hasStableIds(), 1117
zwrotna onAttach(), 1030
zwrotna onCreate(), 1030, 1115
zwrotna onCreateView(), 1031
zwrotna onDataSetChanged(), 1117
zwrotna onDestroy(), 1033, 1115
zwrotna onDestroyView(), 1033
zwrotna onDetach(), 1033
zwrotna onDrag()., 1144
zwrotna onInflate(), 1030
zwrotna onPause(), 1032
zwrotna onResume(), 1032
zwrotna onStop(), 1032
metody cyklu ycia aktywnoci, 79
metody gwne
delete, 139
getType, 139
insert, 139
query, 139
update, 139
metody pobierajce, 1046
metody uzyskiwania aktualizacji pooenia, 574
MFC, Microsoft Foundation Classes, 39
mikrostopnie, 562
mikrotesla, T, 930
milimetr, 235
MIME, Multipurpose Internet Mail
Extensions, 127
modu HttpClient, 338, 343, 405
monitorowanie zdarze animacji, 541
motyw, 227
MultiAutoCompleteTextView, 186

1168 Skorowidz

N
nagrywanie i odtwarzanie dwiku, 622
nakadanie tekstury, 683
nakadka ItemizedOverlay, 558
narzdzia
AAPR, 69
ADT, 38, 56
DDMS, 1019
Developer Tools, 57
do usuwania bdw, 81
wtkowania, 430
narzdzie
Abstract Window Toolkit, 32
adb, 323
AVD Manager, 83
edytora manifestu, 327
Export Unsigned Application Package, 321
Hierarchy Viewer, 175, 242
ekran urzdze, 243
tryb Pixel Perfect, 244
ukad graficzny, 242
jarsigner, 318
keytool, 321, 547
LogCat, 81, 82, 85, 867
wpisy, 870
Swing, 32
widet Toast, 571
zipalign, 322
nazwa skadnika, component name, 266
NDK, Native Development Kit, 940
NFC, Near Field Communication, 35, 939
aktywacja, 940
emulacja karty, 949
odbieranie terminali, 942
odczytywanie terminali, 946
P2P, 949
testowanie technologii, 950
trasowanie terminali, 941
tryby dziaania, 940
wybr filtru intencji, 943
numer portu 5554, 588

O
obiekt
addressContainer, 178
ApplicationInfo, 86

AudioRecord, 629
AudioRecord., 628
Builder, 87
ClipData, 1143
Criteria, 568
criteriaIntent, 266
cursor, 130, 772
DatePicker, 273
DragEvent, 1132
ACTION_DRAG_ENDED, 1132
ACTION_DRAG_ENTERED, 1132
ACTION_DRAG_EXITED, 1132
ACTION_DRAG_LOCATION, 1132
ACTION_DRAG_STARTED, 1132
ACTION_DROP, 1132

Drawable, 109
falseLayoutBottom, 873
FileDescriptor, 611
flight_sort_options_values, 301
Geocoder, 891
GridView, 214
HttpClient, 349
HttpGet, 349
HttpParams, 349
HttpPost, 349
includeInGlobalSearch, 812
IntentService, 485
klasy Spinner, 216
Location, 568
ManateeAdapter, 221
map, 141
MediaController, 621
Menu, 261
Message, 435
MotionEvent, 862, 873, 877, 880, 891
MotionView, 862
nasuchujcy, 251, 897, 1087
nasuchujcy OnInitListener, 845
NdefMessage, 949
NdefRecord, 948
nio, 666
OnCheckedChangeListener, 192
Parcelable, 391
PendingIntent, 515
PreferenceCategory, 311
RadioButton, 195
RemoteViews, 466
SensorEvent, 913

Skorowidz

SensorListener, 915
SharedPreferences, 314
SipProfile, 599
Spinner, 204, 216, 1095
SubMenu, 260
TextToSpeech, 845
TimePicker, 273
Toast, 294
trueBtnTop, 867
VelocityTracker, 875
ViewHolder, 221
wakelock, 478
XmlPullParser, 110
XmlResourceParser, 110
obiekty nasuchujce, 278
obsuga map, 888
obsuga wyjtkw, 343
obszar dropTarget, 1146
ochrona zasobw i funkcji urzdzenia, 325
odbieranie terminali NFC, 942
odbiorca BroadcastReceiver, 580, 858, 919
odbiorca komunikatw, broadcast receiver,
453, 462, 467, 579
alert odlegociowy, 579
definicja odbiorcy, 460
dugoterminowy, 467, 474
opnienia czasowe, 461
powiadomienia, 463
powielanie, 460
odbiorca pozaprocesowy, 462
odbiorca przebywajcy we wasnym
procesie, 462
odbiorca TestReceiver, 495
odbiorca treci, 461
odczytywanie danych w ListActivity, 210
odlego pomidzy dwoma obiektami, 569
odpowied na wybr elementw, 251
odpowied na zdarzenia onClick, 1120
odpowied na zdarzenie onDrag w strefie
upuszczania, 1137
odstpy, 235
odtwarzanie ciszy, 856
odtwarzanie ikony akustycznej, 856
odtwarzanie multimediw, 606
AsyncPlayer, 617
AudioTrack, 618

1169

interfejs API multimediw, 619


JetPlayer, 617
kod aplikacji, 607
MediaPlayer, 618
setDataSource, 611
SoundPool, 612
ukad graficzny, 607
ograniczenia klasy AnimationDrawable, 522
OHA, 48
okna alertw, 274
projektowanie, 274
okna dialogowe
alertw, 273
asynchroniczne, 273
informujce, 273
listy kompletacyjne, 273
modalne, 281
obiekt DatePicker, 273
obiekt TimePicker, 273
okna niezarzdzane, 283
okna zarzdzane
DialogRegistry, 289
GenericManagedAlertDialog, 291
GenericPromptDialog, 292
IDialogProtocol, 288
ManagedActivityDialog, 288
ManagedDialogsActivity, 289, 290
protok, 283
struktura, 286
upraszczanie protokou, 285
pojedynczego wyboru, 273
synchroniczne, 273
wielokrotnego wyboru, 273
zachty, 276
kod, 280
obiekt nasuchujcy, 279
plik XML ukadu graficznego, 277
projektowanie, 276
przeprojektowanie okna, 282
tworzenie i wywietlenie, 279
okno
Devices, 242
Emulator Control, 576
File Explorer, 604
Hierarchy Viewer, 243
kreatora New Android Project, 61

1170 Skorowidz
okno
Launch Options, 84
LogCat, 82, 582, 629, 885, 988
Package explorer, 1018
terminalu, 52
opcja
Add External JARs, 404
Add note, 73
Android SDK and AVD Manager, 323
Android Tools, 321
Build Path/Configure Build Path., 404
Create project from existing sample, 899
Debug As/Android Application, 82
Debugowanie USB, 82
Export Signed Application Package, 323
Launch from snapshot, 84
QUEUE_ADD, 845
QUEUE_FLUSH, 845
Upload Application, 1018
Wipe user data, 84
Open Graphics Library, 650
OpenCORE PacketVideo, 37
OpenGL ES, 39
OpenGL ES 2.0, Patrz biblioteka OpenGL ES 2.0
OpenJDK, 52
operacja
setRotate, 542
setScale, 542
setSkew, 542
setTranslate, 542
operacje na macierzach, 541
oprogramowanie integracyjne, middleware, 337
optymalizacja aplikacji, 322
Oracle/Sun JDK, 53
organizowanie preferencji w kategorie, 310
orientacja w przestrzeni, 936
o gbi, 660
owietlony zielony pokj, 478
implementacja, 478

P
P2P, peer-to-peer, 949
pakiet
.apk, 610
android.app, 1027
android.location, 559

android.media, 601
android.nfc, 948
android.opengl.GLES20., 704
android.provider, 120
android.providers.Contacts, 132
android.view.animation, 525
com.svox.pico, 856
java.nio, 666
map, 545
nio, 652
OpenGL ES, 38
R.java, 92
pakiety, 407
dokumentacja SDK, 412
lista, 408
nazwa, 408
podpisywanie, 409
specyfikacja, 407
usuwanie, 409
pakiety java.*, 47
para kluczy, 318
parametr
childLayout, 202
from, 202
Intent, 169
odwieania czujnika, 913
requestCode, 169
SRC_QUALITY, 616
to, 202
uri, 137
parser jSON, 342
parser SOAP, 342
parser XML, 342
pary typu MIME, 129
pasek dziaania, 1079, 1080
interakcja z menu, 1091
obszar menu, 1081
obszar paska narzdzi, 1081
obszar przycisku ekranu startowego, 1080
obszar tytuu, 1080
obszar zakadek, 1081
tryb wywietlania listy, 1094, 1096
tryby nawigacji, 1089
pasek zakadek
badanie aktywnoci, 1093
pasujca aktywno, 266

Skorowidz

PDU, Protocol Description Unit, 591


perspektywa, 82
perspektywa DDMS, 82, 603
perspektywa Debug, 82
ptla komunikatw, 282
Phillips Dylan, 25
Pico, 43
pierwsza aplikacja, 60
piksel, 235
piksele niezalene od gstoci, 235
piksele niezalene od skali, 235
PKI, Public Key Infrastructure, 411
planowanie bazy danych, 139
plik
.adt, 546
.apk, 318, 407, 601
.dex, 36
.jar, 36
.profile, 53
_has_set_default_values.xml, 304
AbstractRenderer.java, 672
AccountsFunctionTester.java, 974
AIDL usugi tumacza, 399
aktywnoci sterujcej, 442
AlarmIntentPrimacyTester.java, 513
alpha.xml, 530
android.bat, 66
android.jar, 48
AndroidManifest.xml, 58, 60, 68, 72, 213,
258, 304, 323, 374, 419, 422, 442, 445,
456, 459, 462
animacji, 529
arrays.xml, 301
attrs.xml, 1143
BackgroundService.java, 369
BaseTester.java, 498, 974
box1.xml, 1111
CancelRepeatingAlarmTester.java, 508
com.androidbook.services.stockquote
service, 377
commons-lang.tar, 404
commons-lang.zip, 404
ContactData.java, 994
ContactDataFunctionTester.java, 994
contacts.db, 123, 125
CupcakeMaps.ini, 66

1171

debug.store, 320
debug_layout_activity.xml, 977
DebugActivity.java, 975
details.xml, 1041
DownloadImageTask.java, 351
DropBox, 85
DropZone.java, 1137
dropzone.xml, 1135
dwikowy z tekstem, 849
eclipse.exe, 53
gestures, 902
GPX, 572
HttpActivity.java, 347, 355, 357
HttpGetDemo.java, 338
IReportBack.java, 497, 973, 1082
JAR, 407
KML, 572
KMZ, 572
layout/lib_main.xml, 418
List_Layout.xml, 529
main.xml, 241, 302, 331, 354, 373, 421,
442, 456, 672
main_layout.xml, 114
main_menu.xml, 422, 442, 456, 669, 672
MainActivity.java, 363, 373, 867
mainmenu.xml, 302
manifest, 445
manifest klienta, 332
menu, 445
menu/lib_main_menu.xml, 418
MP3, 606
MultiViewTestHarnessActivity.java, 672
myappraw.apk, 321
MyLocationDemoActivity.java, 574
NoSearchActivity, 786
NotesList.java, 88
outfile.apk, 323
Palette.java, 1136
palette.xml, 1135
Person.aidl, 388
planets.xml, 216
ProAndroid3_R13_ProceduryObsugi.zip,
441
proguard.cfg, 1018
prompt_layout.xml, 277
R.java, 74, 97, 112, 115, 424

1172 Skorowidz
plik
RawContact.java, 990
release.keystore, 319
scale.xml, 524
sdcard.img, 602
SDK Manager, 54
searchable.xml, 793
SimpleSuggestionProvider.java, 800
SimpleTriangleRenderer.java, 672
StandaloneReceiver.java, 462
strings.xml, 92, 181, 215, 302, 829, 1145
TestAppActivity.java, 420
TestBCRActivity.java, 456
TestContactsDriverActivity.java, 977
TestHandlersDriverActivity.java, 442
TestLibActivity.java, 417
TestListWidgetProvider.java, 1122
TestOpenGLMainDriverActivity.java, 672
TestReceiver.java, 456
TestRemoteViewsFactory.java, 1126
TestRemoteViewsService.java, 1128
TranslateService.java, 402
ukadu graficznego, 94, 444, 458
ukadu graficznego main.xml, 94
Utils.java, 455, 456, 462, 980
web.xml, 68
wewntrzny, 137
XML, 98, 109, 268
XML animacji rotacyjnej, 531
XML preferencji, 302
XML zawierajcy definicje menu, 269
zasobw menu, 458
zawierajcy informacje o widecie, 1129
zdalnego ukadu graficznego, 1109
ZIP, 449, 489
pliki
aplikacji sucej do tumacze, 397
do testowania usug
implementacji dostawcy propozycji, 799
nieskompresowane, 98
parcelowane, 388
programu wywietlajcego list kont, 972
projektu menedera alarmw, 497
projektu TestBCR, 489
projektu testowego, 456
projektu z odbiorc komunikatw, 490

ukadw graficznych, 114


wideo, 619
widetu urodzinowego, 746
podmenu, 260
podpis cyfrowy, 410
podpisywanie aplikacji, 318
podpisywanie plikw, 411
podpisywanie pliku .apk, 321
podpisywanie pliku .jar, 318
podrcznik referencyjny rodowiska
OpenGL ES, 653
pojemnik, 176
pojemnik ListView, 201, 221
pole NFC, 939
pole QSB, 771, 793
polecenia powoki, 124
polecenie
#ls /system/bin, 123
adb, 83
adb devices, 121, 122
adb help, 122
android list avd, 122
find, 123
ipconfig, 56
ls, 123
ls -l, 123
ls /data/data, 123
sqlite> .tables, 125
sqlite>.exit, 125
poczenia rwnorzdne (P2P) NFC, 949
pooenie, 568, 932
pooenie biece, 576
pooenie geograficzne, 559
aktualizacja danych, 569
LocationManager, 571
MyLocationOverlay, 574
pomiar grawitacji, 927
pomoc techniczna, 1012
POP, Post Office Protocol, 954
port#, 83
powiadomienia, 464
kod odbiorcy komunikatw, 465
powoka ash, 123
powoka dostawcy widetu, 743
predefiniowanie identyfikatora, 97

Skorowidz

preferencje, 295
ekran preferencji, 297
kategorie, 311
klasa ListPreference, 296
lista preferencji, 297
magazyn danych, 306
programowe zarzdzanie, 312
przechowywanie stanu aktywnoci, 313
widok CheckBoxPreference, 305
widok EditTextPreference, 307
widok RingtonePreference, 308
zagniedenie elementw
PreferenceScreen, 310
zapisywanie stanu, 313
preferencje aplikacji, 295
preferencje pola wyboru, 305
preferencje RingtonePreference, 309
prefiks vnd, 129
procedura DeferWorkHandler, 433
procedura obsugi, handler, 431, 432
klasy sterownika, 441
menu, 445
proces w Androidzie
Activity, 427
BroadcastReceiver, 427
ContentProvider, 427
Service, 427
procesy, 407
program SQLite Explorer, 965
program testujcy meneder alarmw, 502
projekt bibliotek, 414, 420, 425
identyfikatory wspdzielonych zasobw, 424
kod aktywnoci, 420
manifest, 419, 422
menu, 418, 422
twierdzenia, 414, 417
ukad graficzny, 418, 421
projekt Provider, 48
protok
odbiorcy komunikatw, 468
SIP, inicjalizacji sesji, 597
SOAP, 127
SSL, 37
zarzdzanych okien dialogowych, 283, 287
przeciganie obiektw, 876
kod Java, 876
ukad graficzny, 876

1173

przekroczenie limitu czasu, 344, 348


przekroczenie limitu czasu gniazda, 343
przekroczenie limitu czasu poczenia, 343
przesonicie kontrolki ListView, 209
przetwarzanie tekstu na mow, 841
ptla przekazywania wyrae, 847
pliki dwikowe, 848
prdko mowy, 842
silnik Pico, 842
ledzenie wyrae, 846
ukad graficzny, 848
ustawienia silnika, 842
usunicie z kolejki tekstu, 845
wyraenie, 846
zapisywanie pliku dwikowego, 850
przycisk
Generate API Key, 547
Inspect Screenshot, 243
Install Selected, 55
Load View Hierarchy, 242
przeczania, 190
Publish, 1021
Screen Capture, 1019
wyszukiwania, 769, 777, 778
przyciski dziaania
definicja, 836
keycode, 836
kolumny, 837
queryActionMsg, 837
suggestActionMsg, 837
suggestActionMsgColumn, 837
punkt, 235

Q
QEMU, 39
QSB, Quick Search Box, 769

R
raporty o bdach aplikacji, 1011
refleksja, 87
reguy przydzielania intencji, 166
rejestracja upowanienia, 126
rejestrator dwiku, 642
rejestrator wideo, 632
aktywno, 632
AndroidManifest.xml, 639

1174 Skorowidz
rejestrator wideo
kod obsugujcy wstrzymywanie, 633
kod przetwarzania, 636
metody zwrotne, 638
rejestrowanie aktualizacji lokacji, 569
rejestrowanie aktywnoci, 156
rejestrowanie dostawcy, 150
rejestrowanie multimediw, 621
analiza procesu rejestracji, 630
AudioRecord, 626
CAMCORDER, 625
MediaRecorder, 622
nieskompresowane dane audio, 630
rejestrowanie rozmowy, 625
VOICE_RECOGNITION, 625
za pomoc intencji, 641
rejestrowanie odbiorcy komunikatw, 456
rejestrowanie widoku dla menu
kontekstowego, 263
rejestry dziennika LogCat, 872
rekord, 136
renderowanie, 707
renderowanie kwadratu, 689
REST, REpresentational State Transfer, 119
RESTful, 41
RFID, Radio Frequency Identification, 939
RISC, Reduced Instruction Set Computer, 39
rodzaje adapterw, 204
ArrayAdapter<T>, 204
CursorAdapter, 204
ResourceCursorAdapter, 204
SimpleAdapter, 204
SimpleCursorAdapter, 204
rodzaje menu, 259
rodzaje zasobw, 99
cigi znakw, 99
kolorowe obiekty rysowane, 100
kolory, 99
obrazy, 100
tablice cigw znakw, 99
wielokrotnoci, 99
wasne pliki XML, 100
wasne, nieskompresowane pliki
dodatkowe, 100
wasne, nieskompresowane zasoby, 100
wymiary, 99

rozszerzanie klasy ContentProvider, 141


RPC, Remote Procedure Call, 368
RTP, Real-time Transport Protocol, 598
RTSP, Real-time Streaming Protocol, 598
rysowanie wielokta, 693
rysowanie wielu figur geometrycznych, 699
rzutowanie obrazu trjwymiarowego, 659
glFrustum, 659
gluLookAt, 659
glViewport, 659

S
schemat danych, 167
SD, Secure Digital, 601
SDK, Software Development Kit, 32
SDP, Session Description Protocol, 598
segment cieki, 135
sekwencja przecigania, 1140
serwis Google Maps, 553
sie
3G, 38
Bluetooth, 38
EDGE, 38
WiFi, 38, 364
silnik Pico, 842
silnik przetwarzania tekstu na mow, 43
silnik renderujcy prostokt, 679
silnik SquareRenderer, 690
silnik TTS, 845, 850, 859
dostpno jzyka, 857
funkcje zaawansowane, 854
metody jzykowe, 857
odtwarzanie ciszy, 856
odtwarzanie ikony akustycznej, 856
SIP, Session Initiation Protocol, 585
skala mapy, 552
skalowanie, 528
sklep, Patrz Android Market
skadnia odniesienia do zasobu, 95
skrt MD5 certyfikatu testowego, 547
skrty dla elementu menu, 272
skrzynka odbiorcza, 592
SMS, Short Messaging Service, 585
foldery, 593
monitorowanie wiadomoci, 589
skrzynka odbiorcza, 592

Skorowidz

wiadomoci przychodzce, 589


wysyanie wiadomoci, 586
SOAP, Simple Object Access Protocol, 119
sp, 235
specyfikacja JSR 239, 652
specyfikacja pakietu, 407
spis dostpnych kontaktw, 979
spis kwalifikatorw konfiguracji, 113
sprzeda aplikacji, 1012
lokalizacja aplikacji, 1014
obsuga rnych rozmiarw ekranu, 1012
ponowne kierowanie do sklepu, 1016
przygotowanie ikony aplikacji, 1015
przygotowanie pliku .apk do wysania, 1018
przygotowanie pliku
AndroidManifest.xml, 1013
testowanie dziaania, 1012
usuga licencyjna, 1017
ustalanie ceny, 1016
SSL, Secure Sockets Layer, 37
staa
CATEGORY_SYSTEM., 261
FILL_PARENT, 182
intent.ACTION_SEARCH, 805
MATCH_PARENT, 182
Menu.CATEGORY_ALTERNATIVE, 266
Menu.CATEGORY_SECONDARY, 248
Menu.CATEGORY_SYSTEM, 248
Notes.CONTENT_URI, 73
stae trybu agregacji, 970
stan uaktywnienia przycisku, 189
stan wcinity przycisku, 189
stan zatrzymania, 476, 477
stany aktywnoci, 80
stany wtku, 441
stos Java, 666
stos drugoplanowy, 1059
stos programowy, 337
stos programowy Android SDK, 33, 37
StrictMode, 84
strona startowa Androida, 719
strona startowa z polem QSB, 769
struktura klas gestw, 900
struktura preferencji, 296
struktura skadnikw, 428
strumienie audio, 855

1175

styl ErrorText, 226


styl ErrorText.Danger, 226
style, 224
dla fragmentw tekstu, 225
nadrzdne, 226
umieszczane dynamiczne, 225
umieszczane w widoku, 226
wykorzystywane w wielu widokach, 225
Sun JDK, 52
superklasa aktywnoci, 172
symbol #, 123
synonim, 141
system GPS, 82
system operacyjny
iPhone OS, 33
Linux, 52
Mac OS X, 52
Mobile Linux, 33
Moblin, 33
Symbian OS, 33
Windows 7, 52
Windows Mobile, 33
Windows Vista, 52
Windows XP, 52
szablon intencji oczekujcej, 1119

cieka danych, 168


rodowisko
Android SDK, 52
chronionej pamici, 78
Dalvik VM, 36
Eclipse, 51
Eclipse IDE for Java Developers, 53
IDE, 48
IDE Eclipse, 51
J2EE, 58, 338
Java ME, 652
JRE, 52
JVM, 32
OpenGL ES 2.0, 39
programowania, 51
projektowe, 89
testowe biblioteki OpenGL, 667
testowe do sprawdzania menu, 254

1176 Skorowidz

T
tabela contact, 1001
tabela wyszukiwania, 968
tabela zawierajca wyjtki agregacji, 1001
tablica cigw znakw, 101
tablica dodanych elementw menu, 267
tablica flight_sort_options, 301
tagi do symulowania podmenu, 271
technologia ARM, 39
technologia M3G, 652
technologia Ndef, 946
technologia NFC, 939
teksturowane koa, 703
teksturowany kwadrat, 699
teksturowany wielobok, 700
tekstury, 694
glActiveTexture, 697
glBindTexture, 697
glGenTextures, 697
glTexCoordPointer, 697
glTexEnv, 697
glTexParameter, 697
GLUtils.texImage2D, 697
proces obsugi, 695
rysowanie, 698
znormalizowane wsprzdne, 694
terminal
ACTION_NDEF_DISCOVERED, 942
ACTION_TAG_DISCOVERED, 942
ACTION_TECH_DISCOVERED, 942
testowanie
animacji typu alfa, 530
danych kontaktu, 995
dugoterminowych usug, 488
dostawcy BookProvider, 150
kontaktw zbiorczych, 982
nieprzetworzonych kontaktw, 990
odbiorcw komunikatw, 489
pierwszestwa intencji, 512
procedur obsugi, 442
technologii NFC, 950
ustawie alarmw, 500
widetu wywietlajcego list, 1130
TextView, 183
ThreadPolicy, 85
transformacja widoku, 542
trasowanie terminali NFC, 941

tryb
agregacji, 970
debugowania, 82
debugowania USB, 82
MODE_PRIVATE, 314
MODE_WORLD_READABLE, 314
MODE_WORLD_WRITEABLE, 314
portretowy, 1052
rozwijalnego menu, 1104
ruchu ulicznego, 552
usuwania bdw, 82
widoku ulic, 552
wyszukiwania, 771
TTS, Text To Speech, 841
tworzenie
aktywnego folderu, 722
aplikacji Notepad, 70
cyfrowego podpisu, 411
dostawcy widetw, 1122
fragmentu wywietlajcego okna
dialogowe, 1055
instancji widetu, 742
intencji oczekujcej, 495
intencji oczekujcej na komunikat, 1118
intencji oczekujcych, 511
interfejsu uytkownika w pliku XML, 179
interfejsu uytkownika za pomoc kodu, 177
kategorii preferencji, 311
klasy fabrykujcej, 1126
klasy SoundPool, 616
komunikatu, 435
konfiguracji uruchomieniowej, 63
konta Google, 956
listy pakietw, 408
menu, 249
menu za pomoc plikw XML, 268
niestandardowych adapterw, 218
niestandardowych animacji, 1075
obiektu nasuchujcego listy nawigacji, 1095
odbiorcy komunikatw, 455
odniesie do kontrolek, 182
odpowiedzi dla elementw menu, 270
odpowiedzi dla menu kontekstowego, 264
okna alertu, 275
okna dialogowego zachty, 277
powiadomie, 465
pl wyboru, 190

Skorowidz

projektu, 449
projektu bibliotek, 417
prostego odbiorcy, 454
tosamoci w sklepie, 1006
trjktw, 676
urzdzenia AVD, 67, 602
wystpienia fragmentu, 1029
typy agregacji, 1001
typy danych, 167
typy MIME, 77, 128, 130, 265
typy treci, 129
application, 129
audio, 129
example, 129
image, 129
message, 129
model, 129
multipart, 129
text, 129
video, 129

U
Ubuntu, 52
ukad graficzny, layout, 94, 95, 113, 176, 227
animacja, 523
dostosowanie do urzdzenia, 239
optymalizacja, 242
tworzenie interfejsu uytkownika, 240
usuwanie bdw, 242
ukad graficzny
aktywnoci LocalSearchEnabledActivity, 794
aktywnoci SearchActivity, 792
aktywnoci SearchInvokerActivity, 789
aktywnoci TestAlarmsDriverActivity, 502
aktywnoci TestOpenGLMainDriver, 671
aktywnoci wyszukiwania, 828
aplikacji MapViewDemo, 549
aplikacji odtwarzajcej multimedia, 607
aplikacji rejestrujcej, 631
aplikacji tumaczcej, 398
dla aplikacji TouchDemo1, 863
do animacji poklatkowej, 519
do wywoywania klasy AsyncTask, 354
FrameLayout
widok ImageView, 239

klasy SearchActivity, 828

1177

LinearLayout, 178, 228


ciar, 228
grawitacja, 228
RelativeLayout
interfejs uytkownika, 237
TableLayout
kontrolka EditText, 234
nieregularna tabela, 233
trueLayoutTop, 871
usugi IStockQuoteService, 384
usugi StockQuoteService2, 389
widetu, 749
zawierajcy widok debugowania, 1090
umowa EULA, 1019
upowanienie, 127
uprawnienia, 325
atrybuty, 329
definiowanie uprawnie, 334
dla funkcji i zasobw, 326
identyfikatorw URI, 332
do danych, 168
niestandardowe, 326, 330
przekazywanie uprawnie, 333
w pliku AndroidManifest.xml, 325
uprawnienie
android.permission.ACCESS_COARSE_
LOCATION, 567
android.permission.ACCESS_FINE_
LOCATION, 567, 580
android.permission.INTERNET, 600, 610
android.permission.READ_CONTACTS,
980
android.permission.READ_PHONE_
STATE, 596
android.permission.RECORD_AUDIO, 626
android.permission.USE_SIP, 600
android.permission.WRITE_EXTERNAL_
STORAGE, 853
uruchamianie aktywnoci, 162
uruchamianie emulatora, 83
Urzd Przydzielania Numerw
Internetowych, 129
urzdzenia AVD, 60, 63, 65, 122
urzdzenia typu handheld, 282
urzdzenie Android Developer Phone, 1005

1178 Skorowidz
usuga, 59
dugoterminowa, 486, 488
Google Maps, 553
Google Maps JavaScript API, 569
HTTP, 43, 337
IStockQuoteService, 377, 380, 381, 382, 384
LocationManager, 566, 567, 569, 571
lokalna, 367, 369
nietrwaa, 484
notowa giedowych, 377
RemoteViewsService, 1112, 1113
RESTful, 48
ServiceManager, 566
StockQuoteService2, 390
Test60SecBCRService, 474
trwaa, 485
WakefulIntentService, 472
zdalna, 367
zorientowana na pooenie, 43
usugi
definiowanie interfejsu, 376
przekazywanie plikw parcelowanych, 388
przekazywanie typw danych, 385
uruchamianie i zatrzymywanie, 478
utworzenie i zamknicie, 478
usugi obsugujce jzyk AIDL, 368, 376
usugi w Androidzie, 368
ustanawianie menedera alarmw, 496
ustawianie obrazu, 196
usuwanie bdw, 82
usuwanie danych, 138

V
VoIP, Voice over Internet Protocol, 597

W
walidator licencji Android Market, Market
License Validator, 52
waluta klienta, Buyers Currency, 1016
warto startOffset, 529
wtek
narzdzia, 429
stan Dead, 441
stan New thread, 441
stan Not runnable, 441
stan Runnable, 441

wtek gwny
aktywnoci, 428
dostawcy treci, 429
informacje o stanie, 439
odbiorcy komunikatw, 429
opnianie operacji, 432
przetrzymywanie, 432
usugi, 429
wtek pojedynczy, 429
wtek roboczy, 436
testowanie, 442
wzy, 427
wiadomoci e-mail, 593
widoczno menu, 272
widok, view, 39, 58, 176
CheckBoxPreference, 305
contact_entities_view, 971
EditTextPreference, 307
encji kontaktw, 971
GridView, 200
ImageView, 221, 520
Konta i synchronizacja, 954
ListPreference, 313
ListView, 200, 201, 202, 525
MapView, 554
MapView wraz ze znacznikami, 557
niestandardowy - kko, 1140
RemoteViews, 735
RingtonePreference, 308
definiowanie preferencji, 309
interfejs UI, 308
TextView, 95, 202, 263
view_contacts, 971
zdalny, 1106
widet, widget, 176, 736
cykl ycia, 740
definiowanie, 740
definiowanie dostawcy, 747
definiowanie rozmiaru, 748
definiowanie w pliku manifest, 740
definiowanie w pliku XML, 741
implementacja abstrakcyjna modelu, 754
implementacja aktywnoci
konfiguratora, 761
implementacja dostawcy, 751
implementacja modeli, 753
implementacja modelu stanw, 758

Skorowidz

interfejs modelu, 753


ksztat ta, 750
metody zdarze zwrotnych, 745
odinstalowanie pakietw, 745
ograniczenia i rozszerzenia, 764
tworzenie instancji, 742
ukad graficzny, 749
ukad graficzny formularza, 763
Urodziny, 757
usunicie instancji widetu, 745
wspdzielone preferencje, 755
widet ekranu startowego
AndroidManifest.xml, 1129
metadane dostawcy widetw, 1128
pliki projektu, 1121
ukad graficzny widetu, 1128
widet RadioButton, 192
widet urodzinowy, 746
widety ekranu startowego, 42, 736, 1105
lista, 738
tworzenie instancji widetu, 737
wieloczciowa metoda POST, multipart
POST, 341
wielodotykowo, 879
kod Java, 880
ukad graficzny, 880
wyniki narzdzia LogCat, 883
zastosowanie, 882
wielokrotnoci, 101
wielowtkowy modu HttpClient, 346
wirtualna maszyna, 33
wasne pliki zasobw XML, 109
waciwo
colspan, 234
onClick, 192
orientation, 228
wczanie widokw, 603
wskanik do danych, 159
wspdzielenie danych, 412
wspdzielone identyfikatory uytkownika, 412
wsprzdne tekstury, 694
wtyczka ADT, 82, 546
wtyczka Galaxy Tab, 52
wyjtek IllegalArgumentException, 629
wyjtki protokoowe, 343
wyjtki transportowe, 343
wykonywanie zdj, 643

1179

wymiary, 235
Cale, 235
Milimetry, 235
Piksele, 235
Piksele niezalene od gstoci, 235
Piksele niezalene od skali, 235
Punkty, 235
wysoko pojemnika ListView, 209
wysyanie aplikacji, 1018
wysyanie komunikatu, 454
wysyanie powiadomienia, 464
wyszukiwanie
aktywno wyszukiwania, 773
aplikacja odpowiedzialna za ustawienia, 775
dostawcy propozycji, 772
dostp do aktywnoci testowych, 785
globalne, 768
interakcja aktywnoci z przyciskiem, 780, 782
jawne wywoywanie, 787
kod rdowy aktywnoci, 778
kursor propozycji, 772
menu aktywnoci, 784
metadane, 793
pliki aktywnoci, 778
pliki projektu, 777
pliki ukadu graficznego, 778
pole wyszukiwania, 769
propozycje wyszukiwania, 771, 772
propozycje zerowe, 771
SearchInvokerActivity, 789
tryb propozycji zerowych, 771
typy aktywnoci, 777
ukad graficzny aktywnoci, 781
ustawienia, 775, 777
wywoywanie aplikacji, 774
wyszukiwanie globalne, 769
dostawcy propozycji, 774
wyszukiwanie lokalne, 769, 790, 811
aktywno, 795
pole wyszukiwania, 796
wyniki wyszukiwania, 796
wyszukiwanie w Androidzie, 768
wywoanie dostawcy widetu, 1117
wywoanie obiektu Cursor, 132
wywoywanie ekranu startowego, 166
wywoywanie usugi, 391
wzorce identyfikatorw URI, 148

1180 Skorowidz

X
XUL, XML User Interface Language, 39

Z
zabezpieczenia, 317
certyfikat cyfrowy, 318
stosowanie uprawnienie, 325
zabezpieczenia na granicach procesu, 324
zabezpieczenia rodowiska wykonawczego, 324
zakadka
Permissions, 328
Virtual devices, 83
Window/Android SDK and AVD
Manager, 58
zasady postpowania w Android Market, 1006
zasoby, 40, 91
a zmiany konfiguracji, 112
alternatywne, 113
Androida, 98
childLayout, 203
domylne, 113
identyfikatory zasobw, 114
nieskompresowane, 111
obrazw w jzyku XML, 107
plurals, 101
R.drawable.frame_animation, 523
typu Color, 104
typu color w kodzie Java, 105
typu color-drawable, 108
typu color-drawable w kodzie Java, 108
typu color-drawable w kodzie XML, 108
typu dimension, 105
typu dimension w kodzie Java, 106
typu dimension w kodzie XML, 106
typu drawable, 196
typu image, 106
typu image w rodowisku Java, 107
typu layout, 94
typu string, 92, 95, 103
typu string w jzyku XML, 104
typu string w kodzie Java, 103
wielokrotno, 102

zastpowanie metody funkcj, 607


zaufany wydawca certyfikatw, certificate
authority, CA, 318
zdalne wywoanie procedury, 368
zdalny ukad graficzny, 1109
wczytywanie, 1111
zdarzenia dotyku, 888
zdarzenie ACTION_MOVE, 885
zintegrowane przeszukiwanie Androida, 42
zmienna
ignoreLastFinger, 894
PATH, 55
systemowa PATH, 321
rodowiskowa JAVA_HOME, 53
rodowiskowa PATH, 55
znacznik
<activity>, 227
<application>, 227
<big>, 224
<monospace>, 224
<small>, 224
<strike>, 224
<sub>, 224
<sup>, 224
<uses-permissions>, 334
accelerateInterpolator, 532
group, 268
ikony menu, 271
kategorii grupy, 271
menu, 268
showAsAction, 1092
uses-permission, 625
wczania menu, 272
wyczania menu, 272
zaznaczania, 271

danie metody GET, 340


danie metody POST, 340
danie typu MIME, 129

Vous aimerez peut-être aussi