145 75 13MB
Polish Pages [498] Year 2015
Spis treści
Wstęp 1. Elementy projektu aplikacji na platformę Android w środowisku Eclipse 2. Pierwsza aplikacja „Witaj Świecie” 3. Emulator urządzenia z systemem Android AVD 4. Główne elementy składowe aplikacji na platformie Android 4.1. Kontekst 4.2. Aktywności 4.3. Intencje 4.4. Usługi 4.5. Dostawcy treści 4.6. Odbiorcy treści 5. Podstawy struktury pliku AndroidManifest.xml 6. Filtry intencji 7. URI – Uniform Resource Identifier 8. Pliki preferencji i pliki płaskie 8.1. Pliki preferencji 8.2. Pliki płaskie
9. Rysowanie, animacje i podstawy grafiki 3D 9.1. Rysowanie z wykorzystaniem obiektów Canvas i Paint 9.2. Definiowanie kształtów w zasobach XML 9.3. Rysowanie z wykorzystaniem klasy ShapeDrawable 9.4. Animacje 9.5. Podstawy grafiki 3D 10. Zastosowanie bazy danych SQLite 10.1. Projektowanie baz danych 10.2. Podstawy języka SQL 10.3. Struktura aplikacji zawierającej bazę danych 11. Powiadomienia – Notifications 12. Wykorzystanie klasy Thread i AsyncTask 12.1. Wątki – obliczenia w tle 12.2. Wątki – obliczenia w tle z przekazywaniem danych do interfejsu 12.3. AsyncTask – obliczenia w tle z prostą integracją z interfejsem 13. Przetwarzanie plików XML 14. Opis typów zasobów 14.1. Łączenie zasobów z programem 14.2. Typy zasobów 14.3. Definiowanie zasobów alternatywnych (podfoldery /res)
14.4. Zasady budowy różnego typu ikon dla systemu Android 15. Przegląd elementów interfejsu użytkownika oraz projektowanie układów 15.1. Elementy interfejsu GUI 15.2. Projektowanie układów – Layouts 15.3. Identyfikacja i obsługa gestów 15.4. Adaptery danych Podstawowe zasoby internetowe Przypisy
Wstęp Projektowanie aplikacji działających na urządzeniach z systemem operacyjnym Android obok całej analizy funkcjonalnej programu wymaga uwzględnienia specyfikacji warunków, w jakich uruchamiana jest ta aplikacja. Należy zwrócić szczególną uwagę na to, że urządzenia z systemem Android są urządzeniami mobilnymi, co oznacza, że ich zasoby są ograniczone – na przykład przez wydajność procesora lub rozmiar pamięci. Jednocześnie aplikacje budowane na urządzenia mobilne charakteryzują się specyficznym interfejsem komunikacji z użytkownikiem. Twórcy systemu Android zaproponowali więc specyficzny dla urządzeń mobilnych (aplikacji mobilnych) sposób uruchamiania i funkcjonowania tych aplikacji w systemie operacyjnym. Jednym z ważniejszych aspektów działania aplikacji mobilnej jest sposób komunikacji z różnymi elementami, w jakie wyposażone jest urządzenie mobilne, np. aparat, żyroskop, karta SIM, GPS. Efektywne projektowanie aplikacji zgodne z założeniami systemu operacyjnego Android wymaga poznania podstawowych pojęć związanych z budową aplikacji mobilnych oraz cyklu życia aplikacji mobilnej (cykl życia Aktywności). Idea funkcjonowania aplikacji mobilnej obejmuje cztery podstawowe elementy składowe [1]: Kontekst (obiekt typu Context) Kontekst jest środowiskiem, w którym uruchamiana jest zaprojektowana aplikacja mobilna. Za pośrednictwem kontekstu istnieje możliwość zarządzania aplikacją i uzyskiwania dostępu do parametrów aplikacji
oraz urządzenia, na którym została uruchomiona. Aktywność (obiekt typu Activity) Aktywności stanowią podstawowy budulec do tworzenia zaplanowanej funkcjonalności aplikacji mobilnej i zawierają ich implementację. Oznacza to, że wywoływanie funkcji aplikacji mobilnej odbywa się przez uruchomienie Aktywności. W danym zachowaniu można wywoływać kolejne Aktywności, dzięki czemu uzyskujemy możliwość realizacji nawet bardzo złożonych scenariuszy. Podstawową właściwością Aktywności jest krótki czas jej wykonania oraz kontakt z użytkownikiem aplikacji lub innymi aplikacjami. Intencja (obiekt typu Intent) Intencje stanowią mechanizm obsługi zdarzeń występujących w aplikacji mobilnej (komunikaty asynchroniczne). Z wykorzystaniem mechanizmu Intencji możliwe jest uruchomienie Aktywności (także w innej aplikacji) lub serwisu w momencie wystąpienia określonego zdarzenia. Mechanizm Intencji służy także do komunikacji między różnymi aplikacjami uruchomionymi na urządzeniu mobilnym oraz do obsługi zdarzeń pochodzących z innych aplikacji (np. połączenie przychodzące) i wyposażenia urządzenia (np. niski stan baterii, wykonano zdjęcie). Usługa/Serwis (obiekt typu Service) Usługi można traktować jako specyficzne zastosowanie Aktywności. Usługi zawierają implementację funkcji, które nie wykorzystują interfejsu komunikacji z użytkownikiem oraz wymagają długiego czasu działania. Może on wynikać z czasochłonnych algorytmów obliczeniowych lub z potrzeby dostępu do bazy danych (w tym zewnętrznych baz danych) i konieczności wysłania lub pobrania dużej ilości danych. Cechą charakterystyczną usługi jest fakt działania „w tle”. W postaci usługi często implementuje się funkcje działające cyklicznie. Dostawcy treści (obiekt typu ContentProvider) Dostawcy treści (dane) zapewniają dostęp do swoich zasobów innym aplikacjom zainstalowanym na urządzeniu. Udostępniane dane mogą być przechowywane przez dostawcę w bazie danych SQLite lub w inne trwałej postaci. Dostawcy treści mogą też być związani komponentami sprzętowymi zainstalowanymi w urządzeniu – takimi jak GPS.
Przykłady wykorzystania systemowych dostawców treści oraz sposób zaprojektowania własnego dostawcy treści przedstawiono w podrozdziale 4.5. Odbiorcy komunikatów (obiekt typu BroadcastReceiver) Odbiorcy komunikatów są specjalnymi klasami umożliwiającymi komunikację z innymi aplikacjami dzięki otrzymywaniu konkretnych komunikatów wysyłanych przez inne aplikacje zainstalowane na urządzeniu. Przykłady wykorzystania komunikatów systemowych przedstawiono w rozdziale 6, a przykład implementacji własnych komunikatów przedstawiono w podrozdziale 4.6 (Przykład 7). Szczegółowe informacje na temat sposobu wykorzystania poszczególnych elementów składowych aplikacji mobilnej z opisem zasad ich działania i przykładowych sposobów wykorzystania przedstawiono w kolejnych rozdziałach.
Więcej na: www.ebook4all.pl
1. Elementy projektu aplikacji na platformę Android w środowisku Eclipse W środowisku Eclipse do budowy aplikacji mobilnych na platformę Android udostępniony został dedykowany plugin, którego zastosowanie w dużym stopniu porządkuje i upraszcza cały proces twórczy. Zastosowanie środowiska Eclipse umożliwia implementację i testowanie aplikacji na AVD (Android Virtual Devices – patrz rozdział 3) bez konieczności posiadania rzeczywistego urządzenia mobilnego. Takie podejście zmniejsza koszty tworzenia aplikacji i jednocześnie umożliwia testowanie aplikacji na urządzeniach o skrajnie różnych parametrach (rozmiar ekranu, pamięć itd.). Główne okno środowiska Eclipse (przykładowa konfiguracja) jest przedstawione na rysunku 1.
Rysunek 1. Okno główne IDE Eclipse do budowy aplikacji mobilnej na platformę Android
Okno projektu składa się z czterech zasadniczych elementów: 1. Struktura projektu – przedstawia wszystkie elementy wchodzące w skład aplikacji w postaci drzewa katalogów i plików. 2. Główny obszar roboczy – zasadniczy element okna projektu, w którym wprowadza się kod programu, tworzy pliki konfiguracyjne oraz projektuje interfejs komunikacji z użytkownikiem. 3. Szczegóły elementów z głównego obszaru roboczego – dodatkowe informacje dotyczące elementów wybranych w głównym obszarze roboczym. Przykładowo, podczas przeglądania kodu źródłowego w obszarze tym wyświetlają się elementy definicji klas. 4. Konsola z komunikatami (dla wybranej zakładki Console) – zawiera wykaz komunikatów będących konsekwencją działań wykonywanych w środowisku Eclipse (w tym komunikaty o błędach). Na przykładowym ekranie pokazano sekwencję komunikatów związanych z uruchomieniem aplikacji. W tej części projektu można przełączać się między różnymi widokami w zależności od wybranej zakładki. Rysunek 2 przedstawia zakładkę LogCat,
która w dalszej części będzie wykorzystywana do wypisywania z aplikacji komunikatów do logu (polecenie Log) w celu śledzenia pośrednich wyników kontrolnych.
Rysunek 2. Zakładka LogCat zawierająca komunikaty zapisywane do logu – przykład Log.d (”Baza SQL”,””)
Rysunek 3. Okno elementów składowych projektu aplikacji mobilnej
Rysunek 3 przedstawia strukturę projektu w postaci drzewa folderów i plików. Odpowiada ona fizycznej strukturze folderów. Na rysunku przedstawiono strukturę wygenerowaną w trakcie procedury tworzenia nowego projektu z jedną klasą startową, prostym interfejsem (tytuł aplikacji i jedno pole typu tekst) oraz ze zdefiniowaną ikoną startową. Nazwy folderów są
wykorzystywane w trakcie kompilacji, dlatego nie należy ich modyfikować. Wykaz predefiniowanych nazw folderów i podfolderów jest znacznie szerszy. Projektant aplikacji powinien zachować utworzoną strukturę, ma jednak możliwość dodania własnych folderów. Projekt aplikacji mobilnej podzielony jest na następujące elementy składowe (wybrane foldery): src – główny folder zawierający definicję wszystkich elementów oprogramowania zapisanych w języku Java. W przykładzie użyto pliku o nazwie MainActivity.java zawierającego definicję klasy startowej. gen – folder zawierający klasę R.java generowaną automatycznie przez środowisko w momencie definiowania zasobów aplikacji (folder res). Nie należy w nim dokonywać zmian. bin – folder zawierający postać wykonywalną aplikacji po poprawnej kompilacji. libs – folder zawiera biblioteki wykorzystywane do budowy aplikacji. res – zasadniczy folder zawierający wszystkie zasoby będące elementami składowymi projektowanej aplikacji. Zasoby podzielono na kilka grup: drawable-* – pliki graficzne (w przykładzie plik ic_launcher.png). Folder zawiera kilka rozszerzeń służących do umieszczenia tego samego rysunku lub ikony w urządzeniach o różnych rozdzielczościach. layout – pliki XML zawierające definicje GUI. menus – pliki XML zawierające definicje menu. values – pliki XML zawierające definicję różnych typów stałych. W przykładzie plik strings.xml zawiera definicje stałych typu string, do których mogą odwoływać się elementy oprogramowania. Elementem każdej aplikacji jest plik AndroidManifest.xml umożliwiający definiowanie właściwości tworzonej aplikacji. Folder src zawiera cały kod programu – wszystkie elementy dostępne w języku Java. W przypadku rozbudowanych aplikacji folder ten może składać się z wielu pakietów, a pakiety będą zawierały rozbudowaną strukturę podfolderów. Elementy grafiki (zdjęcia i ikony) umieszczane są w folderze res/drawable-*. Należy pamiętać, że dopasowanie grafiki do rozmiaru i parametrów ekranu urządzenia mobilnego wymaga od projektanta aplikacji przygotowania kilku wariantów tej samej grafiki i umieszczenia każdej z nich w odpowiednim podfolderze z taką nazwą, jaka używana jest
w oprogramowaniu. Wyróżnia się kilka rozszerzeń tego podfolderu, na przykład: • drawable-hdpi – zawiera grafikę o wysokiej rozdzielczości przeznaczoną dla dużych urządzeń (high dots per inch); • drawable-mdpi – zawiera grafikę dla urządzeń o średniej rozdzielczości; • drawable-xhdpi – zawiera grafikę o bardzo wysokiej rozdzielczości.
Rysunek 4. Przykład ikon startowych aplikacji dla różnych rozdzielczości urządzeń
Folder, w którym znajduje się projekt GUI (layouts), może zawierać definicję szczegółowych predefiniowanych nazw podfolderów precyzujących wygląd GUI dla różnych urządzeń: • res/layout/activity_main.xml – domyślna definicja GUI; • res/layout-small/activity_main.xml – definicja GUI dla małych ekranów; • res/layout-large/activity_main.xml – definicja GUI dla dużych ekranów; • res/layout-xlarge/activity_main.xml – definicja GUI dla bardzo dużych ekranów. Możliwe jest jeszcze doprecyzowanie definicji w zależności od orientacji ekranu: • res/layout-xlarge-land/activity_main.xml – definicja GUI dla bardzo dużych ekranów w układzie poziomym. Przedstawiony sposób definiowania GUI dla aplikacji umożliwia wykorzystanie urządzeń o bardzo różnych rozmiarach i budowanie odmiennych interfejsów dla tej samej aplikacji. Układ w postaci listy, odpowiedni dla ekranu
o małych rozmiarach, może być nieefektywny dla dużych ekranów, na których bezpośrednio obok listy można wyświetlać szczegóły wybranego elementu.
Rysunek 5. Projekt różnych interfejsów tej samej aplikacji dla urządzeń o odmiennych rozmiarach ekranu
Niektóre elementy składowe projektu mogą być przeglądane i modyfikowane na dwa sposoby – w postaci XML i graficznej: • pliki zawierające projekt interfejsu GUI (layout) (rys. 6);
Rysunek 6. Widok projektu interfejsu: postać XML i graficzna
• pliki zawierające definicje (values) (rys. 7);
Rysunek 7. Widok pliku definicji: postać XML i graficzna
• plik
AndroidManifest.xml.
Rysunek 8. Widok pliku AndroidManifest.xml: postać XML i graficzna
Nie ma znaczenia, która postać pliku jest modyfikowana – wszystkie zmiany wprowadzone w jednej postaci są przenoszone do drugiej przez środowisko Eclipse. Po wybraniu elementu w oknie projektu jego zawartość zostaje wyświetlona w głównym obszarze roboczym.
2. Pierwsza aplikacja „Witaj Świecie” W tym rozdziale przedstawiona zostanie pierwsza kompletna, prosta aplikacja zawierająca jedną Aktywność z podstawowym interfejsem (layout). Aplikacja została utworzona za pomocą kreatora projektu Android Application Project. W dalszej części przedstawione są kolejne kroki kreatora oraz opis utworzonych plików. 1. Uruchomienie kreatora nowego projektu File/New Project.
Rysunek 9. Kreator projektu Android Application Project
2. Określenie parametrów aplikacji.
Rysunek 10. Kreator projektu Android – parametry aplikacji
– nazwa aplikacji wyświetlana na pasku tytułu aplikacji (może zawierać polskie znaki); Project Name – nazwa projektu wyświetlana w oknie projektu; Package Name – nazwa pakietu tworzona w folderze src, w którym będzie umieszczona klasa pierwszej Aktywności; Minimum Required SDK – minimalny poziom SDK aplikacji (aplikacja może nie działać na urządzeniach z niższą wersją SDK); Target SDK/Compile With – należy podawać maksymalny dostępny numer Application Name
wersji SDK; Theme – definicja stylu interfejsu aplikacji. 3. Konfiguracja elementów projektu.
Rysunek 11. Kreator projektu Android – elementy projektu Create custom launcher icon – utworzenie ikony startowej Create activity – utworzenie startowej Aktywności.
projektu;
4. Wybór ikony startowej projektu.
Rysunek 12. Kreator projektu Android – ikona startowa
Kreator
udostępnia domyślną ikonę aplikacji. Przyciski [Image], umożliwiają także wybór innej, wcześniej przygotowanej
[Clipart], [Text]
ikony. 5. Utworzenie startowej Aktywności.
Rysunek 13. Kreator projektu Android – Aktywność startowa
6. Określenie parametrów Aktywności.
Rysunek 14. Kreator projektu Android – parametry Aktywności startowej Activity Name – nazwa klasy Aktywności (folder src); Layout Name – nazwa pliku XML z definicją interfejsu (folder res/layout).
Po zaakceptowaniu wprowadzonych parametrów tworzona jest struktura projektu i generowane zawartości plików. Elementy składowe aplikacji: 1. Plik
src/../MainActivity.java
package com.example.witajswiecie; import android.os.Bundle; import android.app.Activity; import android.view.Menu; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //wywołanie interfejsu setContentView(R.layout.activity_main); } }
2. Plik
res/layout/activity_main.xml
Rysunek 15. Struktura projektu aplikacji „Witaj Świecie”
Rysunek 16. Graficzny podgląd GUI aplikacji „Witaj Świecie”
Plik
res/values/strings.xml
Witaj Świecie Settings Hello world!
Pliki ic_launcher.png w folderach drawable-* zawierają tę samą ikonę w różnych rozmiarach. Pozostałe pliki tworzone w projekcie mają znaczenie pomocnicze i na tym etapie ich analiza nie jest potrzebna.
Rysunek 17. Ikona startowa i widok aplikacji „Witaj Świecie” po uruchomieniu
3. Emulator urządzenia z systemem Android AVD W trakcie procesu tworzenia aplikacji nie jest konieczne posiadanie urządzenia mobilnego. Aplikacje można uruchamiać na emulatorze urządzenia z systemem Android – AVD (Android Virtual Device). Takie rozwiązanie znacznie upraszcza i obniża koszty stworzenia i testowania aplikacji, zwłaszcza testowania aplikacji na różnych wersjach systemu operacyjnego Android oraz na różnych rozmiarach i typach urządzeń. Należy jednak pamiętać, że ostateczne testy i sprawdzenie zachowania aplikacji muszą być wykonywane na docelowych urządzeniach, na których działanie może być odmienne od wyników uzyskiwanych na emulatorze. W tym rozdziale przedstawione zostały właściwości AVD oraz sposób jego konfiguracji i uruchomienia. Interakcja z AVD odbywa się za pomocą klawiatury komputera, przycisków na emulatorze i myszki. Klikając kursorem myszki w ekran urządzenia wirtualnego, odwzorowujemy dotykanie ekranu. Jako gest przeciągania można wykorzystać kursor myszy z wciśniętym lewym klawiszem. Aby skorzystać z AVD, należy najpierw przygotować (skonfigurować) jego wirtualne urządzenia. Menedżer AVD dostępny jest w menu Window/Android Virtual Device Manager. Rysunek 18 przedstawia okno menedżera AVD. Na liście wyświetlone zostają wcześniej zdefiniowane urządzenia AVD różniące się parametrami. Utworzenie nowego urządzenia AVD odbywa się przez wybranie przycisku New… .
Rysunek 18. Widok menedżera urządzeń AVD
Przykładowe konfiguracje dwóch różnych urządzeń są przedstawione na rysunku 19. Porównywane urządzenia AVD różnią się wersją systemu operacyjnego oraz rozmiarem ekranu.
Rysunek 19. Przykładowe parametry konfiguracyjne dwóch wirtualnych urządzeń AVD
Konfigurując nowe urządzenie, należy określić: • nazwę AVD, • rozmiar ekranu oraz jego rozdzielczość, • rozmiar pamięci operacyjnej, • rozmiar pamięci wewnętrznej, • rozmiar karty SD (opcjonalnie), • rodzaj dostępnych kamer. Rysunki 20 oraz 21 przedstawiają wygląd okna emulatora po uruchomieniu dwóch różnych typów zdefiniowanych AVD.
Rysunek 20. Widok emulatora AVD 3,7 cala
Rysunek 21. Widok emulatora AVD 7 cali
Po zakończeniu procesu konfiguracji urządzenia są gotowe do użycia. Po uruchomieniu aplikacji (Run As…) wyświetlana jest lista dostępnych urządzeń
AVD oraz rzeczywistych urządzeń podłączonych do komputera (patrz rys. 22). Z listy należy wybrać to urządzenie, na którym ma być zainstalowana i uruchomiona aplikacja.
Rysunek 22. Wykaz rzeczywistych i wirtualnych urządzeń do uruchomienia aplikacji
Należy pamiętać, że testowanie aplikacji nie powinno odbywać się wyłącznie z wykorzystaniem AVD. Ostateczne sprawdzenie poprawności działania stworzonej aplikacji powinno zostać wykonane na rzeczywistym urządzeniu. Rysunek 23 przedstawia wygląd tej samej aplikacji uruchomionej na AVD oraz na rzeczywistym urządzeniu.
Rysunek 23. Widok uruchomionej aplikacji na AVD i rzeczywistym urządzeniu
Korzystając z AVD, użytkownik ma dostęp do następujących funkcji: • zmiana układu ekranu z poziomego na pionowy i odwrotnie – Ctrl+F11 i Ctrl+F12 lub 7 i 9 na klawiaturze numerycznej; • zmiana głośności – „+” i „–” na klawiaturze numerycznej; • funkcja „HOME” – Home; • rozpoczęcie wybierania numeru telefonu – F3; • włączenie funkcji wyszukiwania – F5; • wyłączenie urządzenia – F7; • menu programowe – F2; • funkcja „BACK” – Esc.
4. Główne elementy składowe aplikacji na platformie Android Wspomniane w pierwszym rozdziale podstawowe elementy składowe aplikacji mobilnej powinny być stosowane zgodnie z wytycznymi sformułowanymi przez twórców platformy.
4.1. Kontekst Kontekst aplikacji (Context) jest punktem wejściowym do kontrolowania działania całej aplikacji (Aktywności i usługi), który zapewnia dostęp do informacji o otoczeniu (kontekście), w jakim wykonywana jest aplikacja. Dostęp do kontekstu możliwy jest przez obiekt Context w następujący sposób: Context context = getApplicationContext();
Utworzony obiekt Context umożliwia między innymi dostęp do zasobów aplikacji (patrz rozdział 14) z wykorzystaniem metody getResources. W kolejnych wierszach przedstawiono przykłady pobrania wartości zasobu typu String o nazwie komunikat1, String komunikat = context.getResources().getString(R.string.komunikat1);
(specjalna klasa R tworzona jest automatycznie w trakcie generowania aplikacji) oraz zasobu typu Color o nazwie naglowek. Color kolor = context.getResources().getColor(R.color.naglowek);
Istnieje możliwość zdefiniowania pliku zawierającego dane dotyczące ustawień aplikacji (plik preferencji). Dostęp do pliku z preferencjami aplikacji możliwy jest przez wywołanie metody getSharedPreferences. SharedPreferences pref = context.getSharedPreferences(”plik”, MODE_PRIVATE); String ustawienie = pref.getString(”ustawienie1”, ”T”);
Metody klasy SharedPreferences umożliwiają pełne zarządzanie wartościami poszczególnych preferencji – zarówno odczyt, jak i zapis ich wartości w trakcie działania aplikacji. Ważnymi zastosowaniami kontekstu są zarządzanie plikami i folderami aplikacji, dostęp do parametrów urządzenia, na którym uruchomiona jest aplikacja, sprawdzanie uprawnień dostępu (permissions) aplikacji do różnych usług oraz dostęp do bazy danych aplikacji. Tworzenie pliku: File file = new File(context.getFilesDir(), ”nazwa”);
Dostęp do usługi lokalizacji: LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
4.2. Aktywności Podstawowym elementem składowym każdej aplikacji mobilnej są Aktywności, które implementują konkretne funkcje programu. Implementacja Aktywności dotyczy zarówno procesów obliczeniowych, jak i interfejsu komunikacji z użytkownikiem. Każda Aktywność może być utożsamiana z interfejsem użytkownika – obowiązuje zasada takiego projektowania Aktywności, aby jedna Aktywność odpowiadała projektowi jednego „ekranu” interfejsu użytkownika (GUI). Ze względu na podstawową rolę Aktywności wprowadzono różne warianty działania Aktywności – dotyczy to różnych metod wymiany danych oraz różnych sposobów wywoływania Aktywności. W systemie operacyjnym Android na urządzeniu mobilnym działająca aplikacja może w danym momencie składać się z kilku działających procesów,
jednocześnie na urządzeniu może być uruchomionych kilka aplikacji. Należy jednak pamiętać, że w danym momencie użytkownikowi udostępniany jest interfejs tylko jednej aplikacji, a dokładniej jednej Aktywności. System operacyjny, zarządzając działaniem Aktywności, tworzy stos wywołań Aktywności. Uruchomiona jest tylko ta Aktywność, która znajduje się na wierzchu stosu i tylko jej interfejs jest dostępny dla użytkownika. Działanie pozostałych Aktywności jest wstrzymane. Ze względu na ograniczone zasoby urządzenia mobilnego umieszczanie kolejnych Aktywności na stosie (uruchamianie kolejnych aplikacji) może spowodować usunięcie najstarszych Aktywności przez system operacyjny w celu zapewnienia wymaganych zasobów dla nowej aplikacji. Oznacza to, że system operacyjny podejmie decyzję o wyłączeniu aplikacji poza kontrolą użytkownika. Konsekwencją takiego podejścia jest konieczność pełnego zarządzania zasobami aplikacji w taki sposób, aby nawet w przypadku jej zatrzymania lub zamknięcia działanie programu zostało zakończone poprawnie. Podstawą projektowania prawidłowych aplikacji (Aktywności) na platformie Android jest zrozumienie cyklu życia Aktywności. Cykl ten znacząco różni się od działania aplikacji na komputerach na przykład z systemem operacyjnym Windows.
Rysunek 24. Cykl życia Aktywności na platformie Android Tabela 1. Tabela przejść między metodami cyklu życia Metoda cyklu życia Aktywności
Opis
Następna możliwa metoda w cyklu życia Aktywności
onCreate()
Metoda onCreate() jest wy woły wana przy uruchamianiu Akty wności. W cy klu ży cia Akty wności wy woły wana jest ty lko raz. W tej metodzie powinno się inicjować wszy stkie struktury dany ch potrzebne do działania aplikacji. Do metody przekazy wany jest obiekt klasy Bundle zawierający zapisany stan działania z poprzedniego uruchomienia, o ile został on wcześniej zachowany . Po tej metodzie następuje zawsze metoda onStart().
onStart()
onRestart()
Metoda wy woły wana po metodzie onStop(), gdy działanie Akty wności jest wznawiane i jej interfejs jest przy wracany na ekran urządzenia. Po niej zostaje wy wołana metoda onStart().
onStart()
onStart()
Metoda wy woły wana po metodzie onCreate() lub po metodzie onRestart(). Wy woły wana jest ona tuż przed pojawieniem się interfejsu Akty wności na ekranie urządzenia. Po niej zawsze wy woły wana jest metoda onResume().
onResume()
onResume()
Metoda wy woły wana po onStart() lub po onPause(). W czasie realizacji tej metody interfejs Akty wności znajduje się na ekranie urządzenia. W tej metodzie można wznowić obliczenia, odtworzy ć połączenia z bazą dany ch, połączenia sieciowe, animacje lub uruchomić urządzenia wy magające wy łączności, takie jak kamera. W tej metodzie należy także wznowić działanie serwisów (jeżeli takie wy stępują). Po tej metodzie rozpoczy na się realizacja zaplanowany ch funkcji Akty wności.
onPause()
onPause()
Metoda wy woły wana, gdy sy stem zamierza uruchomić inną Akty wność. Jest to moment, w który m należy dokonać zapisu wszy stkich ważny ch dany ch, na przy kład zatwierdzenia zapisów do bazy dany ch (commit). Należy pamiętać, że w przy padku, gdy po przejściu do metody onPause() brakuje pamięci dla nowo uruchamianej aplikacji, sy stem operacy jny może zdecy dować o zakończeniu procesu związanego z daną Akty wnością i kolejne metody (podane z prawej strony ) nie zostaną wy wołane. W tej metodzie powinno również następować zatrzy my wanie działań związany ch z odtwarzaniem animacji, kontaktu z bazą dany ch oraz działania serwisów związany ch z Akty wnością. Inna Akty wność nie zostanie uruchomiona do momentu zakończenia metody , dlatego należy uważać, aby czas jej wy kony wania nie by ł zby t długi.
onResume() onStop()
onStop()
Metoda wy woły wana jest po metodzie onPause(), gdy interfejs Akty wności nie jest już widoczny na ekranie urządzenia. Należy pamiętać, że w przy padku braku pamięci (stwierdza to sy stem operacy jny ) onRestart() metoda ta może w ogóle nie zostać wy wołana, gdy ż onDestroy() proces zostanie zakończony po wy wołaniu onPause(). Wy wołanie następny ch metod może by ć pominięte przy zakończeniu pracy aplikacji przez sy stem operacy jny .
onDestroy()
Metoda wy woły wana przed zakończeniem działania aplikacji. W tej metodzie powinno następować pełne zwalnianie wszy stkich zarezerwowany ch zasobów. Tak jak w przy padku onPause(), nie można mieć pewności, że ta metoda zostanie w ogóle wy wołana.
–
Rysunek 29. Komunikaty głównej Aktywności po ponownym uruchomieniu aplikacji
Rysunek 30. Wykonane przejście w cyklu życia aplikacji
Klasa głównej Aktywności (MainActivity.java) package com.rwr.testcykluzycia; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date;
import import import import import import import import import
android.os.Bundle; android.app.Activity; android.content.Intent; android.text.format.DateFormat; android.view.Menu; android.view.View; android.view.View.OnClickListener; android.widget.Button; android.widget.TextView;
public class MainActivity extends Activity { private Button PrzyciskNastepnaAktywnosc; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.textView1); PrzyciskNastepnaAktywnosc = (Button) findViewById (R.id.button1); PrzyciskNastepnaAktywnosc.setOnClickListener (new OnClickListener() { public void onClick(View v) { Intent startAnotherActivity = new Intent(getApplicationContext(), NastepnaActivity.class); startActivity(startAnotherActivity); } }); Komunikat (”MainActivity onCreate”); } private void Komunikat (String komunikat) { SimpleDateFormat formatczasu = new SimpleDateFormat(”H:m:s S”); Date akt = Calendar.getInstance().getTime(); String text = tv.getText().toString() + komunikat + ” ” + formatczasu.format(akt) + ”\n”; tv.setText(text); } @Override protected void onRestart() { super.onRestart(); Komunikat(”MainActivity onRestart”); }
@Override protected void onStart() { super.onStart(); Komunikat(”MainActivity onStart”); } @Override protected void onResume() { super.onResume(); Komunikat(”MainActivity onResume”); } @Override protected void onPause() { Komunikat(”MainActivity onPause”); super.onPause(); } @Override protected void onStop() { Komunikat(”MainActivity onStop”); super.onStop(); } @Override protected void onDestroy() { Komunikat(”MainActivity onDestroy”); super.onDestroy(); } }
Definicja interfejsu głównej Aktywności (layout/activity_main.xml)
Klasa pomocniczej Aktywności (NastepnaActivity.java) package com.rwr.testcykluzycia; import android.app.Activity; import android.os.Bundle; public class NastepnaActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_nastepny); } }
Rysunek 31. Interfejs aplikacji do testowania cyklu życia Aktywności – Aktywność główna
Definicja interfejsu (layout/activity_nastepny.xml)
pomocniczej
Aktywności
Rysunek 32. Interfejs aplikacji do testowania cyklu życia Aktywności – Aktywność następna (pomocnicza)
Plik konfiguracyjny
AndroidManifest.xml
4.3. Intencje Intencje są mechanizmem obsługi zdarzeń występujących w systemie operacyjnym Android. Mechanizm ten jest bardzo szeroki i odnosi się zarówno do przekazywania sterowania między takimi komponentami jak Aktywności i Usługi, jak i do przesyłania między nimi danych. Komunikacja z wykorzystaniem Intencji umożliwia współdziałanie aplikacji z innymi aplikacjami zainstalowanymi na urządzeniu, jak również z takimi elementami jak żyroskop lub GPS. Wyróżnia się dwa podstawowe sposoby wykorzystania Intencji: • wywołanie jawne; • wywołanie niejawne. W pierwszym przypadku (wywołanie jawne) podaje się nazwę klasy do wywołania, która implementuje Aktywność lub Usługę. Przykład wywołania:
Intent intent = new Intent(context, MainActivity.class); startActivity(intent);
Wywoływana klasa musi istnieć w ramach danego pakietu. Drugi przypadek (wywołanie niejawne) udostępnia ogólniejszy mechanizm. Polega on na określeniu, jaka akcja (predefiniowana w klasie Intent) i z jakimi danymi ma zostać wykonana. W tym przypadku nie podaje się jawnie aplikacji, której dotyczy wywołanie, a jedynie definiuje, co ma być wykonane. O tym, jaka aplikacja najlepiej pasuje do danej sytuacji, zdecyduje system operacyjny. Z takim wykorzystaniem Intencji ściśle związane są tzw. Filtry Intencji, które zostaną przedstawione w dalszej części rozdziału. Zasadniczo Filtry intencji definiują możliwe do wykonania akcje udostępniane przez program innym aplikacjom w ramach systemu operacyjnego. Przykład wywołania Intencji z określeniem rodzaju akcji (ACTION_VIEW) i danych dla tej akcji (http://www.wcy.wat.edu.pl): Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(”http://www.wcy.wat.edu.pl”)); startActivity(intent);
Dla powyższego wywołania system operacyjny wybierze aplikację umożliwiającą przeglądanie strony internetowej pod adresem http://www.wcy.wat.edu.pl. Niejawne wywołania Intencji mogą być obsługiwane przez inne Aktywności, Usługi oraz Dostawców Treści. Przykład 1. Wywołanie jawne w ramac h aplikac ji bez przekazywania danyc h Przykład przedstawia sposób, w jaki można przekazać sterowanie z jednej Aktywności do drugiej w ramach tej samej aplikacji bez przekazywania danych. Zdefiniowane zostały dwie Aktywności: klasa MainActivity oraz DrugaActivity dziedziczące po klasie Activity. W Aktywności głównej zdefiniowany jest przycisk wywołujący drugą Aktywność. Klasa głównej Aktywności (MainActivity.java) package com.example.intentjawnewywolanie; import android.os.Bundle; import android.app.Activity;
import import import import import
android.content.Intent; android.view.Menu; android.view.View; android.view.View.OnClickListener; android.widget.Button;
public class MainActivity extends Activity { Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button1); button.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { StartAktywnosci(); } } ); } protected void StartAktywnosci() { // Jawne wywołanie drugiej Aktywności Intent intent = new Intent(this, DrugaActivity.class); startActivity(intent); } }
Klasa drugiej Aktywności (DrugaActivity.java) package com.example.intentjawnewywolanie; import android.os.Bundle; import android.app.Activity; import android.view.Menu; public class DrugaActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_druga);
} }
Definicja interfejsu głównej Aktywności (layout/activity_main.xml)
Rysunek 33. Interfejs aplikacji do testowania wywołania jawnego Aktywności
Definicja interfejsu drugiej Aktywności (layout/activity_druga.xml)
Rysunek 34. Interfejs drugiej Aktywności do testowania wywołania jawnego Aktywności
Plik konfiguracyjny
AndroidManifest.xml
Plik zasobów (res/values/strings.xml)
Intent-jawne Settings Główna aktywność Druga aktywność Uruchom drugą aktywność
Przykład 2. Wywołanie jawne w ramac h aplikac ji z przekazaniem danyc h Przykład przedstawia sposób, w jaki można przekazać sterowanie z jednej
Aktywności do drugiej w ramach tej samej aplikacji oraz dodatkowo przekazać dane. Zdefiniowane zostały dwie Aktywności: klasa MainActivity oraz DrugaActivity dziedziczące po klasie Activity. W Aktywności głównej zdefiniowane jest pole edytowane (editText1) umożliwiające wprowadzenie danych przez użytkownika oraz przycisk wywołujący drugą Aktywność z przekazaniem danych. W drugiej Aktywności zdefiniowane zostało pole tekstowe (textView1), do którego wpisywana jest wartość przekazana z Aktywności głównej. Klasa głównej Aktywności (MainActivity.java) package com.example.intentjawnewywolaniedane; import import import import import import import import
android.os.Bundle; android.app.Activity; android.content.Intent; android.view.Menu; android.view.View; android.view.View.OnClickListener; android.widget.Button; android.widget.EditText;
public class MainActivity extends Activity { Button b; EditText pole; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // referencja do przycisku b = (Button) findViewById(R.id.button1); b.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { PrzeslijDane(); } } ); // Odnośnik do pola z danymi do wprowadzenia // i przekazania do drugiej Aktywności pole = (EditText) findViewById(R.id.editText1);
} protected void PrzeslijDane() { Intent intent = new Intent (this, DrugaActivity.class); // Przygotowanie danych do przekazania // Pobranie z komponentu ”pole” zawartości tekstu intent.putExtra(”przekazanedane”, pole.getText().toString()); startActivity(intent); } }
Klasa drugiej Aktywności (DrugaActivity.java) package com.example.intentjawnewywolaniedane; import import import import import
android.os.Bundle; android.app.Activity; android.content.Intent; android.view.Menu; android.widget.TextView;
public class DrugaActivity extends Activity { TextView poledanych; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_druga); poledanych = (TextView) findViewById(R.id.textView1); //Odczytanie przekazanych danych Intent intent = getIntent(); String dane = intent.getStringExtra(”przekazanedane”); // Wstawienie otrzymanych danych do pola tekstowego // ”poledanych” poledanych.setText(dane); } }
Definicja interfejsu głównej Aktywności (layout/activity_main.xml)
Definicja interfejsu drugiej Aktywności (layout/activity_druga.xml)
Rysunek 35. Interfejs Aktywności głównej i drugiej – przykład 1
Plik konfiguracyjny AndroidManifest.xml oraz (res/values/strings.xml) analogicznie do przykładu 1.
plik
zasobów
Rysunek 36. Przykładowe uruchomienie aplikacji – Przykład 2
Przykład 3. Wywołanie jawne w ramac h aplikac ji z przekazaniem danyc h i oc zekiwaniem na wynik Przykład przedstawia sposób, w jaki można przekazać sterowanie z jednej Aktywności do drugiej w ramach tej samej aplikacji, przesłać dane oraz dodatkowo ustawić Aktywność w stanie oczekiwania na wynik obliczeń drugiej Aktywności. Zdefiniowane zostały dwie Aktywności: klasa MainActivity oraz DrugaActivity dziedziczące po klasie Activity. W Aktywności głównej zdefiniowane jest pole edytowane (editText1) umożliwiające wprowadzenie przez użytkownika liczby oraz przycisk wywołujący drugą Aktywność z przekazaniem danych. W drugiej Aktywności zdefiniowano pole tekstowe (editText1), do którego użytkownik wprowadza drugą liczbę. Widoczna jest
także liczba wprowadzona wcześniej w głównej Aktywności. Po wybraniu przez użytkownika przycisku „Sumuj i zwróć wynik” obie wprowadzone liczby są sumowane, wynik zostaje wysłany (setResult), a druga Aktywność – zamykana (finish()). Jednocześnie w głównej Aktywności obsługiwany jest przesłany wynik. W przykładzie świadomie pominięto zagadnienie kontroli poprawności danych wprowadzanych przez użytkownika, aby nie komplikować rozważanego zagadnienia. Schemat postepowania: 1. Uruchomienie Aktywności głównej. 2. Wprowadzenie przez użytkownika liczby w polu EditText. 3. Obsługa przycisku „Wyślij dane i czekaj na wynik”. Pobranie liczby z pola edycyjnego oraz wstawienie (intent.putExtra) jej do obiektu intent, a następnie uruchomienie drugiej Aktywności w trybie oczekiwania na wynik (startActivityForResult). protected void WyslijDaneiCzekajNaWynik() { Intent intent = new Intent (this, DrugaActivity.class); intent.putExtra(”liczba”, pole.getText().toString()); startActivityForResult(intent, DRUGA_ACTIVITY_REQUEST_CODE); }
gdzie w klasie MainActivity znajduje się definicja: private static final int DRUGA_ACTIVITY_REQUEST_CODE = 1;
Stała o nazwie DRUGA_ACTIVITY_REQUEST_CODE jest identyfikatorem umożliwiającym przesłanie numeru kodu potwierdzającego, że dane przesyłane do głównej Aktywności z innych Aktywności są prawidłowym oczekiwanym wynikiem. Taki mechanizm powiadamiania jest wymagany, ponieważ dana Aktywność może asynchronicznie oczekiwać na dane z wielu różnych Aktywności, a otrzymywane wyniki mogą być oznaczane jako prawidłowy wynik lub błąd w obliczeniach. 4. W drugiej Aktywności (w metodzie onCreate()) z głównej Aktywności pobierane są wysłane dane o nazwie „liczba”. Intent intent = getIntent(); String dane = intent.getStringExtra(”liczba”);
5. W polu tekstowym
textView2
wypisywana jest otrzymana liczba.
tekst.setText(dane);
6. Użytkownik wprowadza drugą liczbę w polu edycyjnym (editText1). 7. Użytkownik wybiera przycisk „Sumuj i zwróć wynik”, co powoduje uruchomienie zdarzenia OnClickListener i wywołanie procedury PrzygotujWynikiWyslij(). Ta procedura skutkuje obliczeniem sumy obu liczb i przygotowaniem wyniku w obiekcie intent pod nazwą „wynikdodawania”. protected void PrzygotujWynikiWyslij() { // Pobierz liczbę otrzymaną z Aktywności głównej // i dodaj do niej liczbę podaną w aktywności drugiej skladnik2 = Integer.valueOf(pole.getText().toString()); int wynik = skladnik1 + skladnik2; String odpowiedz = String.valueOf(wynik); Intent intent = new Intent(); intent.putExtra(”wynikdodawania”, odpowiedz); setResult(RESULT_OK, intent); }
8. Wykonanie funkcji setResult uruchamia wysłanie obliczonego wyniku (obiekt intent). Dodatkowo przesyłany jest predefiniowany status wyniku – w tym przypadku RESULT_OK, co oznacza, że udało się uzyskać wynik i jest on prawidłowy.
Rysunek 37. Przykładowe uruchomienie aplikacji – Przykład 3
Klasa głównej Aktywności (MainActivity.java) package com.example.intentjawnewywolaniezwrot; import import import import import import import import import
android.os.Bundle; android.app.Activity; android.content.Intent; android.view.Menu; android.view.View; android.view.View.OnClickListener; android.widget.Button; android.widget.EditText; android.widget.TextView;
public class MainActivity extends Activity { Button b; EditText pole; TextView wynikzwrotny; // Kod wyniku drugiej Aktywności private static final int DRUGA_ACTIVITY_REQUEST_CODE = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
b = (Button) findViewById(R.id.button1); b.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { WyslijDaneiCzekajNaWynik(); } } ); pole = (EditText) findViewById(R.id.editText1); wynikzwrotny = (TextView) findViewById(R.id.textView2); } protected void WyslijDaneiCzekajNaWynik() { Intent intent = new Intent (this, DrugaActivity.class); intent.putExtra(”liczba”, pole.getText().toString()); startActivityForResult (intent, DRUGA_ACTIVITY_REQUEST_CODE); }
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if (requestCode == DRUGA_ACTIVITY_REQUEST_CODE) { // To jest odpowiedź wysłana z drugiej Aktywności if (resultCode == RESULT_OK) { // To jest odpowiedź ze statusem OK String wynik = data.getStringExtra(”wynikdodawania”); // Wypisanie wyniku wynikzwrotny.setText(wynik); } } } }
Klasa drugiej Aktywności (DrugaActivity.java) package com.example.intentjawnewywolaniezwrot;
import import import import import import import import import
android.os.Bundle; android.app.Activity; android.content.Intent; android.view.Menu; android.view.View; android.view.View.OnClickListener; android.widget.Button; android.widget.EditText; android.widget.TextView;
public class DrugaActivity extends Activity { Button b; TextView tekst; EditText pole; int skladnik1, skladnik2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_druga); tekst = (TextView) findViewById(R.id.textView2); //Odczytanie przekazanych danych Intent intent = getIntent(); String dane = intent.getStringExtra(”liczba”); // Wstawienie otrzymanych danych do pola tekstowego // ”tekst” tekst.setText(dane); skladnik1 = Integer.valueOf(dane); pole = (EditText) findViewById(R.id.editText1); b = (Button) findViewById(R.id.button1); b.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { // Przygotowanie i wysłanie wyniku PrzygotujWynikiWyslij(); // Jawne wywołania zakończenia drugiej // Aktywności finish(); } } );
} protected void PrzygotujWynikiWyslij() { // Pobierz liczbę otrzymaną z Aktywności głównej // i dodaj do niej liczbę podaną w Aktywności drugiej skladnik2 = Integer.valueOf(pole.getText().toString()); int wynik = skladnik1 + skladnik2; String odpowiedz = String.valueOf(wynik); Intent intent = new Intent(); intent.putExtra(”wynikdodawania”, odpowiedz); setResult(RESULT_OK, intent); } }
Definicja interfejsu głównej Aktywności (layout/activity_main.xml)
Definicja interfejsu drugiej Aktywności (layout/activity_druga.xml)
Rysunek 38. Interfejsy Aktywności – Przykłady 3 i 1
Plik konfiguracyjny
AndroidManifest.xml
Plik zasobów (res/values/strings.xml)
Intent-Jawne Wywołanie Zwrot Settings Aktywność główna Druga aktywność Wyślij dane i czekaj na wynik Sumuj i zwróć wynik DrugaActivity
Przykład 4. Wywołanie niejawne Aktywnośc i bez pobierania danyc h W przykładzie przedstawiony został sposób wywołania Aktywności w sposób niejawny, bez podania nazwy klasy wywoływanej Aktywności. Jak już wcześniej wspomniano, wywołanie niejawne polega na podaniu nazwy akcji do wykonania oraz danych dodatkowych. W przykładzie pokazano, jak wywołać niejawnie Aktywność (element innej aplikacji) w celu wyświetlenia zawartości strony internetowej o adresie http://www.google.pl. Jednocześnie przykład przedstawia wywołanie Aktywności (z innej aplikacji) w celu wyświetlenia listy kontaktów zapisanych na danym urządzeniu. W wywołaniu niejawnym dodatkowe dane przekazywane są w postaci uniwersalnego identyfikatora URI. Ze względu na to, że wywołanie niejawne powoduje wywołanie Aktywności innych aplikacji, w określenie, jaka aplikacja ma zostać wywołana,
zaangażowany jest system operacyjny, który wybiera odpowiednią (pasującą do akcji i danych) aplikację na podstawie definicji zawartych w ich plikach AndroidManifest.xml. W celu zrozumienia zasad wykorzystania URI w rozdziale 6 przedstawiono podstawowe informacje dotyczące reguł ich prawidłowego stosowania. Elementy interfejsu aplikacji do testowania wywołań niejawnych.
Rysunek 39. Interfejsy głównej Aktywności – Przykład 4
Rysunek 40. Interfejsy Aktywności do wyświetlania strony internetowej i kontaktów – Przykład 4
Klasa głównej Aktywności (MainActivity.java) package com.example.intentniejawne; import import import import import import import import
android.net.Uri; android.os.Bundle; android.app.Activity; android.content.Intent; android.view.Menu; android.view.View; android.view.View.OnClickListener; android.widget.Button;
public class MainActivity extends Activity { Button b, b2; @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); b = (Button) findViewById(R.id.button1); b.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { UruchomNiejawneWywolanieURL(); } } ); b2 = (Button) findViewById(R.id.button2); b2.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { UruchomNiejawneWywolanieKontakty(); } } ); } protected void UruchomNiejawneWywolanieKontakty() { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(”content://contacts/people”)); startActivity(intent); } protected void UruchomNiejawneWywolanieURL() { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(”http://www.google.com”)); startActivity(intent); } }
Definicja interfejsu głównej Aktywności (layout/activity_main.xml)
Ważnym elementem wywołania niejawnego jest programowe sprawdzenie, czy dla danego obiektu Intent istnieje dopasowanie rozumiane jako aplikacja, która ma zadeklarowaną możliwość obsługi danej akcji i danych. Poniżej przedstawiony jest przykład wywołania Intencji, dla której wynik sprawdzenia liczby aplikacji obsługujących akcję ACTION_VOICE_COMMAND z danymi content://contacts/people jest równy 0. Podany został także przykład implementacji funkcji CzyIstniejeIntent wykonującej sprawdzenia z wykorzystaniem obiektu typu PackageManager. protected void UruchomNiejawneWywolanie() { Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND, Uri.parse(”content://contacts/people”)); if (CzyIstenieIntent(intent)) { startActivity(intent); } else {
Log.d (”Test Intent”, ”Brak dopasowania - nie ma aplikacji, która jest w stanie obsłużyć dane wywołanie”); } } private boolean CzyIstniejeIntent(Intent intent) { PackageManager packageManager = getApplicationContext().getPackageManager(); List resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); return resolveInfo.size() > 0; }
Przykład 5. Wywołanie niejawne Aktywnośc i z oc zekiwaniem na dane zwrotne W przykładzie przedstawiony został sposób wywołania Aktywności w sposób niejawny z oczekiwaniem na dane zwrotne w postaci danych osoby z kontaktów. Dane osoby wybierane są z wykorzystaniem interfejsu aplikacji obsługującej kontakty. Po wybraniu pozycji z listy kontaktów następuje przekierowanie do Aktywności wywołującej kontakty i wykonywana jest operacja pobrania danych wskazanych na liście.
Rysunek 41. Interfejs Aktywności do wywołania listy kontaktów – Przykład 5
Przykładowy scenariusz działania: 1. W głównej Aktywności wybierany jest przycisk „Zapytaj o osobę”. 2. Wywoływana jest aplikacja obsługująca kontakty i wyświetlana aktualna zawartość bazy kontaktów (w przykładzie dwie osoby).
3. Po wskazaniu pierwszej osoby następuje powrót do Aktywności głównej i przetwarzane są dane osoby wybranej w kontaktach (dane zapisywane są w logu). 4. Punkty od 1 do 3 powtarzane są dla drugiej osoby z listy kontaktów. Klasa głównej Aktywności (MainActivity.java) package com.example.intentniejawnezwrot; import android.net.Uri; import android.os.Bundle; import android.provider.Contacts.People; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.app.Activity; import android.content.ContentResolver; import android.content.Intent; import android.database.Cursor; import android.util.Log; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { Button b; private static final int KONTAKTY_REQUEST_CODE = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); b = (Button) findViewById(R.id.button1); b.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { WywolajIntencje(); } } ); } protected void WywolajIntencje() { Intent intent = new Intent(Intent.ACTION_PICK, Uri.parse(”content://contacts/people”)); startActivityForResult(intent,KONTAKTY_REQUEST_CODE);
} @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == KONTAKTY_REQUEST_CODE) { // To jest odpowiedź wysłana z drugiej Aktywności if (resultCode == RESULT_OK) { // To jest odpowiedź ze statusem OK String wynik = data.toString(); Log.d(”Test Intent”, wynik); //Pobranie identyfikatora URI dla zwróconych danych Uri daneKontaktu = data.getData(); //Skierowanie do bazy kontaktów pytania //o szczegółowe dane osoby wskazanej w URI Cursor cursor = getContentResolver().query(daneKontaktu, null, null, null, null); //Pobranie pierwszego (jedynego) wyniku if (cursor.moveToFirst()) { //Pobranie nazwy osoby String name = cursor.getString(cursor.getColumnIndexOrThrow (Contacts.DISPLAY_NAME)); Log.d(”Test Intent”, name); } } } }
Zagadnienia związane z tworzeniem pytań (query) i obsługą kursorów (cursor) przedstawione są w rozdziale 10. Wykonanie przedstawionego scenariusza spowoduje utworzenie logu, co potwierdza możliwość pobierania danych z innych aplikacji (dostawców treści – 4.5) z wykorzystaniem ich interfejsów i przetwarzania ich we własnej aplikacji. 05-10 21:27:22.818: D/Test Intent(1739): Intent { dat=content://contacts/people/1 flg=0x1 (has extras) } 05-10 21:27:22.847: D/Test Intent(1739): Jan Abacki 05-10 21:27:32.427: D/Test Intent(1739): Intent { dat=content://contacts/people/2 flg=0x1 (has extras) } 05-10 21:27:32.447: D/Test Intent(1739): Tomasz Babacki
4.4. Usługi Usługi zwane także Serwisami (Services) stosowane są w przypadkach, gdy wymagane jest długotrwałe lub okresowe przetwarzanie danych bez częstego kontaktu z interfejsem użytkownika. Sterowanie Usługami realizowane jest zazwyczaj z Aktywności i do niej przekazywane są wyniki działania Usług. Należy jednak pamiętać, że Usługa nie jest osobnym wątkiem, dlatego w przypadku realizacji operacji czasochłonnych i zasobochłonnych należy jawnie uruchamiać osobne wątki, aby odciążyć główny wątek obsługujący interfejs użytkownika. Dla systemu operacyjnego Android Usługi mają wyższy priorytet niż Aktywności, dlatego w przypadku braku zasobów ich działanie jest wstrzymywane na samym końcu, gdy brak jest możliwości uruchomienia nowej aplikacji. Analogicznie do Aktywności, Usługi posiadają własne metody wywoływane w momencie przejścia do kolejnych etapów ich cyklu życia. W trakcie cyklu życia Usługi wywoływane są następujące metody: • onCreate() – wywoływana w momencie tworzenia Usługi, • onStartCommand() – wywoływana w momencie uruchomienia Usługi, • onDestroy() – wywoływana w momencie zakończenia działania Usługi. W dalszej części przedstawiony jest przykład aplikacji do testowania cyklu życia Usługi. Aplikacja zawiera definicję Aktywności głównej MainActivity oraz definicję Usługi MojSerwis. Główna Aktywność zawiera definicję dwóch przycisków umożliwiających uruchomienie [START] i wyłączenie [STOP] Usługi. Przekazywanie sterowania oraz reakcje Usługi na oba przyciski są śledzone z wykorzystaniem logu (Log.d). Klasa głównej Aktywności (MainActivity.java) package com.example.testuslugi; import import import import import import import import import import
android.os.Bundle; android.os.IBinder; android.app.Activity; android.app.Service; android.content.Intent; android.util.Log; android.view.Menu; android.view.View; android.view.View.OnClickListener; android.widget.Button;
public class MainActivity extends Activity { private Button buttonStart; private Button buttonStop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); buttonStart = (Button) findViewById(R.id.button1); buttonStop = (Button) findViewById(R.id.button2); //definicje Listenera buttonStart.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Log.d(”Serwis”,”Start”); StartSerwisu(); } } ); buttonStop.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Log.d(”Serwis”,”Stop”); StopSerwisu(); } } ); } protected void StopSerwisu() { Intent serwisIntent = new Intent (this, MojSerwis.class); stopService(serwisIntent); } protected void StartSerwisu() { Intent serwisIntent = new Intent (this, MojSerwis.class); startService(serwisIntent); } }
Klasa Usługi (MojSerwis.java) package com.example.testuslugi; import import import import
android.app.Service; android.content.Intent; android.os.IBinder; android.util.Log;
public class MojSerwis extends Service { @Override public void onCreate() { super.onCreate(); Log.d(”Serwis”,”onCreate()”); } @Override public void onDestroy() { super.onDestroy(); Log.d(”Serwis”,”onDestroy()”); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(”Serwis”,”onStartCommand()”); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { Log.d(”Serwis”,”onBind()”); return null; } }
Definicja interfejsu głównej Aktywności (layout/activity_main.xml)
Rysunek 42. Interfejs aplikacji do testowania Usług (Serwisów)
Plik konfiguracyjny
AndroidManifest.xml
Plik zasobów (res/values/strings.xml)
STOP
Przykładowy scenariusz uruchamiania i zatrzymywania działania Usługi Po uruchomieniu aplikacji trzykrotnie wybierany jest przycisk START, następnie przycisk STOP, START i STOP. Przycisk START 04-30 19:18:09.013: D/Serwis(910): Start 04-30 19:18:09.033: D/Serwis(910): onCreate() 04-30 19:18:09.033: D/Serwis(910): onStartCommand()
Przycisk START 04-30 19:18:12.493: D/Serwis(910): Start 04-30 19:18:12.503: D/Serwis(910): onStartCommand()
Przycisk START 04-30 19:18:17.443: D/Serwis(910): Start 04-30 19:18:17.483: D/Serwis(910): onStartCommand()
Przycisk STOP 04-30 19:18:19.053: D/Serwis(910): Stop 04-30 19:18:19.073: D/Serwis(910): onDestroy()
Przycisk START 04-30 19:20:04.854: D/Serwis(910): Start 04-30 19:20:04.893: D/Serwis(910): onCreate() 04-30 19:20:04.893: D/Serwis(910): onStartCommand()
Przycisk STOP 04-30 19:20:34.303: D/Serwis(910): Stop 04-30 19:20:34.343: D/Serwis(910): onDestroy()
Działanie Usługi nie wymaga, aby na urządzeniu był widoczny interfejs Aktywności, która ją wywołała. Rozwinięcie przykładu przedstawia działanie Usługi „w tle”, niezależnie od stanu Aktywności, która wywołała Usługę. Jak już wspomniano, działanie Usługi może zostać zatrzymane przez system operacyjny w momencie, gdy zabraknie zasobów do uruchomienia kolejnej aplikacji, którą wywołał użytkownik. Zmodyfikowana klasa głównej Aktywności (MainActivity.java) package com.example.testuslugi; import import import import import import import import import import
android.os.Bundle; android.os.IBinder; android.app.Activity; android.app.Service; android.content.Intent; android.util.Log; android.view.Menu; android.view.View; android.view.View.OnClickListener; android.widget.Button;
public class MainActivity extends Activity { private Button buttonStart; private Button buttonStop; @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); Log.d(”Aktywność”,”onCreate()”); setContentView(R.layout.activity_main); buttonStart = (Button) findViewById(R.id.button1); buttonStop = (Button) findViewById(R.id.button2); //definicje Listenera buttonStart.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Log.d(”Serwis”,”Start”); StartSerwisu(); } } ); buttonStop.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Log.d(”Serwis”,”Stop”); StopSerwisu(); } } ); } protected void StopSerwisu() { Intent serwisIntent = new Intent (this, MojSerwis.class); stopService(serwisIntent); } protected void StartSerwisu() { Intent serwisIntent = new Intent (this, MojSerwis.class); startService(serwisIntent); } @Override protected void onDestroy() { super.onDestroy(); Log.d(”Aktywność”,”onDestroy()”); }
@Override protected void onRestart() { Log.d(”Aktywność”,”onRestart()”); super.onRestart(); } @Override protected void onResume() { Log.d(”Aktywność”,”onResume()”); super.onResume(); } @Override protected void onStop() { Log.d(”Aktywność”,”onStop()”); super.onStop(); } }
Uruchomienie aplikacji 04-30 19:32:15.644: D/Aktywność(1039): onCreate() 04-30 19:32:15.863: D/Aktywność(1039): onResume()
Uruchomienie Usługi – przycisk START w Aktywności 04-30 19:32:24.583: D/Serwis(1039): Start 04-30 19:32:24.643: D/Serwis(1039): onCreate() 04-30 19:32:24.643: D/Serwis(1039): onStartCommand()
Wyłączenie Aktywności głównej – przycisk HOME systemu operacyjnego 04-30 19:32:31.993: D/Aktywność(1039): onStop()
Ponowne wywołanie Aktywności – kliknięcie w ikonę aplikacji 04-30 19:32:49.323: D/Aktywność(1039): onRestart() 04-30 19:32:49.323: D/Aktywność(1039): onResume()
Przycisk START w Aktywności 04-30 19:32:58.183: D/Serwis(1039): Start 04-30 19:32:58.193: D/Serwis(1039): onStartCommand()
Przycisk STOP w Aktywności 04-30 19:33:00.303: D/Serwis(1039): Stop 04-30 19:33:00.335: D/Serwis(1039): onDestroy()
W dalszej części przedstawione jest kolejne rozwinięcie aplikacji. Rozszerzenie polega na dodaniu własnego zadania wywoływanego cyklicznie (co 5s) jako przedmiotu działania Usługi. Dodana została klasa MojeZadanieCykliczne będąca rozszerzeniem klasy TimerTask.
Zmodyfikowana klasa Usługi (MojSerwis.java) package com.example.testuslugi; import java.util.Timer; import java.util.TimerTask; import import import import
android.app.Service; android.content.Intent; android.os.IBinder; android.util.Log;
public class MojSerwis extends Service { private TimerTask tt; private Timer t; private class MojeZadanieCykliczne extends TimerTask { @Override public void run() { // Zadanie cykliczne Log.d(”Serwis”,”Wykonuję Moje zadanie”); } } @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); Log.d(”Serwis”,”onCreate()”); // Utworzenie obiektu z zadaniem cyklicznym tt = new MojeZadanieCykliczne(); // Utworzenie obiektu do wywoływania zadania cyklicznego t = new Timer(); //Pierwsze uruchomienie po 10s (10000ms) //Powtarzanie zadania co 5s (5000ms) t.scheduleAtFixedRate(tt, 10000, 5000); } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.d(”Serwis”,”onDestroy()”); //Usunięcie zadania cyklicznego t.cancel(); Log.d(”Serwis”,”Usuwam zadanie cykliczne”); }
@Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub Log.d(”Serwis”,”onStartCommand()”); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub Log.d(”Serwis”,”onBind()”); return null; } }
W dalszej części zaprezentowany jest przebieg przykładowego scenariusza uruchomienia Aktywności i Usługi. Uruchomienie aplikacji: 05-01 13:53:42.862: D/Aktywność(336): onCreate() 05-01 13:53:42.973: D/Aktywność(336): onResume()
Uruchomienie Usługi – przycisk START w Aktywności: 05-01 13:54:04.463: D/Serwis(336): Start 05-01 13:54:04.473: D/Serwis(336): onCreate() 05-01 13:54:04.484: D/Serwis(336): onStartCommand() Po 10s uruchamia się pierwszy raz metoda run() MojeZadanieCykliczne: 05-01 13:54:14.493: D/Serwis(336): Wykonuję Moje zadanie
z
klasy
Po 5s kolejne uruchomienie: 05-01 13:54:19.533: D/Serwis(336): Wykonuję Moje zadanie
Po 5s kolejne uruchomienie: 05-01 13:54:24.502: D/Serwis(336): Wykonuję Moje zadanie
Po 5s kolejne uruchomienie: 05-01 13:54:29.490: D/Serwis(336): Wykonuję Moje zadanie
Wyłączenie Aktywności głównej – przycisk HOME systemu operacyjnego: 05-01 13:54:31.233: D/Aktywność(336): onStop()
Po 5s kolejne uruchomienie – Aktywność niewidoczna, ale Usługa nadal działa: 05-01 13:54:34.506: D/Serwis(336): Wykonuję Moje zadanie
Po 5s kolejne uruchomienie: 05-01 13:54:39.487: D/Serwis(336): Wykonuję Moje zadanie
Po 5s kolejne uruchomienie: 05-01 13:54:44.548: D/Serwis(336): Wykonuję Moje zadanie
Ponowne wywołanie Aktywności – kliknięcie w ikonę aplikacji: 05-01 13:54:48.063: D/Aktywność(336): onRestart() 05-01 13:54:48.063: D/Aktywność(336): onResume()
Po 5s kolejne uruchomienia …: 05-01 05-01 05-01 05-01 05-01 05-01
13:54:49.542: 13:54:54.531: 13:54:59.535: 13:55:04.534: 13:55:09.531: 13:55:14.505:
D/Serwis(336): D/Serwis(336): D/Serwis(336): D/Serwis(336): D/Serwis(336): D/Serwis(336):
Wykonuję Wykonuję Wykonuję Wykonuję Wykonuję Wykonuję
Moje Moje Moje Moje Moje Moje
zadanie zadanie zadanie zadanie zadanie zadanie
Przycisk STOP Aktywności – zakończenie działania Usługi: 05-01 13:55:14.782: D/Serwis(336): Stop 05-01 13:55:14.793: D/Serwis(336): onDestroy() 05-01 13:55:14.793: D/Serwis(336): Usuwam zadanie cykliczne
Zakończenie działania aplikacji: 05-01 13:55:31.743: D/Aktywność(336): onStop() 05-01 13:55:31.743: D/Aktywność(336): onDestroy()
4.5. Dostawcy treści Dostawcy treści są jednym z ważniejszych elementów systemu operacyjnego Android. Dostawcę treści można zdefiniować jako abstrakcyjną warstwę ułatwiającą dostęp do danych zapisanych na urządzeniu. Przykładowymi Dostawcami treści dostarczanymi razem z system Android są: • Przeglądarka – Browser, • Kontakty – Contacts, • Ustawienia – Settings, • Multimedia – MediaStore, • Dziennik połączeń – CallLog, • Kalendarz – Calendar (od wersji 4.0). Oprócz Dostawców treści zdefiniowanych przez producenta systemu Android istnieje możliwość projektowania własnych Dostawców, z których, po odpowiedniej konfiguracji, mogą korzystać inne zainstalowane na urządzeniu aplikacje. Opracowanie własnego Dostawcy treści wymaga zdefiniowania interfejsu udostępniania (a także wprowadzania i modyfikacji) danych zapisanych w zasobach Dostawcy. Zazwyczaj Dostawca treści posiada własne mechanizmy pozyskiwania danych do własnej (lokalnej) bazy – przez własny interfejs lub z wykorzystaniem Usług pobierających lub modyfikujących dane Dostawcy
w sposób cykliczny. Przykład 5 przedstawia sposób wykorzystania systemowego Dostawcy treści danych kontaktowych zapisanych w aplikacji Contacts. Przykład 6 przedstawia sposób przygotowania własnego Dostawcy treści z pełnym interfejsem obsługi danych. Przykład 6. Dostawc a treśc i i klient korzystając y z interfejsu Dostawc y W przykładzie zdefiniowano Dostawcę treści (content provider) zawierającego informacje o studentach (imię, nazwisko i średnia ocena studiów) oraz pokazano przykład osobnej aplikacji (klienta) korzystającej z treści udostępnianych przez Dostawcę z wykorzystaniem zdefiniowanego interfejsu. Dostawca treści przechowuje dane w bazie SQLite, dlatego interfejs w klasie Dostawcy zawiera definicje podstawowych operacji przeglądania, wstawiania, modyfikacji oraz usuwania danych z tabeli bazy danych. Tworzenie bazy danych opisano w rozdziale 10. Dostawca treści jest aplikacją, która nie posiada interfejsu graficznego (nie ma zdefiniowanej żadnej Aktywności). Z tego powodu po jej zainstalowaniu dodawanie i modyfikacja danych zapisanych w Dostawcy treści możliwe są wyłącznie za pośrednictwem interfejsu Dostawcy. Podstawowa klasa definiująca interfejs dostawcy dziedziczy z klasy ContentProvider. Druga aplikacja będąca przykładem klienta wykorzystującego Dostawcę treści posiada jedną Aktywność zawierającą zestaw edytowanych pól tekstowych oraz przycisków umożliwiających przeglądanie, wstawianie, modyfikację oraz usuwanie danych zapisanych w Dostawcy treści.
Rysunek 43. Interfejs Aktywności klienta Dostawcy treści – przykład 6
Przyciski: • Wstaw – wstawienie do bazy Dostawcy treści podanego w polach edycyjnych imienia, nazwiska i średniej oceny. • Wypisz – wypisanie do logu całej zawartości bazy danych.
• Jeden – wypisanie do logu danych studenta o ID podanym w polu edycyjnym, jednocześnie danymi (imię, nazwisko, ocena) uzupełniane są pozostałe pola edycyjne. • Zmień – zmiana danych studenta o podanym ID na wartości podane w polach edycyjnych. • Usuń – usunięcie danych studenta o podanym ID. Definicja Aktywności klienta: public class MainActivity extends Activity { Button b,w,j,z,u; EditText imie, nazwisko, ocena, id; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(”DT”,”Klient - start”); id = (EditText) findViewById(R.id.id); imie = (EditText) findViewById(R.id.imie); nazwisko = (EditText) findViewById(R.id.nazwisko); ocena = (EditText) findViewById(R.id.ocena); b = (Button) findViewById(R.id.wstaw); b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //Tutaj obsługa wstawiania danych do Dostawcy treści Log.d(”DT”,”Klient - wstawianie”); ContentValues dane = new ContentValues(); dane.put(”IMIE”, imie.getText().toString()); dane.put(”NAZWISKO”, nazwisko.getText().toString()); dane.put(”OCENA”, ocena.getText().toString()); Uri uri = getContentResolver(). insert(Uri.parse(”content://com.example .dostawcatrescistudenci. DostawcaTresciOStudentachS QL/studenci”), dane); Log.d(”DT”,”Klient - zwrócone URI= ” + uri.toString()); } });
w = (Button) findViewById(R.id.wypisz); w.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //Tutaj obsługa wypisywania wszystkich danych studentów Log.d(”DT”,”Klient - pobranie danych i ich wypisanie”); Cursor cursor = getContentResolver(). query(Uri.parse(”content://com.example .dostawcatrescistudenci. DostawcaTresciOStudentachSQL/studenci”), null, null, null, null); if(cursor.moveToFirst()){ do{ int Id = cursor.getInt(cursor.getColumnIndex (”_ID”)); String Imie = cursor.getString (cursor.getColumnIndex(”IMIE”)); String Nazwisko = cursor.getString (cursor.getColumnIndex(”NAZWISKO”)); String Ocena = cursor.getString (cursor.getColumnIndex(”OCENA”)); Log.d(”DT”,”Klient - [” + Integer.toString(Id) + ”] ” +Imie + ” ” + Nazwisko + ” ” + Ocena); } while(cursor.moveToNext()); } } });
j = (Button) findViewById(R.id.wybrany); j.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Tutaj obsługa wyświetlania danych studenta o podanym ID Log.d(”DT”,”Klient - pobranie danych o podanym ID”); Cursor cursor = getContentResolver(). query(Uri.parse(”content://com.example.dostawcatres DostawcaTresciOStudentachSQL/studenci/”+id.getText( null, null, null, null); if(cursor.moveToFirst()){ int _Id = cursor.getInt(cursor.getColumnIndex(”_ID”)); String _Imie =
cursor.getString(cursor.getColumnIndex(”IMIE”)); imie.setText(_Imie); // wstawianie odczytanego imienia do pola // edycyjnego String _Nazwisko = cursor.getString(cursor.getColumnIndex(”NAZWISKO”)); nazwisko.setText(_Nazwisko); // wstawianie odczytanego nazwiska do pola // edycyjnego String _Ocena = cursor.getString(cursor.getColumnIndex (”OCENA”)); ocena.setText(_Ocena); // wstawianie odczytanej oceny do pola edycyjnego Log.d(”DT”,”Klient - [” + Integer.toString(_Id) + ”] ” + _Imie + ” ” + _Nazwisko + ” ” + _Ocena); } else { Log.d(”DT”,”Klient - Brak danych dla ID=” +id.getText().toString()); } } }); z = (Button) findViewById(R.id.modyfikacja); z.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //Tutaj obsługa modyfikacji danych studenta o podanym ID Log.d(”DT”,”Klient - modyfikacja danych o podanym ID”); ContentValues dane = new ContentValues(); dane.put(”IMIE”, imie.getText().toString()); dane.put(”NAZWISKO”, nazwisko.getText().toString()); dane.put(”OCENA”, ocena.getText().toString());
int liczba = getContentResolver(). update(Uri.parse(”content://com.example.dostawcatresci DostawcaTresciOStudentachSQL/studenci/”+id.getText().t dane, null, null); Log.d(”DT”,”Klient - zmodyfikowanych rekordów ” + Integer.toString(liczba)); } }); u = (Button) findViewById(R.id.usun); u.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {
//Tutaj obsługa usuwania danych studenta o podanym ID Log.d(”DT”,”Klient - usuwanie danych o podanym ID”); int liczba = getContentResolver().delete (Uri.parse(”content://com.example.dostawcatrescistuden DostawcaTresciOStudentachSQL/studenci/”+id.getText().t null, null); Log.d(”DT”,”Klient - usuniętych rekordów ” + Integer.toString(liczba)); } }); } }
Definicja Dostawcy treści zawiera elementy związane z obsługą baz danych (zagadnienia związane z bazą danych przedstawiono w rozdziale 10). Klasa SQLiteOpenHelper została wykorzystana do utworzenia bazy danych oraz obsługi ewentualnych zmian jej wersji. Dziedziczenie po klasie ContentProvider wymaga implementacji następujących metod: • delete() – usuwanie danych, zwracana liczba usuniętych rekordów, • query() – pobieranie danych, zwracany jest obiekt typu Cursor, za którego pośrednictwem klient pobiera wybrane rekordy danych, • insert() – wstawianie danych, zwracany jest obiekt typu Uri zawierający identyfikator wstawionej pozycji danych, • update() – aktualizacja danych, zwracana jest liczba zaktualizowanych rekordów danych, • onCreate() – utworzenie Dostawcy treści, w tym stworzenie bazy danych przy pierwszym uruchomieniu. Zawartość pliku
DostawcaTresciOStudentachSQL.java: public class DostawcaTresciOStudentachSQL extends ContentProvider { //Definicja adresu URI Dostawcy treści, który będzie //wykorzystywany //przez inne aplikacje do komunikacji z tym Dostawcą treści //o studentach static final String PROVIDER_NAME = ”com.example.dostawcatrescistudenci .DostawcaTresciOStudentachSQL”; static final String URL = ”content://” + PROVIDER_NAME +
”/studenci”; static final Uri CONTENT_URI = Uri.parse(URL); //Definicja kolumn danych wykorzystywanych przez dostawcę treści static final String _ID = ”_ID”; // klucz główny static final String IMIE = ”IMIE”; static final String NAZWISKO = ”NAZWISKO”; static final String OCENA = ”OCENA”; //Definicje związane z bazą danych SQLite private SQLiteDatabase db; static final String NAZWA_BAZY_DANYCH = ”bazadostawcy.db”; static final String NAZWA_TABELI = ”STUDENCI”; static final int WERSJA_BAZY_DANYCH = 1; static final String CREATE_DB_TABLE = ” CREATE TABLE ” + NAZWA_TABELI + ” (”+ _ID +” INTEGER PRIMARY KEY AUTOINCREMENT, ” + ” ”+ IMIE +” VARCHAR(10) NOT NULL, ” + ” ”+ NAZWISKO +” VARCHAR(20) NOT NULL, ” + ” ”+ OCENA +” VARCHAR(3) NOT NULL);”; //Klasa pomocnicza umożliwiająca dopasowanie wzorców // w przekazywanych // do dostawcy URI static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { uriMatcher.addURI(PROVIDER_NAME, ”studenci”, 1); uriMatcher.addURI(PROVIDER_NAME, ”studenci/#”, 2); // w miejscu znaku ”#” może być dowolna liczba }; //Klasa pomocnicza obsługi bazy danych //Utworzenie bazy przy pierwszym uruchomieniu private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context){ super(context, NAZWA_BAZY_DANYCH, null, WERSJA_BAZY_DANYCH); Log.d(”DT”,”Dostawca DatabaseHelper - init”); } @Override public void onCreate(SQLiteDatabase db) { Log.d(”DT”,”Dostawca - Utworzenie bazy danych”);
db.execSQL(CREATE_DB_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //Implementacja obsługi zmiany wersji bazy danych } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.d(”DT”,”Dostawca - usuwanie studenta o ID=”+uri. getLastPathSegment()); int typUri = uriMatcher.match(uri); int usunietych = 0; switch (typUri) { case 2: usunietych = db.delete(NAZWA_TABELI, ”_ID = ”+uri.getLastPathSegment(), null); break; default: Log.d(”DT”,”Dostawca - modyfikacja danych - błędne URI”); } getContext().getContentResolver().notifyChange(uri, null); return usunietych; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { Log.d(”DT”,”Dostawca wywołanie - insert()”); long id = db.insert(NAZWA_TABELI, null, values); //Weryfikacja, czy rekord został wstawiony if (id > 0) { Uri _uri = ContentUris.withAppendedId(CONTENT_URI, id); getContext().getContentResolver(). notifyChange(_uri, null); return _uri; }
throw new SQLException(”Błąd wstawiania danych” + uri); } @Override public boolean onCreate() { Log.d(”DT”,”Dostawca wywołanie - onCreate()”); //Przy pierwszym uruchomieniu Dostawcy treści //należy uruchomić dostęp do bazy danych. //Jeżeli brak jest bazy danych NAZWA_BAZY_DANYCH, //to zostanie ona utworzona. Context context = getContext(); //Obsługa bazy danych (utworzenie) DatabaseHelper dbHelper = new DatabaseHelper(context); // Otwarcie bazy // Zmienna ”db” umożliwi Dostawcy treści modyfikację // zawartości bazy danych db = dbHelper.getWritableDatabase(); return (db == null)? false:true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor cursor = null; SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(NAZWA_TABELI); int typUri = uriMatcher.match(uri); switch (typUri) { case 1: Log.d(”DT”,”Dostawca - pobranie wszystkich”); cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); break; case 2: Log.d(”DT”,”Dostawca - pobranie danych dla ID= ”+uri.getLastPathSegment()); qb.appendWhere(”_ID=”+uri.getLastPathSegment()); cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); break; } cursor.setNotificationUri(getContext() .getContentResolver(), uri); return cursor; }
@Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.d(”DT”,”Dostawca - modyfikacja dla ID=”+uri.getLastPathSegment()); int typUri = uriMatcher.match(uri); int zmodyfikowanych = 0; switch (typUri) { case 2: zmodyfikowanych = db.update(NAZWA_TABELI, values, ”_ID = ”+uri.getLastPathSegment(), null); break; default: Log.d(”DT”,”Dostawca - modyfikacja danych - błędne URI”); } getContext().getContentResolver().notifyChange(uri, null); return zmodyfikowanych; } }
Plik konfiguracyjny Dostawcy treści.
AndroidManifest.xml
zawiera definicję aplikacji jako
Dla powyższych definicji klas wykonano następujący scenariusz: 1. Zainstalowanie aplikacji Dostawcy treści. 2. Zainstalowanie aplikacji klienta. 3. Uruchomienie aplikacji klienta. D/DT(24256): Klient - start
4. Wprowadzenie danych studenta (Jan Abacki, 3.5) – przycisk „ Wstaw”. D/DT(957): D/DT(957): D/DT(957): D/DT(398):
Dostawca DatabaseHelper – init Dostawca - Utworzenie bazy danych Dostawca wywołanie - insert() Klient - zwrócone URI=content://com.example.dostawcatrescistudenci. DostawcaTresciOStudentachSQL/studenci/1
5. Wprowadzenie danych studenta (Tomasz Babacki, 4.5) – przycisk „ Wstaw”. D/DT(398): D/DT(1653): D/DT(398):
Klient – wstawianie Dostawca wywołanie - insert() Klient – zwrócone URI=content://com.example.dostawcatrescistudenci. DostawcaTresciOStudentachSQL/studenci/2 6. Pobranie wszystkich danych – przycisk „ Wypisz”. D/DT(3841): Klient - pobranie danych i ich wypisanie D/DT(3957): Dostawca - pobranie wszystkich D/DT(3841): Klient - [1] Jan Abacki 3.5 D/DT(3841): Klient - [2] Tomasz Babacki 4.5
7. Wprowadzenie do pola ID wartości 1 (pobranie danych studenta o ID=1) –
przycisk „ Jeden”. D/DT(3841): Klient - pobranie danych o podanym ID D/DT(3957): Dostawca - pobranie danych dla ID=1 D/DT(3841): Klient - [1] Jan Abacki 3.5
W polach edycyjnych widoczne są dane osoby o ID=1. 8. Zmiana imienia na Grzegorz – przycisk „ Zmień”. D/DT(3841): Klient - modyfikacja danych o podanym ID D/DT(3957): Dostawca - modyfikacja dla ID=1 D/DT(3841): Klient - zmodyfikowanych rekordów 1 9. Pobranie wszystkich danych – przycisk „ Wypisz”. D/DT(3841): Klient - pobranie danych i ich wypisanie D/DT(3957): Dostawca - pobranie wszystkich D/DT(3841): Klient - [1] Grzegorz Abacki 3.5 D/DT(3841): Klient - [2] Tomasz Babacki 4.5
10. Wstawienie do pola ID wartości 1 – przycisk „ Usuń”. D/DT(3841): Klient - usuwanie danych o podanym ID D/DT(3957): Dostawca - usuwanie studenta o ID=1 D/DT(3841): Klient - usuniętych rekordów 1 11. Pobranie wszystkich danych – przycisk „ Wypisz”. D/DT(3841): Klient - pobranie danych i ich wypisanie D/DT(3957): Dostawca - pobranie wszystkich D/DT(3841): Klient - [2] Tomasz Babacki 4.5
4.6. Odbiorcy treści Komunikaty w systemie Android są jednym z zasadniczych mechanizmów (obok Intencji – patrz podrozdział 4.3) umożliwiających współdziałanie aplikacji. Komunikaty wysyłane są do systemu operacyjnego i za jego pośrednictwem – do innych aplikacji. Aplikacja uzyskuje możliwość pobierania informacji o pojawiających się komunikatach (zdarzeniach) przez odpowiednie skonfigurowanie Filtrów intencji w pliku Androidmanifest.xml (patrz rozdział 6). W systemie Android zdefiniowany został zestaw typowych komunikatów mogących wpływać na pracę aplikacji, np.: poziom naładowania baterii spadł do minimum, włączono GPS lub przyszła wiadomość SMS. Jako rozszerzenie możliwości komunikacji istnieje opcja zdefiniowania własnych komunikatów. W przykładzie 7 przedstawiono sposób zdefiniowania i wykorzystania własnego komunikatu. Przykład 10 przedstawia sposób wykorzystania komunikatów systemowych do własnej obsługi przychodzących wiadomości SMS. Przykład 7. Implementac ja odbiorc y i nadawc y komunikatów W przykładzie zdefiniowano dwie aplikacje. Pierwsza z nich jest nadawcą komunikatów. Rozesłanie komunikatu możliwe jest z wykorzystaniem przycisku lub zadania cyklicznego. Do rozsyłania komunikatów wykorzystana została metoda sendBroadcast(intent). Druga aplikacja obok głównej Aktywności posiada klasę definiującą odbiorcę komunikatów
MojOdbiorcaKomunikatow dziedziczącą po klasie BroadcastReceiver. Klasa MojOdbiorcaKomunikatow musi być zadeklarowana w pliku Androidmanifest.xml z podaniem rodzaju komunikatu, na jaki ma reagować
(Filtr intencji). W przykładzie zdefiniowano własny typ komunikatu: com.example.komunikatynadawca.MOJ_KOMUNIKAT.
Rysunek 44. Interfejsy aplikacji nadawcy i odbiorcy komunikatów – Przykład 7
Klasa głównej Aktywności nadawcy (MainActivity.java) package com.example.komunikatynadawca; import import import import import import import import import import
java.util.Timer; java.util.TimerTask; android.os.Bundle; android.app.Activity; android.content.Intent; android.util.Log; android.view.Menu; android.view.View; android.view.View.OnClickListener; android.widget.Button;
public class MainActivity extends Activity { Button b; private TimerTask tt;
private Timer t; static final String NEW_ACTION = ”com.example .komunikatynadawca.MOJ_KOMUNIKAT”; static final String WIADOMOSC = ”Treść wiadomości”; static final String WIADOMOSC_CYKLICZNA = ”Treść wiadomości cyklicznej”; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(”Komunikaty”,”Start nadawcy”); b = (Button) findViewById(R.id.button1); b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setAction(NEW_ACTION); intent.putExtra(”wiadomosc”, WIADOMOSC); sendBroadcast(intent); Log.d(”Komunikaty”,”Wysłanie wiadomości:” + WIADOMOSC); } }); // Utworzenie obiektu z zadaniem cyklicznym tt = new MojeZadanieCykliczne(); // Utworzenie obiektu do wywoływania zadania cyklicznego t = new Timer(); // Pierwsze uruchomienie po 20s (10000ms) // Powtarzanie zadania co 5s (5000ms) t.scheduleAtFixedRate(tt, 20000, 5000); } private class MojeZadanieCykliczne extends TimerTask { @Override public void run() { // Zadanie cykliczne Intent intent = new Intent(); intent.setAction(NEW_ACTION); intent.putExtra(”wiadomosc”, WIADOMOSC_CYKLICZNA); sendBroadcast(intent); Log.d(”Komunikaty”,”Wysłanie wiadomości cyklicznej:” + WIADOMOSC_CYKLICZNA); } }
}
Klasa głównej Aktywności odbiorcy (MainActivity.java) package com.example.komunikatyodbiorca; import import import import import import
android.os.Bundle; android.app.Activity; android.content.Intent; android.util.Log; android.view.Menu; android.widget.TextView;
public class MainActivity extends Activity { TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.tvKomunikat); } @Override protected void onResume() { super.onResume(); Intent intent = getIntent(); //Odczytanie przekazanych danych String dane = intent.getStringExtra(”wiadomosc”); if (dane != null) { Log.d(”Komunikaty”,”Odebrane dane w Aktywności głównej odbiorcy: [” + dane + ”]”); //Wstawienie otrzymanych danych do pola tekstowego ”tv” tv.setText(dane); } } }
Klasa odbiorcy komunikatów (MojOdbiorcaKomunikatow.java) package com.example.komunikatyodbiorca; import java.util.TimerTask; import import import import
android.content.BroadcastReceiver; android.content.Context; android.content.Intent; android.util.Log;
import android.widget.TextView; public class MojOdbiorcaKomunikatow extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d(”Komunikaty”,”Odebrany komunikat”); // Odczytanie przekazanych danych String dane = intent.getStringExtra(”wiadomosc”); Log.d(”Komunikaty”,”Odebrane dane: [” + dane + ”]”); // Przekazanie danych do głównej Aktywności Intent intent2 = new Intent(); intent2.setClassName(”com.example.komunikatyodbiorca”, ”com.example.komunikatyodbiorca.MainActivity”); intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent2.putExtra(”wiadomosc”, dane); context.startActivity(intent2); } }
Definicja interfejsu (layout/activity_main.xml)
głównej
Aktywności
nadawcy
Definicja interfejsu (layout/activity_druga.xml)
drugiej
Aktywności
odbiorcy
Plik konfiguracyjny
AndroidManifest.xml odbiorcy
W kolejnych wierszach przedstawiono przykładowy scenariusz użycia opracowanych aplikacji (zapisy z logu): Start nadawcy. 06-23 18:00:42.720: D/Komunikaty(367): Start nadawcy
Naciśnięcie przycisku „Wyślij wiadomość”. 06-23 18:00:49.011: D/Komunikaty(367): Wysłanie wiadomości: Treść wiadomości Odbiorca onReceive w klasie MojOdbiorcaKomunikatow odbiera komunikat. 06-23 18:00:49.020: D/Komunikaty(334): Odebrany komunikat 06-23 18:00:49.020: D/Komunikaty(334): Odebrane dane: [Treść wiadomości]
Klasa odbiorcy wysyła odebrane dane do swojej Aktywności głównej. 06-23 18:00:49.120: D/Komunikaty(334): Odebrane dane w Aktywności głównej odbiorcy: [Treść wiadomości]
Po 20 sekundach od uruchomieniu nadawcy uaktywnia się wywoływanie
zadania cyklicznego co 5 s. 06-23 18:01:02.780: D/Komunikaty(367): Wysłanie wiadomości cyklicznej: Treść wiadomości Cyklicznej Odbiorca onReceive w klasie MojOdbiorcaKomunikatow odbiera komunikat. 06-23 18:01:02.800: D/Komunikaty(334): Odebrany komunikat 06-23 18:01:02.811: D/Komunikaty(334): Odebrane dane: [Treść wiadomości cyklicznej]
Klasa odbiorcy wysyła odebrane dane do swojej Aktywności głównej. 06-23 18:01:02.870: D/Komunikaty(334): Odebrane dane w Aktywności głównej odbiorcy: [Treść wiadomości cyklicznej]
Po 5 sekundach od pierwszej wiadomości cyklicznej wysłanie kolejnej cyklicznej wiadomości. 06-23 18:01:07.770: D/Komunikaty(367): Wysłanie wiadomości cyklicznej: Treść wiadomości cyklicznej Odbiorca onReceive w klasie MojOdbiorcaKomunikatow odbiera komunikat. 06-23 18:01:07.790: D/Komunikaty(334): Odebrany komunikat 06-23 18:01:07.790: D/Komunikaty(334): Odebrane dane: [Treść wiadomości cyklicznej]
Klasa odbiorcy wysyła odebrane dane do swojej Aktywności głównej. 06-23 18:01:07.850: D/Komunikaty(334): Odebrane dane w Aktywności głównej odbiorcy: [Treść wiadomości cyklicznej]
Po 5 sekundach od ostatniej wiadomości cyklicznej wysłanie kolejnej wiadomości. 06-23 18:01:12.790: D/Komunikaty(367): Wysłanie wiadomości cyklicznej: Treść wiadomości cyklicznej Odbiorca onReceive w klasie MojOdbiorcaKomunikatow odbiera komunikat. 06-23 18:01:12.820: D/Komunikaty(334): Odebrany komunikat 06-23 18:01:12.820: D/Komunikaty(334): Odebrane dane: [Treść wiadomości cyklicznej]
Klasa odbiorcy wysyła odebrane dane do swojej Aktywności głównej. 06-23 18:01:12.910: D/Komunikaty(334): Odebrane dane w Aktywności głównej odbiorcy: [Treść wiadomości cyklicznej]
5. Podstawy struktury pliku AndroidManifest.xml Plik AndroidManifest.xml (plik Manifest) jest najważniejszym elementem umożliwiającym definiowanie właściwości aplikacji oraz sposobu zachowania się aplikacji w reakcji na pojawiające się komunikaty w urządzeniu z systemem Android. W poprzednich rozdziałach w prezentowanych przykładach zostały przedstawione wybrane elementy struktury pliku AndroidManifest.xml z krótkim komentarzem. Plik AndroidManifest.xml, zgodnie z opisem przedstawionym w rozdziale 1, powinien być umieszczony w głównym folderze projektu. W tym rozdziale podsumowane zostały najważniejsze elementy pliku konfiguracyjnego wykorzystane w przykładowych aplikacjach. W pliku AndroidManifest.xml umieszcza się informacje o: pakiecie zawierającym aplikację (tożsamość aplikacji); parametrach systemowych wymaganych do prawidłowego działania aplikacji; Aktywnościach i Usługach zdefiniowanych w aplikacji; uprawnieniach aplikacji do wykorzystania innych elementów dostępnych w urządzeniu; 5. dodatkowych właściwościach aplikacji związanych z odbieraniem i wysyłaniem treści; 6. wymaganej konfiguracji komponentów sprzętowych. 1. 2. 3. 4.
Plik AndroidManifest.xml (patrz rys. 8) może być edytowany na dwa sposoby. Pierwszy z nich wykorzystuje edytor okienkowy, drugi polega na bezpośredniej edycji pliku XML. W dalszej części wykorzystana zostanie
możliwość edycji pliku XML. Nagłówek pliku (manifest)
Wymagania
dotyczące właściwości
ekranu
urządzenia
(supports-
screens)
Jeżeli jest to wymagane z punktu widzenia prawidłowości działania aplikacji, należy (można) określić warunki, jakie muszą spełniać właściwości ekranu.
Właściwości aplikacji (application) Zasadniczą częścią pliku konfiguracyjnego AndroidManifest.xml są definicje związane z aplikacją oraz jej Aktywnościami i Usługami.