You are on page 1of 220

OD REDAKCJI

O
taczają nas zewsząd. Na pierwszy rzut oka niewidoczne, ukry-
te w kieszeniach mijających nas przechodniów, zamknięte
w teczkach bądź neseserach. Cały czas aktywne, tworzą gi-
gantyczną, ruchomą sieć, która oplata nasz świat. Dzięki nim, możemy
porozumiewać się niemalże bez ograniczeń i mieć nieprzerwany do-
stęp do informacji czy rozrywki... Czy to wstęp do powieści typu scien-
ce-fiction? Oczywiście, że nie: łatwo odgadnąć, iż mowa tu o nowocze-
snych urządzeniach mobilnych. Kiedy myślimy o przytoczonych po-
wyżej faktach, dochodzimy do wniosku, iż przyszło nam żyć w niesa-
mowitych czasach rozkwitu Ery Informacji.
W przeciągu ostatnich 30 lat byliśmy świadkami szalonego bo-
omu komputerów osobistych, rewolucji internetowej, zaś ostatnio
– lawinowego rozwoju urządzeń przenośnych. Jednakże kolejne
lata obserwacji kolejnych nowinek technologicznych utwierdzają
nas w przekonaniu, iż dziedzina Informatyki, a szczególnie – dział-
ka wytwarzania oprogramowania - jest ciągle tym smolistym grzę-
zawiskiem nie do pokonania, o którym pisał w 1975 roku Frede-
rick P. Brooks Jr. w swoim bestsellerze p.t. Mityczny osobomiesiąc.
Odpowiedź na pytanie 'dlaczego?' jest prosta: czynnik ludzki. Po
prostu nie da się raz zaprojektować i stworzyć miliona kopii ideal-
nego programisty – tak jak to się z robi z procesorami czy innymi
układami elektronicznymi. Kolejne pokolenia inżynierów oprogra-
mowania trzeba żmudnie i nieustannie edukować i wychowywać
– ciągle od nowa i od nowa. Jednakże, o dziwo – cały czas znajdują
się chętni do tego aby brnąć we wspomniane wcześniej grzęzawi-
sko, uczyć się i ujarzmiać niezbadane dotąd obszary informatycz-
nej wiedzy oraz praktyki. Dlaczego tak się dzieje? Może dlatego, że
programowanie daje Człowiekowi niespotykane dotąd możliwo-
ści kreowania otaczającej go rzeczywistości, co z kolei niesie nie-
samowitą satysfakcję. Może to właśnie ten entuzjazm napędza-
ny poprzez kolejne pokolenia informatyków sprawia, iż ta młoda
dziedzina wiedzy rozwija się tak dynamicznie...
Drogi Czytelniku, mamy zaszczyt oddać do Twoich rąk pierwszy,
pilotażowy numer SDJ: Biblia Programisty – Programowanie Urzą-
dzeń Mobilnych. Nie bez przyczyny tematem przewodnim tej nowej
serii zostało programowanie urządzeń mobilnych. Na kartach pi-
sma, które trzymasz w ręku znajdziesz rozwiązania szeregu pro-
blemów, z którymi tu i teraz zmagają się programiści, ujarzmiający
nowinki technologiczne ukryte w naszych kieszeniach. Prezentowa-
ny materiał został dobrany przekrojowo, tak abyś miał okazję prze-
konać się o różnorodności rozwiązań mobilnych. Wierzymy, iż nieza-
leżnie od tego czy jesteś doświadczonym programistą czy też świe-
żo upieczonym studentem informatyki, przedstawione tu infor-
macji pokażą Ci nowe kierunki i pomogą pewniej poruszać się po
technologicznych meandrach programowania nowoczesnych tele-
fonów komórkowych. Mamy również nadzieję, iż przedstawione ar-
tykuły będą dla Ciebie bodźcem do zgłębiania nowych zagadnień
i poszerzania swojej wiedzy.
Na koniec chcieliśmy podkreślić, iż Redakcji SDJ bardzo zależy
na zapoznaniu się z Twoją opinię o niniejszym numerze, zarówno
w odniesieniu do prezentowanej treści jak i formy. Jeśli zechciał-
byś podzielić się Twoimi uwagami w tym temacie, to zapraszamy
do kontaktu.

Łukasz Łopuszański
Redaktor naczelny

Rafał Kocisz
Redaktor prowadzący

4 SDJ Extra 34 Biblia


SDJ Extra 34 Biblia

SPIS TREŚCI
10 Opis DVD

PROGRAMOWANIE
WINDOWS MOBILE
12 RIL API – w sercu telefonu
Przemysław Mogaj
Radio Interface Layer (RIL) jest kluczowym elementem odpowia-
dającym za komunikacje pomiędzy systemem Windows Mobi-
le a siecią GSM. W środowisku programistów WM panuje opinia,
że API do obsługi RIL jest niejasne i trudne w użyciu. W artykule
postaramy się pokazać, jak powiada przysłowie – nie taki diabeł
straszny jak go malują.

24 RAPI – Współpraca komputerów


desktop z Windows CE i Mobile
Daniel Stoiński
Czasami podłączamy urządzenie mobilne wyposażone w Win-
dows CE/Mobile do komputera stacjonarnego, aby skopiować
pliki, połączyć się z Internetem czy wygodnie wysłać z klawiatury
kilka SMS-ów. W świecie mobilnych urządzeń Microsoftu te i wiele
innych ciekawych funkcji dostępnych jest za pośrednictwem bi-
blioteki RAPI. Nie tylko dla MS Windows.

40 Rozpocznij Przygodę z Windows Mobile


– Zobacz, jak to się robi w technologii Microsoft
Daniel Dudek
Firma Microsoft od wielu lat wspiera programistów aplikacji mo-
bilnych, dając im coraz nowsze i lepsze narzędzia. Z roku na rok
programiści mają dostęp do większej ilości zasobów urządzeń
mobilnych, a prostota, z jaką można tworzyć takowe aplikacje
w chwili obecnej, nie odstrasza już nowych adeptów tej sztuki.

46 MS SQL SERVER CE 3.5 – Jak wykorzystać


w swojej aplikacji silnik SQL Servera CE 3.5.
Łukasz Strąk
Dzięki nowej wersji SQL Servera CE możesz zarządzać dany-
mi poprzez zapytania bez konieczności instalacji dodatko-
wych usług i bez dostępu do Internetu. Ułatwia to dystrybu-
cję oprogramowania. W artykule znajdziesz informacje, które
pozwolą Ci rozpocząć pisanie aplikacji wykorzystujących SQL
Server CE.

56 Moc pod kontrolą – Efektywne zarządzanie zasi-


laniem w aplikacjach dla systemu Windows Mobile
Marian Witkowski
Odwiecznym problemem i wyzwaniem dla konstruktorów urzą-
dzeń mobilnych była możliwość ich długiej pracy z wykorzysta-

6 SDJ Extra 34 Biblia


Miesięcznik Software Developer’s Journal (12 numerów w roku)
jest wydawany przez Software Press Sp. z o.o. SK

Dyrektor wydawniczy: Anna Adamczyk


niem zasilania bateryjnego. Cel ten został w dużej mierze osią-
gnięty, ale mimo wszystko źle napisana aplikacja potrafi zmusić Redaktor naczelny: Łukasz Łopuszański
użytkownika do częstszego używania ładowarki. lukasz.lopuszanski@software.com.pl

Redaktor prowadzący: Rafał Kocisz rafal.kocisz@software.com.pl

Korekta: Tomasz Łopuszański

PROGRAMOWANIE SYMBIAN OS Projekt okładki:


Agnieszka Marchocka
Monika Grotkowska
62 Obsługa akcelerometru na platformie
Series60 – Wykorzystaj w pełni możliwości Skład i łamanie:
telefonu komórkowego! Monika Grotkowska monika.grotkowska@software.com.pl
Tomasz Kostro tomasz.kostro@software.com.pl
Rafał Kocisz, Wojciech Gasek
Akcelerometr, bądź inaczej – sensor ruchu, od stosunkowo niedaw- Dział produkcji i kolportażu: Alina Stebakow
na jest wykorzystywany jako rozszerzenie technologicznych możli- alina.stebakow@software.com.pl

wości nowoczesnych telefonów komórkowych. Artykuł pokazuje, Kierownik produkcji: Andrzej Kuca
jak oprogramować akcelerometr w aplikacjach Symbian OS, działa- andrzej.kuca@software.com.pl
jących na platformie S60, oraz jak w praktyce wykorzystać potencjał
tego urządzenia. Nakład: 6 000 egz.

Adres korespondencyjny:
Software Press Sp. z o.o. SK,
78 SDL na Symbianie – Cztery porty ul. Bokserska 1, 02-682 Warszawa, Polska
Bartosz Taudul tel. +48 22 427 36 91, fax +48 22 224 24 59
www.sdjournal.org cooperation@software.com.pl
W jaki sposób można wykorzystać jedną z najlepszych i naj-
szerzej wykorzystywanych bibliotek ułatwiających tworzenie
gier? Czy jest dostępna jej implementacja na Symbiana? Na
co należy zwrócić szczególną uwagę podczas jej użytkowa- Dział reklamy: adv@software.com.pl
nia? Odpowiedzi na wszystkie te pytania (i nie tylko) znajdu-
ją się w artykule. Obsługa prenumeraty: EuroPress Polska
software@europress.pl

Dołączoną do magazynu płytę DVD przetestowano programem


AntiVirenKit firmy G DATA Software Sp. z o.o.

Redakcja dokłada wszelkich starań, by publikowane w piśmie i na

PROGRAMOWANIE ANDROID towarzyszących mu nośnikach informacje i programy były poprawne,


jednakże nie bierze odpowiedzialności za efekty wykorzystania ich;
nie gwarantuje także poprawnego działania programów shareware,
90 Android Market bliżej dewelopera – Stworze- freeware i public domain.
nie aplikacji to dopiero początek. Dowiedz się, Uszkodzone podczas wysyłki płyty wymienia redakcja.
jak ją sprzedać! Wszystkie znaki firmowe zawarte w piśmie są własności
Adam Skrzyszewski odpowiednich firm.
Głównym zmartwieniem deweloperów planujących sprzedaż Zostały użyte wyłącznie w celach informacyjnych.
swoich programów jest dotarcie do odpowiedniej grupy od-
Redakcja używa systemu automatycznego składu
biorców. Android Market, jako oficjalna platforma dystrybucyj-
na, zakłada nieograniczony dostęp do wszystkich posiadaczy
urządzeń z oprogramowaniem Google. Czy zastanawiałeś się, Osoby zainteresowane współpracą prosimy o kontakt:
jak wykorzystać ten ogromny potencjał? cooperation@software.com.pl

Druk: ArtDruk www.artdruk.com


98 Android na przykładzie: geotagging zdjęć
– Praktyczne wykorzystanie najciekawszych funk-
Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela
cji mobilnej platformy firmy Google pomocy technicznej w instalowaniu i użytkowaniu programów
Piotr Buła zamieszczonych na płycie DVD dostarczonej razem z pismem.
Android to nowa, otwarta platforma mobilna od Google.
Sprzedaż aktualnych lub archiwalnych numerów pisma po innej
W artykule przedstawiono ciekawsze jej możliwości, na przy- cenie niż wydrukowana na okładce – bez zgody wydawcy – jest
kładzie aplikacji umożliwiającej geotagging zdjęć wykona- działaniem na jego szkodę i skutkuje odpowiedzialnością sądową.
nych za pomocą telefonu komórkowego opartego na tej plat-
formie.

www.sdjournal.org 7
SDJ Extra 34 Biblia

118 Google Android – Programowanie z wykorzy-


staniem Google Maps
PROGRAMOWANIE JAVAME
Igor Kruk 148 JME na przykładzie – Przelicznik walut na
W artykule „JME – MIDlet – Przelicznik walut na podstawie kur- podstawie kursów NBP
sów NBP” opisuje architekturę JME oraz prezentuje sposób, w ja- Igor Kruk
ki można rozpocząć przygodę z programowaniem z wykorzysta- Jeszcze kilka lat temu obsługa technologii Java była informacją,
niem tej technologii. Artykuł poświęcony jest platformie Android, która miała zachęcić do zakupu telefonu komórkowego. Przez
na której można tworzyć zaawansowane aplikacje m.in. z wyko- tych kilka lat nastąpił znaczny rozwój „komórek” i choć obecnie
rzystaniem Google Maps. większość z nich posiada własny system operacyjny i nie służą już
tylko do wykonywania rozmów i przesyłania wiadomości, to tech-
nologia JME jest nadal popularna.
124 Google Android
– Programowanie interfejsu użytkownika pod 154 Klasyczna platformówka na komórkę – Pisze-
Android OS my grę bazującą na platformie Java Micro Edition
Tomasz Milczarek Cezariusz Klonkowski
Artykuł jest wprowadzeniem do programowania interfejsu użyt- Tworzenie każdej gry, nawet najprostszej, łączy w sobie wiele wy-
kownika (UI – User Interface) na platformie Google Android. Oma- zwań, z którymi musi uporać się programista. Tworzenie aplika-
wia podstawowe komponenty interfejsu i sposoby rozmieszcza- cji dla platformy mobilnej niesie dodatkowe problemy. Jednak sa-
nia ich na ekranie. Przedstawia również tworzenie formularzy, tysfakcja z napisania własnej, nawet bardzo prostej gry na telefon
przenoszenie danych między formularzami oraz interakcję mię- komórkowy, jest bezcenna! Jeśli chciałbyś poczuć taką satysfak-
dzy różnymi ekranami użytkownika. cję, to przeczytaj ten artykuł.

168 Testowanie aplikacji na platformie JME


– Zbieranie informacji debugowych w ograniczo-
PROGRAMOWANIE IPHONE OS nym środowisku uruchomieniowym
Marcin Maciejewski
132 Poznajemy iPhone SDK – Pierwsze kroki
Tomasz Dubki
Nikt nie zaprzeczy, że jednym z najpopularniejszych urządzeń mo-
bilnych ostatnich 12 miesięcy jest iPhone. Doskonały wygląd ze-
wnętrzny, przejrzysty graficzny interfejs użytkownika oraz bogata
funkcjonalność to tylko niektóre jego cechy. W niniejszym artykule
przedstawimy to urządzenie z punktu widzenia programisty. ROZWIĄZANIA MOBILNE
172 Lotus Notes Traveler
Andrzej Olsztyński
140 Objective-C kontra Java i C++ – Wprowadze- W tym roku, zgodnie z danymi IBM, po raz pierwszy w historii,
nie do języka więcej ludzi na świecie będzie miało telefon komórkowy niż sta-
Rafał Kocisz cjonarny. Wiele produktów IBM jest dostępnych na platformach
Java i C++ panują niepodzielnie w działce technologii mobilnych. Ję- mobilnych. Ostatnim ważnym wydarzeniem w tym zakresie by-
zyk Objective-C jest stosunkowo nowym graczem na tym rynku, stoi ło wprowadzenie oprogramowania Lotus Notes Domino, czyli pa-
jednak za nim potężna marketingowa siła platformy Apple iPhone/ kietu IBM do pracy grupowej na iPhone.
iTouch. Niniejszy artykuł zawiera szybkie wprowadzenie do Objecti-
ve-C oraz porównanie jego możliwoci z językami Java i C++.

8 SDJ Extra 34 Biblia


178 Stary software – nowa platforma zanie dające ogromne możliwości, dlatego warto się mu bli-
Kamil Kowalski, Artur Chruściel żej przyjrzeć.
W jednym z numerów SDJ (2/2009) przybliżyliśmy profil prac ze-
społu zajmującego się rozwojem oprogramowania dla radiote-
lefonów systemu TETRA w krakowskim centrum Motoroli. Dziś
przyjrzymy się problemom, przed którymi stanęliśmy w ostatnich
latach w związku z migracją na nowe platformy sprzętowe.

FELIETONY
182 Oracle SPATIAL – Opis podstawowych funk- 214 Raport większości
cjonalności opcji Oracle SPATIAL dedykowanej Arkadiusz Merta
dla systemów GIS Technologie mobilne otwierają nowe możliwości – być może jesz-
Tomasz Murtaś cze bardziej rewolucyjne niż zjawisko Internetu. W odróżnieniu
Poprzez opcję SPATIAL baza danych zyskuje możliwość zarządza- od Sieci – tort jest dzielony na innych zasadach, i to przez znacz-
nia danymi o charakterze przestrzennym. To zupełnie nowy wy- nie większą ilość graczy.
miar pozwalający jeszcze precyzyjniej odwzorowywać rzeczywi-
stość, modelować dane i analizować zależności. 217 Ruch jest wszystkim [cel niczym]
Arkadiusz Merta
Co napędza rozwój technologi mobilnych? Czy to nasze potrzeby,
186 Wzorce projektowe w Java Card czy potrzeba zwiększenia naszej efektywności.
Leszek Siwik, Krzysztof Lewandowski, Adam Woś
Tworzenie oprogramowania wymaga starannego projektu i du-
żej dokładności. Platforma Java Card do tej pory pozostawała
swego rodzaju skansenem, gdzie szeroko akceptowany był kod,
który w innych obszarach byłby absolutnie nieakceptowalny.
Z pewnością warto więc poświęcić czas na zapoznanie się z do-
brymi praktykami projektowania i programowania

192 Open GL ES 1.x – Fakty i mity


Krystian Kostecki
Programowanie aplikacji na urządzenia przenośne jest fascynu-
jącym zajęciem. Dodanie im trzeciego wymiaru sprawia jeszcze
większą satysfakcję. Czy tworzenie efektownej, ale i wydajnej gra-
fiki na te malutkie urządzenia nie jest abstrakcją lub mitem? Jak
wykorzystać moc krzemu, który mieści się w dłoni? Czy można
wierzyć standardom? Oceńcie sami.

206 Flash Lite


Grzegorz Trubiłowicz, Mateusz Pietrek, Karol Bednarz
Flash Lite pomimo kilku lat na rynkach całego świata nadal
pozostaje nieodkrytym środowiskiem. Jest to jednak rozwią-

www.sdjournal.org 9
Opis CD

Kursy mularzy, przenoszenie danych między formularzami oraz inte-


rakcję między różnymi ekranami użytkownika
Google Android charakteryzuje się bogatym zestawem kom-
ponentów pozwalającym na realizację nawet najbardziej skom-
plikowanych interfejsów. W przypadku developerów niezbyt za-
WINDOWS MOBILE dowolonych ze standardowej biblioteki, zawsze pozostaje moż-
Kurs podstaw tworzenia aplikacji na Windows Mobile (kurs w liwość rozszerzania istniejących już komponentów.
języku angielskim). Szkolenie zostało podzielone na 7 działów: Z racji tego, że Android tworzony jest przez firmę Google, ist-
nieje łatwy dostęp do takich komponentów jak MapView czy
• wprowadzenie WebView, umożliwiających korzystanie w tworzonych aplika-
• emulatory cjach z Google Maps oraz pozwalających na łatwe przeglądanie
• podstawy programowania aplikacji okienkowych zawartości Internetu. To znacznie poszerza krąg pomysłów na
• zaawansowane aplikacje Windows Forms aplikacje, już na starcie pozwala uczynić je bardziej ciekawymi
• wykorzystanie baz danych i konkurencyjnymi.
• bezpieczeństwo Można sądzić, że powyższe zalety (prostota tworzenia inter-
• programowanie mobilnych aplikacji internetowych fejsów, duży wybór komponentów, łatwe wykorzystanie Google
Maps i Internetu) spowodują, że Android OS umocni swoją po-
W każdym z działów możemy wybrać jaki sposób podania zycję w świecie platform programistycznych na urządzenia mo-
tych samych zagadnień jest dla nas najwygodniejszy – doku- bilne.
ment, laboratoria hands-on-lab, webcast z prezentacją czy scre- Dzięki temu tutorialowi dowiesz się, jak poprawnie rozmie-
encast z kodem. ścić komponenty komponenty użytkownika na ekranie, jak
Mobile development is growing fast, and Windows Mobile is tworzyć poszczególne widgety, jak ekrany użytkownika współ-
at the forefront with over 18 million phones shipped last year pracują ze sobą.
and many more cutting-edge devices on the way. Visual Stu- Pierwsza część screencasta stanowi omówienie idei stojącej
dio developers have tremendous opportunities in this space. za powstaniem Google Android OS, podstawowych zasad, któ-
Why? Developing for a Windows Mobile phone leverages your re przyjęli twórcy tego systemu oraz przedstawiona jest jego ar-
existing coding experience and takes it to new heights. In this chitektura.
track, we’ll go through the fundamentals of building mobile ap- Omówione zostały typowe kontrolki i ich zastosowanie.
plications. You’ll learn how to set up Visual Studio with the la- Druga część screencasta przedstawia przykład tworzonej apli-
test SDK and device emulators, and you’ll see how to build, de- kacji – realizującej wykresy biorytmów. Tworzenie aplikacji zo-
ploy and debug applications. We’ll also explore AJAX capabili- stało pokazane w następujących krokach:
ties that offer the richness of the desktop for mobile devices.
• tworzenie nowego projektu w środowisku Eclipse ADT
Level 1: Mobile Development Introduction (Android Development Toolkit);
• konfiguracja projektu;
Level 2: Device Emulators • tworzenie podstawowej klasy akcji;
• tworzenie layoutu;
Level 3: Mobile Windows Forms Development • definiowanie widoków w plikach XML;
• oprogramowywanie kontrolek;
Level 4: Advanced Mobile Windows Forms • oprogramowywanie zdarzeń w Google Android;
Development • interakcja pomiędzy kontrolkami a klasą typu Activity;
• stworzenie przejścia między dwoma ekranami użytkownika;
Level 5: SQL Server CE Introduction • przesyłanie danych między ekranami;
• stworzenie własnego widoku.
Level 6: Security and Deployment

Level 7: Mobile Web Development Narzędzia


Google Android – Programowanie Windows Mobile 6.5 Developer Toolkit
interfejsu użytkownika pod Wydana na początku czerwca 2009 dokumentacja, przykładowy
Android OS kod, nagłówki, biblioteki oraz emulatory do najnowszej wersji syste-
Screencast jest wprowadzeniem do programowania interfejsu mu Windows Mobile 6.5.
użytkownika (UI – User Interface) na platformie Google Andro- Dostępne są dwie wersje – w zależności od tego czy interesu-
id. Omawia podstawowe komponenty interfejsu i sposoby roz- je nas pisanie aplikacji dla systemu Windows Mobile Professional
mieszczania ich na ekranie. Przedstawia również tworzenie for- czy Standard.

10 SDJ Extra 34 Biblia


Opis CD

Toolkit wymaga instalacji Windows Mobile 6 SDK (do pobrania przejazdu, informuje uczestników spotkania w razie ewentualnego
bezpłatnie ze stron Microsoftu). spóźnienia. Wraz z kodem aplikacji czytelnicy otrzymują zestaw do-
kumentów opisujących krok po kroku poszczególne etapy tworze-

Aplikacje nia danej aplikacji. Od wprowadzenia, przez dobór architektury aż


po dodawanie do projektu kolejnych elementów kodu. Czytelnicy
mogą sami stworzyć aplikacje na podstawie dokumentów lub uczyć
Consumer Solution Accelerator się przez czytanie gotowego kodu.
Consumer Solution Accelerator jest wzorcową aplikacją dla Win-
dows Mobile napisaną w językach C# i C++, w oparciu o .NET Materiały dodatkowe do artykułów
Compact Framework 3.5.
Aplikacja ma na celu ułatwić użytkownikowi dotarcie na spotka-
nie – prezentuje mapy wykorzystując nawigację GPS, szacuje czas

Redakcja nie udziela pomocy technicznej


w instalowaniu i użytkowaniu programów
zamieszczonych na płytach CD-ROM
dostarczonych razem z pismem.

Jeśli nie możesz


odczytać zawartości
płyty CD, a nie jest ona
uszkodzona mechanicznie,
sprawdź ją na co najmniej
dwóch napędach CD.
W razie problemów z płytą,
prosimy pisać pod adres:
cd@software.com.pl

www.sdjournal.org 11
Programowanie Windows Mobile

RIL API
w sercu telefonu

Radio Interface Layer (RIL) jest kluczowym elementem odpowiadającym


za komunikacje pomiędzy systemem Windows Mobile a siecią GSM.
W środowisku programistów WM panuje opinia, że API do obsługi RIL
jest niejasne i trudne w użyciu. W tym artykule postaram się pokazać, że
– jak powiada przysłowie – nie taki diabeł straszny jak go malują.
Mówiąc o RIL, mamy na myśli pewne war-
Dowiesz się: Powinieneś wiedzieć: stwy abstrakcji, które oddzielają sprzęt od sys-
• Czym jest Radio Interface Layer; • Jak programować w języku C++; temu – myślę, że jak najbardziej będzie tu na
• Jak używać RIL API; • Podstawy WINAPI. miejscu porównanie do sieciowego mode-
• W jaki sposób stworzyć serwis systemowy lu OSI – zostało to pokazane na Rysunku 1.
Windows Mobile; Na samym szczycie hierarchii mamy aplika-
• W jaki sposób korzystać z rejestrów systemu cje wchodzące w bezpośrednią komunikację
Windows Mobile. z użytkownikiem i dostarczające mu funk-
cjonalność związaną z telefoniczną stroną
urządzeń z WM (zauważ, że pod pojęciem
tykule, a ponieważ w moim odczuciu naj- OEM nie kryją się tylko aplikacje dostarcza-
lepiej uczyć się na bazie przykładów, dla- ne przez producenta telefonu, ale również
Poziom trudności tego też, aby zobrazować podstawowe me- twoje). Szczebel niżej kryje się tzw. RIL Proxy
chanizmy, pokażę Ci drogi czytelniku, jak – to właśnie z nim będziesz miał do czynie-
stworzyć prostą aplikację, która pozwo- nia, tworząc aplikacje oparte o Radio Interface
li na blokowanie połączeń telefonicznych, Layer. Wywołując funkcje wchodzące w skład

P
amiętam jakby to było wczoraj: do- a dodatkowo wyśle informacyjnego SMS'a RIL API, tak naprawdę wysyłasz zapytania
stałem do analizy kod źródłowy, na- do osoby, która próbowała się z Tobą skon- do Proxy, w odpowiedzi otrzymując wyniki.
leżało stwierdzić, czy flow progra- taktować. RIL Proxy tłumaczy twoje zapytania na od-
mu jest zgodny z przyjętą koncepcją. Kod powiednie polecenia typu DeviceIOControl, i
ani długi, ani krótki, zadanie wydawało się Ale po kolei... wysyła je do sterownika modemu (radia). Na-
więc proste, i uznałem, że to nagroda za Wiem, że najchętniej uruchomiłbyś IDE i za- stępnie te polecenia tłumaczone są na komen-
wcześniejszy okres wytężonej pracy... Kilka czął pisać kod, na początek warto jednak po- dy AT, które wędrują do urządzenia, by tam
godzin i kaw, później niebezpiecznie blisko wiedzieć słów kilka o tym, czym jest RIL. zostać odpowiednio przetworzone.
zbliżyłem się do granicy załamania nerwo- Pomyśl teraz chwilę o urządzeniach z syste- Taka architektura ma wiele zalet. Po
wego, a moje myśli skakały pomiędzy pla- mem Windows Mobile – można wymienić pierwsze wymaga na producentach sprzę-
nem zamachu na osobę, która wymyśliła, dwie ich główne linie rozwojowe. Po pierw- tu radiowego stworzenie sterowników w ta-
że wszystko dzieje się asynchronicznie, a sze są to urządzenia klasy PDA (ostatnimi ki sposób, aby były kompatybilne z RIL API-
rozważaniami na temat sposobów na skró- czasy na wymarciu), z drugiej zaś strony urzą- (jak już wspomniałem, cała komunikacja w
cenie swoich cierpień. Tak właśnie wyglą- dzenia, które można sklasyfikować jako tzw. systemie WM jest oparta właśnie o RIL'a).
dał mój pierwszy kontakt z RIL'em. Nieste- Smartphony. Jaka jest pomiędzy nimi różni- To z kolei pozwala producentom telefonów
ty, a może właśnie stety od tamtego czasu ca? Oczywiście te drugie pozwalają na szero- na wybór szerokiej gamy podzespołów, bez
nader często przyszło mi pracować w opar- ko rozumianą komunikację pomiędzy urzą- przejmowania się narzutem czasu niezbęd-
ciu o wspomniane API. Swoimi doświad- dzeniem a siecią GSM/UMTS. I tu na scenę nym do wypuszczenia produktu na rynek.
czeniami postaram się podzielić w tym ar- wchodzi właśnie RIL (Radio Interface Layer). Inną zaletą jest fakt asynchroniczności do-

RIL: Szybki start


Aby tworzyć aplikacje w oparciu o RIL API, musisz mieć dostęp do symboli znajdujących się w pliku RIL.DLL na urządzeniu. Można go uzyskać w
sposób dwojaki. Pierwszą drogą jest użycie funkcji LoadLibrary, a następnie zmapowanie potrzebnych symboli. Innym rozwiązaniem jest dołą-
czenie do projektu statycznej biblioteki RIL.LIB oraz załączenie pliku nagłówkowego RIL.H. Ponieważ pliki te nie wchodzą w skład Windows Mo-
bile SDK, konieczne jest zdobycie ich z innych źródeł. Microsoft dołącza je do OEM Adaptation Kit(OAK) – zestawu bibliotek i plików nagłów-
kowych przeznaczonych do współpracy z pakietem Platform Builder. Niezbędne pliki można również pobrać z tej strony: http://www.xs4all.nl/
~itsme/projects/xda/ril.html. Przed kompilacją musisz pamiętać, aby do dodatkowych bibliotek linkera dodać bibliotekę RIL.LIB.

12 SDJ Extra 34 Biblia


RIL API – w sercu telefonu

stępu do sprzętu – pozwala to na uniknię- nej strony systemu. Pozwala to zaoszczę- nego edytowania rejestrów (dlaczego wła-
cie sytuacji, w której jedna aplikacja zawłasz- dzić czas, zostaje bowiem zdjęta z progra- śnie rejestrów, niech pozostanie jeszcze przez
cza modem, odcinając dostęp do niego pozo- misty konieczność przyswajania wielu róż- chwilę tajemnicą), stworzyłem bardzo pro-
stałej części systemu. niących się architekturami metod dostępu stą aplikację, której ekran można zobaczyć na
Do 2004 roku RIL API przeznaczone by- do poszczególnych funkcji. Rysunku 2 (kod aplikacji znajduje się na pły-
ło do wewnętrznego użytku Microsoftu, Mimo że RIL API zostało upublicznio- cie DVD). Aplikacja ta daleka jest od dosko-
a nawet po tej dacie Microsoft zalecał, aby ne, to jednak pliki nagłówkowe nie są nie- nałości i przewidziana jest do użytku na plat-
używać API takich jak TAPI, SMS API, SIM stety dołączone do Windows Mobile SDK. formie WM Professional, wydaje mi się jed-
API... Ostatnio jednak certyfikowany tre- Aby dowiedzieć się skąd je zdobyć oraz jak nak, że stanowić będzie dobry starter do dal-
ner Microsoftu stwierdził, że to podejście zacząć pracę, spójrz proszę do ramki RIL: szych działań.
jest już nieaktualne, i gigant z Redmond po- Szybki Start. We wstępie określiliśmy mniej więcej, co
prosił swoich trenerów, aby spopularyzowa- będzie robić nasza aplikacja. Pora zastanowić
li bezpośrednie korzystanie z Radio Interfa- Najtrudniejszy pierwszy krok się teraz nad znalezieniem odpowiedzi na py-
ce Layer. Jakie może to Tobie, jako programi- Spójrzmy na aplikację okiem przysłowiowego tanie jak? – na to pytanie odpowiadać będzie-
ście, przynieść korzyści? Po pierwsze – może Kowalskiego. Przeciętny użytkownik nie jest my krok po kroku, w myśl zasady po nitce do
się to przyczynić do wzrostu wydajności Two- świadom tego, jak wiele skomplikowanych kłębka.
jej aplikacji(wszystkie wspomniane powyżej działań podejmowanych jest za tzw. fronten-
API są tylko wrapperami na RIL). dem – dla niego aplikacja jest tożsama z tym, Rejestry systemowe
Po drugie – wydaje mi się, że każdy, kto co może zobaczyć na wyświetlaczu. W tym Rozpocznijmy od znalezienia sposobu na za-
był zmuszony do pracy z chociażby z TAPI, artykule skupię się jednak na tym, co się dzie- pisywanie konfiguracji naszej aplikacji. Przez
doceni prostotę RIL. Kolejnym argumen- je w tle – programowanie interfejsu użytkow- konfigurację będziemy tu rozumieć trzy ro-
tem jest to, że przy użyciu jednego API jest nika pozostawiając innym autorom. Aby jed- dzaje informacji. Po pierwsze – czy nasza
się w stanie kontrolować całość telefonicz- nak nie stawiać Cię przed koniecznością ręcz- aplikacja powinna być aktualnie aktywna,
czy nie? Po drugie – czy powinniśmy odrzu-

��� ��� ��������� ���������


�����

��������� ��������� ��������� ���������

���������
�����������������

�������������

��������������������������
�������������������������

�������������

Rysunek 1. Schemat architektury RIL Rysunek 2. Przykładowy interfejs użytkownika

Rejestry – Typy wartości


• REG _ SZ – łańcuch tekstowy zakończony znakiem końca łańcucha( /0);
• REG _ EXPAND _ SZ – łańcuch tekstowy zakończony znakiem końca łańcucha, zawierający w sobie zmienne środowiskowe(np. %PATH%);
• REG _ MULTI _ SZ – seria łańcuchów tekstowych oddzielonych znakami końca łańcucha i zawierająca, zakończona podwójnym znakiem
końca łańcucha;
• REG _ DWORD – 4-bajtowa wartość binarna;
• REG _ BINARY – dowolna wartość binarna;
• REG _ DWORD _ BIG _ ENDIAN – wartość typu DWORD przechowywana w formacie big endian;
• REG _ DWORD _ LITTLE _ ENDIAN – to samo co REG_DWORD;
• REG _ LINK – łącze symboliczne;
• REG _ NONE – wartość nieokreślona;
• REG _ RESOURCE _ LIST – lista zasobów sterownika.

www.sdjournal.org 13
Programowanie Windows Mobile

cać wszystkie przychodzące połączenia? Po mat XML, jednak z powodu braku natywne- Pierwszy przechowuje konfigurację sprzę-
trzecie – jeśli nie mamy odrzucać wszystkich go wsparcia dla tego formatu, ta droga wydaje tową oraz ustawienia aplikacji, drugi – jak
połączeń, to czy są takie numery, które po- się zbyt pracochłonna. wskazuje nazwa – przechowuje dane spe-
winniśmy blokować? W ramach konfiguracji Na szczęście system Windows Mobile- cyficzne dla użytkownika , a ostatni – in-
musimy również uwzględnić treść wiadomo- (podobnie jak i inne systemy z rodziny Win- formacje na temat skojarzeń plików i obiek-
ści SMS, która zostanie przesłana do odrzuco- dows) dostarcza bardzo przyzwoity system tów OLE.
nego numeru, jak również liczbę numerów, przechowywania konfiguracji – są to reje- Ważną zaletą rejestrów jest to, że ich za-
które będziemy blokować(tak dla ułatwienia stry systemowe. Rejestry można traktować wartość zostaje zachowana po restarcie urzą-
sobie życia). jako pewien rodzaj bazy danych, zawierają- dzenia. Oczywiście nie należy umieszczać
Zastanówmy się, w jaki sposób taką konfi- cej zbiór kluczy i wartości, przy czym klucze wartości gdziekolwiek – wprowadziłoby to
gurację możemy przechowywać? Pierwsze in- mogą zawierać w sobie zarówno inne klucze, tylko bałagan i utrudniło ewentualne ana-
stynktownie nasuwające się rozwiązanie to jak i wartości. W ten sposób tworzy się drze- lizy. Zwyczajowo przyjęło się umieszczać
zapisywanie jej w pliku. Historia zapisywa- wiasta struktura, która w swoich liściach za- konfigurację aplikacji w następującej ścież-
nia konfiguracji w plikach ma długą trady- wiera konkretne wartości, a klucze stanowią ce rejestru: HKEY_LOCAL_MACHINE\
cję. Kiedyś aplikacje z PC-towych wersji Win- ich gałęzie. Software\{Nazwa firmy}\{Nazwa aplika-
dowsa używały do tego celu plików INI. Oso- Od korzenia tego drzewa odchodzą trzy cji}. Tak też uczynimy i my. Jako nazwę fir-
biście uważam, że jeśli chodzi o konfigurację główne pnie: HKEY_LOCAL_MACHINE, HKEY_ my przyjmiemy SDJ, a jako nazwę aplikacji
opartą o pliki – doskonale sprawdza się for- CURRENT_USER, oraz HKEY_CLASSES_ROOT. CallRejecter.
Rejestry mogą przechowywać wartości
Listing 1. RegistryHelper.h różnego typu, od wartości liczbowych ty-
pu DWORD, przez łańcuchy tekstowe, aż
#ifndef _REGISTRYHELPER_H_ po wartości binarnych(uprasza się jednak,
#define _REGISTRYHELPER_H_ aby nie próbować tam wpychać obrazków
czy innych plików binarnych). Lista możli-
#define MAX_BLOCKED_NUM 40 wych typów wartości została przedstawio-
#define MAX_TEXT_LENGTH 256 na w ramce Rejestry – typy wartości. Należy
#define MESSAGE_BUFFER_SIZE 161 w tym miejscu jeszcze wspomnieć o kilku
#define SOFTWARE_KEY L"\\Software\\SDJ\\CallRejecter" zasadach, którymi powinno się kierować,
#define MESSAGE_VALUE L"Message" dodając do rejestru własne klucze i warto-
#define REJECT_ALL_CALLS_VALUE L"RejectAllCalls" ści (ma to znaczący wpływ na wydajność
#define BLOCKED_LIST_VALUE L"BlockedList" dostępu do rejestrów):
#define ACTIVATE_SERVICE_VALUE L"ActivateService"
• należy sprawić, by struktura zagnieżdżo-
typedef struct callrejectersettings_tag{ nych kluczy była tak płytka, jak to tylko
DWORD dwActivateService; możliwe;
DWORD dwBlockAll; • zamiast zagnieżdżonych kluczy, jeśli
DWORD dwBlockedCount; to tylko możliwe, należy używać war-
TCHAR tcMessage[MESSAGE_BUFFER_SIZE]; tości;
TCHAR tcBlockedList[MAX_BLOCKED_NUM][MAX_TEXT_LENGTH]; • jedna wartość powinna przechowy-
} CALLREJECTERSETTINGS, *LPCALLREJECTERSETTINGS; wać tak wiele informacji, jak to tyl-
ko możliwe(np. jeśli będziemy posia-
VOID ConvertToStringsArray(TCHAR lpszOutput[][MAX_TEXT_LENGTH],LPTSTR dać wartości dotyczące czasu i daty –
lpszInput,LPDWORD lpdwCount); warto rozważyć połączenie ich w jed-
VOID ConvertToRegMultiSz(LPTSTR lpszOutput,TCHAR lpszInput[][MAX_TEXT_LENGTH],DWORD ną wartość.
nCount);
DWORD CalculateRegMultiSzSize(TCHAR lpszInput[][MAX_TEXT_LENGTH], DWORD nCount); Samo dostarczenie odpowiednio sformato-
wanej struktury, pozwalającej na grupowa-
BOOL SetDwordValue(DWORD dwValue,LPTSTR lpszKey, LPTSTR lpszValueId); nie różnych informacji, na niewiele by się
BOOL SetSzValue(TCHAR lpszValue[MESSAGE_BUFFER_SIZE],LPTSTR lpszKey, LPTSTR zdało, gdyby nie funkcje pozwalające opero-
lpszValueId); wać na rejestrach. Do podstawowych operacji
BOOL SetMultiSzValue(TCHAR lpszValue[][MAX_TEXT_LENGTH],DWORD nCount,LPTSTR wystarczy używać czterech podstawowych
lpszKey,LPTSTR lpszValueId); funkcji: RegCreateKeyEx , RegQueryValueEx.
RegSetValueEx , RegCloseKey. Pierwsza z nich
BOOL GetDwordValue(LPDWORD lpdwValue,LPTSTR lpszKey,LPTSTR lpszValueId); tworzy klucz lub, jeśli ten istnieje – otwie-
BOOL GetMultiSzValue(TCHAR lpszValue[][MAX_TEXT_LENGTH],LPDWORD lpdwCount,LPTSTR ra go. Aby używać dwóch kolejnych funkcji
lpszKey,LPTSTR lpszValueId); (odpowiednio: odczyt i zapis wartości), nale-
ży podać uchwyt do klucza zwrócony przez
BOOL GetSzValue(TCHAR lpszValue[MESSAGE_BUFFER_SIZE],LPTSTR lpszKey,LPTSTR pierwszą funkcję. Po zakończeniu operacji
lpszValueId); na kluczu należy zamknąć uchwyt do nie-
go przy użyciu ostatniej z funkcji. Oczywi-
BOOL ReadSettings(LPCALLREJECTERSETTINGS lpcrsSettings); ście zbiór funkcji służących operacjom na re-
BOOL SaveSettings(CALLREJECTERSETTINGS crcSettings); jestrach jest obszerniejszy(patrz odnośnik w
#endif ramce W Sieci). Tutaj wyjaśniono jedynie te
najczęściej stosowane.

14 SDJ Extra 34 Biblia


RIL API – w sercu telefonu

Listing 2. RegistryHelper.cpp (Wycinek)


#include "stdafx.h" HKEY hkey;
#include <windows.h> DWORD dwDisposition;
#include "RegistryHelper.h" DWORD dwType;
DWORD dwSize;
BOOL ReadSettings(LPCALLREJECTERSETTINGS lpcrsSettings) DWORD dwRejectAllCalls;
{ if(ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE,
memset(lpcrsSettings,0,sizeof(CALLREJECTERSETTINGS)); lpszKey,0, NULL, 0, 0, NULL, &hkey,
if (!GetDwordValue(&(lpcrsSettings->dwActivateService),SO &dwDisposition))
FTWARE_KEY,ACTIVATE_SERVICE_VALUE)) {
{ dwType = 0;
return FALSE; dwSize = 0;
}
if (!GetDwordValue(&(lpcrsSettings->dwBlockAll),SOFTWARE_ if (ERROR_SUCCESS == RegQueryValueEx(hkey, lpszValueId,
KEY,REJECT_ALL_CALLS_VALUE)) 0, &dwType, NULL, &dwSize))
{ {
return FALSE; if ((REG_DWORD == dwType)&&(dwSize>0))
} {
if (!GetSzValue(lpcrsSettings->tcMessage,SOFTWARE_ dwRejectAllCalls=0;
KEY,MESSAGE_VALUE))
{ if (ERROR_SUCCESS == RegQueryValueEx(hkey,
return FALSE; lpszValueId, 0, &dwType,
} reinterpret_cast<LPBYTE>(&dwRejectAll
if (!GetMultiSzValue(lpcrsSettings->tcBlockedList,&(lpcr Calls), &dwSize))
sSettings->dwBlockedCount),SOFTWARE_ {
KEY,BLOCKED_LIST_VALUE)) *lpdwValue=dwRejectAllCalls;
{ bResult = TRUE;
return FALSE; }
} }
return TRUE; }
} }
RegCloseKey(hkey);
BOOL SaveSettings(CALLREJECTERSETTINGS crsSettings) return bResult;
{ }
if(!SetDwordValue(crsSettings.dwBlockAll,SOFTWARE_
KEY,REJECT_ALL_CALLS_VALUE)) BOOL SetDwordValue(DWORD dwValue,LPTSTR lpszKey, LPTSTR
{ lpszValueId)
return FALSE; {
} BOOL bResult = FALSE;
if(!SetDwordValue(crsSettings.dwActivateService,SOFTWARE HKEY hkey;
_KEY,ACTIVATE_SERVICE_VALUE)) DWORD dwDisposition;
{ DWORD dwType;
return FALSE; DWORD dwSize;
} DWORD dwRejectAllCalls;
if(!SetSzValue(crsSettings.tcMessage,SOFTWARE_ if(ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE,
KEY,MESSAGE_VALUE)) lpszKey, 0, NULL, 0, 0, NULL, &hkey,
{ &dwDisposition))
return FALSE; {
} dwType = REG_DWORD;
if(!SetMultiSzValue(crsSettings.tcBlockedList,crsSettings dwRejectAllCalls=dwValue;
.dwBlockedCount,SOFTWARE_KEY,BLOCKED_ dwSize = sizeof(DWORD);
LIST_VALUE)) if (ERROR_SUCCESS == RegSetValueEx(hkey, lpszValueId,
{ 0, dwType, reinterpret_cast<LPBYTE>(&
return FALSE; dwRejectAllCalls), dwSize))
} {
return TRUE; bResult=TRUE;
} }
}
BOOL GetDwordValue(LPDWORD lpdwValue,LPTSTR lpszKey,LPTSTR
lpszValueId) RegCloseKey(hkey);
{ return bResult;
BOOL bResult = FALSE; }

www.sdjournal.org 15
Programowanie Windows Mobile

Na Listingach 1 i 2 przedstawiony zo- Wartości argumentów ustawionych na zero wartość (sizeof(DWORD), o tyle już w przy-
stał fragment kodu wrappera, który posłuży lub NULL, oraz ostatniego argumentu – są ma- padku wartości typu REG_SZ lub REG_MUL-
nam do zapisywania/odczytywania ustawień ło istotne. Pod wartość hKey podstawiony zo- TI_SZ musimy wcześniej zaalokować odpo-
naszej aplikacji. Kod na listingu drugim jest stanie uchwyt do klucza, o który wnioskowa- wiednią ilość pamięci. W tym celu przy pierw-
niekompletny, przedstawione zostały jedynie liśmy (w naszym przypadku to będzie klucz szym użyciu dodajemy operację odczytu, w
funkcje związane z odczytem/ zapisem war- CallRejecter). Następnie musimy zainicjali- której zamiast wskaźnika na bufor, przekazu-
tości typu REG_DWORD(przez analogie łatwo zować parametry, które przekażemy do funk- jemy wartość NULL.
jest dopisać funkcje dla pozostałych typów cji: RegSetValueEx(hkey, lpszValueId, 0, W ten sposób dokonamy jedynie odczy-
wartości). Dodatkowo umieszczone na Li- dwType, reinterpret_cast<LPBYTE>(&dwRe tu rozmiaru danej wartości (w bajtach) oraz
stingu 2 zostały funkcje czytające/zapisujące jectAllCalls), dwSize)). Funkcja ta przyj- jej typu.
całość naszych ustawień do/z struktury typu muje wartość uchwytu do klucza, który pobra-
CALLREJECTERSETTINGS(zdefiniowanej na Li- liśmy w poprzednim kroku, a następnie łańcuch Serwisy systemowe
stingu 1). określający nazwę wartości, którą chcemy usta- Mając ustalony sposób, w jaki będziemy prze-
Prześledźmy krok po kroku operacje od- wić. Kolejny argument jest zarezerwowany i mu- chowywać ustawienia dla naszej aplikacji, wy-
czytu(funkcja GetDwordValue) i zapisu(funk- si być ustawiony na zero, następnie musimy po- padałoby zastanowić się, co będzie stanowiło
cja SetDwordValue) umieszczone na Listingu dać typ wartości(dla tej konkretnej sytuacji bę- jej szkielet. Jak już wspomniałem, chcielibyśmy,
2. Rozpocznijmy od tej drugiej. W pierwszym dzie to REG_DWORD), a na końcu bufor, któ- aby nasza aplikacja działała w trybie 24/7, czy-
kroku staramy się otworzyć interesujący nas ry zawiera wartość do zapisu. W ostatnim para- li zawsze, kiedy uruchomione jest urządzenie.
klucz: RegCreateKeyEx(HKEY_LOCAL_MACHINE, metrze przekazujemy rozmiar bufora. Finalnym Możemy to zrobić na dwa sposoby. Pierwszym
lpszKey, 0, NULL, 0, 0, NULL, &hkey, krokiem jest zwolnienie uchwytu do wcześniej jest standardowa aplikacja, która po uruchomie-
&dwDisposition)) Jako pierwszy parametr pobranego klucza (RegCloseKey(hkey)). niu będzie ustawiać się w tryb oczekiwania na
podajemy wartość klucza głównego, natomiast Jeśli teraz przyjrzysz się funkcjom odczy- nadejście odpowiednich komunikatów, a na-
drugi to wartość podklucza. Gdybyśmy chcieli tującym, zauważysz zapewne, że w zasadzie stępnie umieścić ją w sekcji autostart urządze-
iterować od korzenia do konkretnej gałęzi drze- nie różnią się one znacząco. Jedyną różnicą nia. Rozwiązanie to jest jednak mało eleganc-
wa, mysielibyśmy wywoływać tę funkcję tak dłu- (oprócz użycia funkcji RegQueryValueEx w kie - nie powinno się tworzyć w ten sposób pro-
go, aż doszlibyśmy do interesującego nas klucza. miejsce funkcji RegSetValueEx) jest to, iż tym gramów, które nie komunikują się bezpośred-
Istnieje na szczęście prostszy sposób. Wystar- razem nie musimy inicjalizować konkretnymi nio z użytkownikiem. Inżynierowie projektują-
czy podać jeden z kluczy głównych, a następnie wartościami zmiennych odpowiedzialnych za cy system Windows Mobile w celu realizacji za-
ścieżkę prowadzącą bezpośrednio do interesu- typ oraz rozmiar na konkretne wartości. O ile dań uruchamianych w tle dostarczyli programi-
jącego nas klucza(w naszym przypadku będzie w przypadku wartości typu REG_DWORD stom mechanizm w postaci tzw. serwisów syste-
ona miała wartość(\Software\SDJ\CallRejecter). rozmiar bufora będzie miał zawsze tę samą mowych, czyli aplikacji, które z założenia nie bę-
dą wchodzić w bezpośrednią interakcję z użyt-
kownikiem na poziomie graficznym. Zadaniem
serwisów jest monitorowanie stanu systemu i
reagowanie na zmiany w nim zachodzące. W
ten właśnie sposób zaprogramujemy postawio-
ne sobie zadanie.
Serwisy (zwane też usługami) są realizowa-
ne w postaci dynamicznie łączonych bibliotek
DLL ( jeśli nie wiesz jeszcze, w jaki sposób takie
biblioteki tworzyć, spójrz proszę do ramki W sie-
ci, jeden z linków prowadzi do artykułu na ten
temat) ładowanych przez jeden z ważniejszych
procesów systemowych – Services.exe. Aż do
Rysunek 3. Przykładowy wpis do rejestrów dotyczący usługi LgeCCoreDrv wersji Windows CE 4.2 .NET programiści w ce-

Serwisy – Rejestry
Poniżej zamieszczony został zbiór możliwych wartości związanych z inicjalizacją serwisu przez proces Services.exe. W nawiasach podano typ da-
nej wartości.

• Context (REG _ DWORD) – wartość inicjalizująca przekazywana do procedury inicjalizacyjnej;


• Description (REG _ SZ) – opis serwisu;
• DisplayName (REG _ SZ) – nazwa serwisu;
• Dll (REG _ SZ) – dynamicznie łączona biblioteka (DLL) zawierająca serwis;
• Flags (REG _ DWORD) – określa zbiór flag używanych do modyfikacji zachowania funkcji ActivateServices. Oto poprawne wartości flag:
• DEVFLAGS _ NONE (0x00000000): brak określonych flag;
• DEVLFAGS _ UNLOAD (0x00000001): wyładuj serwis po powrocie z funkcji xxx _ Init;
• DEVFLAGS _ LOADLIBRARY (0x00000002): skorzystaj z funkcji LoadLibrary do załadowania pliku DLL usługi;
• DEVFLAGS _ NOLOAD (0x00000004): nie ładuj serwisu;
• DEVFLAGS _ TRUSTEDCALLERONLY (0x00010000) : serwis może być wywołany tylko przez zaufane procesy;
• Index (REG _ SZ) – indeks (instancji serwisu);
• Keep (REG _ DWORD) – jeśli ustawiony na 0, serwis zostanie wyładowany natychmiast po inicjalizacji;
• Order (REG _ DWORD) – kolejność, w jakiej ładowane są serwisy, serwis o najniższej wartości ładowany jest w pierwszej kolejności;
• Prefix (REG _ SZ) – trzyliterowy prefiks definiujący usługę – wraz z wartością indeksu pozwala na odwołanie się do konkretnej instancji
serwisu.

16 SDJ Extra 34 Biblia


RIL API – w sercu telefonu

lu realizacji zadań serwisów zmuszeni byli uży- kające z podejścia do serwisu jako pliku (Open, tacji. CRS_Init – jak nietrudno się domyślić, ta
wać systemu sterowników – dopiero w tej wersji Close, Read, Write, Seek). Na Listin- funkcja jest wywoływana podczas inicjowania
pojęcie to zostało wprowadzone do rodziny CE. gu 3 został przedstawiony szkielet naszego ser- serwisu, co może nastąpić w dwóch sytuacjach:
W związku z tymi zaszłościami historycznymi wisu. Zauważmy, że w miejsce xxx wstawi- w wyniku wywołania funkcji RegisterService
usługi odziedziczyły po sterownikach pewne ce- liśmy trzyliterowy prefiks CRS (dla ciekaw- lub w momencie, w którym następuje inicjali-
chy, jak np. punkty wejścia, lub również trzylite- skich – wymyśliłem go jako akronim od Call zacja procesu Services.exe. W tym drugim przy-
rowy prefiks, który wraz z numerem instancji Rejecter Service). Musimy jeszcze sprawić, aby padku jako argument do funkcji przekazana zo-
serwisu służy do pobrania uchwytu. nasze funkcje były widoczne na zewnątrz stanie umieszczona w rejestrach wartość Con-
Skąd jednak zarządca serwisów systemo- pliku DLL. Możemy to zrobić dwojako, po text. W przypadku powodzenia, powinna zo-
wych wie, że dany serwis ma zostać załadowa- pierwsze możemy do tego użyć dyrektywy _ stać zwrócona wartość niezerowa, w przeciwnej
ny? I czy samo załadowanie serwisu do pamię- _declspec(dllexport) przy definicji funkcje; sytuacji usługa zostanie wyładowana.
ci oznacza, że będzie on działał? Odpowiem w drugim sposobem jest dołączenie do projektu Nie trzeba być orłem, aby odgadnąć prze-
pierwszej kolejności na pytanie drugie: nie, sa- pliku DEF. Taki przykładowy plik definicji zo- znaczenie funkcji CRS_Deinit. Jako jedy-
ma obecność usługi w pamięci nie oznacza jej stał przedstawiony na Listingu 4. ny argument przyjmuje ona wartość zwróco-
działania (oczywiście w sensie spełniania funk- Czas wyjaśnić znaczenie poszczególnych ną przez poprzednią funkcję. Pamiętajmy, że
cji, dla której serwis został stworzony, w dal- funkcji, zanim przejdziemy do ich implemen- skoro funkcja CRS_Init zwraca wartość typu
szym ciągu serwis jest gotowy do odbierania
komunikatów od procesu Services.exe). Dla- Listing 3. CallRejecterService.cpp
czego? Otóż dlatego, że serwis może znajdo-
wać się w kilku stanach: może być uruchomio- #include "stdafx.h"
ny lub też wstrzymany, dochodzi do tego jesz- #include <windows.h>
cze kilka mniej istotnych stanów. Wróćmy
więc do pierwszego pytania. Spójrz proszę na BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,
Rysunek 3. Przedstawiony tam został wycinek LPVOID lpReserved )
systemowych rejestrów zawierający wpisy dla {
usługi LgeCCoreDrv. Znajdują się tam infor- return TRUE;
macje na temat wszystkich ważnych właściwo- }
ści związanych z inicjalizacją serwisu (spójrz
na ramkę Serwisy – rejestry). Jeśli podobny DWORD CRS_Init(DWORD dwData)
wpis dodasz do rejestru dla swojego serwisu {
(w ścieżce HKEY_LOCAL_MACHINE\Services\ DWORD dwContext = 1;
{Nazwa serwisu}), wówczas zostanie on za- return dwContext;
ładowany wraz ze startem systemu (pamiętaj }
jednak, że załadowany to nie to samo, co uru-
chomiony). Innym sposobem na załadowanie DWORD CRS_Deinit(DWORD dwContext)
serwisu bez konieczności tworzenia wpisu w {
rejestrze jest użycie funkcji RegisterService return TRUE;
(jest ona zdefiniowana w pliku nagłówkowym }
Services.h). Jest jeszcze trzecia droga – łączą-
ca zalety obydwu rozwiązań. Jest nią funk-
cja ActivateService. W zaprojektowanym DWORD CRS_IOControl(DWORD dwData, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE
przeze mnie GUI pozwoliłem sobie na uży- pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)
cie właśnie tego ostatniego podejścia. Funkcja {
ActivateService przyjmuje dwa argumenty: return TRUE;
pierwszy określający nazwę klucza rejestrów }
zawierającego informację o usłudze, drugi jest
natomiast zarezerwowany i powinien zostać void CRS_PowerDown(DWORD dwContext) {
ustawiony na zero. return;
Wspomniałem wcześniej o punktach wejścia }
do serwisu. Są to funkcje o z góry zdefiniowa-
nych nazwach, które pozwalają procesowi Servi- void CRS_PowerUp(DWORD dwContext)
ces.exe na komunikację z załadowaną biblioteką {
DLL. Istnieje dziesięć standardowych punktów return;
wejścia: xxx_Init, xxx_Deinit, xxx_Open, }
xxx_Close, xxx_Read, xxx_Write, xxx_
Seek, xxx_IOControl, xxx_PowerDown oraz Listing 4. CallRejecterService.def
xxx_PowerUp. Jak już wspomniałem, jest to spa- LIBRARY "CallRejecterService"
dek z czasów, gdy jako dostawców usług używa- EXPORTS
no sterowników. W procesie implementacji xxx CRS_Init
w nazwach funkcji zamieniamy oczywiście na CRS_Deinit
trzyliterowy prefiks, pod jakim serwis funkcjo- CRS_IOControl
nuje w systemie. CRS_PowerDown
Na szczęście nie musimy implementować CRS_PowerUp
wszystkich funkcji. Pominiemy funkcje wyni-

www.sdjournal.org 17
Programowanie Windows Mobile

DWORD, to w łatwy w sposób można na nią rzu-


Listing 5. Funkcja CRS_IOControl tować wskaźnik na dowolną strukturę czy typ.
Dzięki takiemu rozwiązaniu możemy unik-
DWORD CRS_IOControl(DWORD dwData, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE nąć stosowania zmiennych globalnych.
pBufOut, DWORD dwLenOut, PDWORD pdwActualOut) Para funkcji CRS_PowerUp i CRS_PowerDown
{ służy do przygotowania serwisu na przejście
switch(dwCode) urządzenia w stan niskiego poboru energii, a
{ potem na przywrócenie odpowiedniego sta-
case IOCTL_SERVICE_QUERY_CAN_DEINIT: nu po powrocie do normalnego stanu zasila-
{ nia. Funkcje te przyjmują jako argument war-
if ((pBufOut) && (dwLenOut >= sizeof (BYTE))) tość zwróconą przez CRS_Init (vide unika-
{ nie zmiennych globalnych). Powinieneś za-
*pBufOut = 1; wsze obsłużyć w swoim serwisie te dwie funk-
if (pdwActualOut) cje, tak aby możliwe było przed przejściem w
{ stan obniżonego zużycia energii zwolnienie
*pdwActualOut = sizeof(BYTE); jak największej liczby zasobów przywłaszczo-
} nych przez usługę.
} Ostatnia funkcja (CRS_IOControl) odpo-
} wiada za komunikację aplikacji zewnętrz-
case IOCTL_SERVICE_REFRESH: nych z usługą. To właśnie tu trafiają komu-
{ nikaty wysłane chociażby przy użyciu funk-
UpdateConfiguration(); cji ServiceIOControl. Na Listingu 5 zo-
} stała umieszczona wypełniona funkcja
break; CRS_IOControl naszego serwisu.
} W naszym prostym serwisie obsłużymy je-
return TRUE; dynie dwa komunikaty, zachęcam jednak do
} eksperymentów z innymi, jak również do de-
finiowania swoich. Pierwszym z obsłużonych
Listing 6. Brakująca zawartość pliku CallRejecterService.cpp komunikatów jest IOCTL_SERVICE_QU-
#include <Service.h> ERY_CAN_DEINIT.
#include "RilHelper.h" Może być on np. następstwem wywoła-
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, nia funkcji DeregisterService (której zada-
LPVOID lpReserved ) niem jest wyładowanie serwisu). Jeżeli usłu-
{ ga z jakiegoś powodu nie może być wyładowa-
return TRUE; na, wówczas w buforze wyjściowym powinna
} ustawić zero, jeżeli wartość w buforze będzie
niezerowa, oznacza to, że będzie mogła zostać
DWORD CRS_Init(DWORD dwData) wywołana funkcja CRS_Deinit.
{ Drugim komunikatem obsłużonym w na-
DWORD dwContext = 0; szym serwisie jest IOCTL_SERVICE_RE-
if (UpdateConfiguration()) FRESH, którym subtelnie sugerujemy serwi-
{ sowi, że powinien ponownie wczytać swo-
if SUCCEEDED(InitializeCallRejecter()) ją konfigurację, ponieważ uległa ona zmianie.
{ Co prawda nie zdefiniowaliśmy jeszcze funk-
dwContext = 1; cji UpdateConfiguration, ale zapewniam Cię,
} że gdybyś zdefiniował chociażby puste ciało tej
} funkcji, dokonał odpowiednich wpisów w re-
return dwContext; jestrach i wrzucił skompilowaną bibliotekę na
} urządzenie, byłbyś w stanie uruchomić naszą
DWORD CRS_Deinit(DWORD dwContext) usługę (a nawet udałoby Ci się ją zatrzymać)
{
DeinitializeCallRejecter(); Nuda, nic się nie dzieje...
return TRUE; Gdybyśmy poprosili inżyniera Mamonia o ko-
} mentarz na temat naszego oprogramowania,
void CRS_PowerDown(DWORD dwContext) { tak pewnie by je właśnie podsumował i nie ma
DeinitializeCallRejecter(); w tym nic dziwnego, zważywszy na fakt, iż w
return; tej chwili nasza usługa nie robi właściwie nic
} (a już zdecydowanie nie to, do czego ją prze-
void CRS_PowerUp(DWORD dwContext) widzieliśmy). Pora więc nieco ożywić akcję i
{ wprowadzić wreszcie na scenę głównego boha-
InitializeCallRejecter(); tera tego artykułu. Najpierw jednak spójrzmy
return; na Listing 6 – umieściłem na nim wypełnione
} ciała pozostałych funkcji, nie przejmujemy się
przy tym faktem, że wprowadzone zostały ko-

18 SDJ Extra 34 Biblia


RIL API – w sercu telefonu

lejne funkcje, o których implementacji nic nie


wiemy. Za chwilę wszystko stanie się jasne (o Listing 7. RilHelper.h
ile już nie jest). #ifndef _RILHELPER_H_
Na Listingach 7 i 8 została przedstawiona po- #define _RILHELPER_H_
została część naszego serwisu (ta, która wresz-
cie coś robi). Jak wspomniałem we wstępie, #include "ril.h"
RIL API ma wśród wielu programistów opi-
nię, nazwijmy to delikatnie, niezbyt przyjemne- #define RIL_TIMEOUT 3000
go – dzieje się tak prawdopodobnie dlatego, że
z jednej strony większa część funkcji wchodzą- #define HANGUP_SUCCESS L"HangUpSuccess"
cych w jego skład ma charakter asynchroniczny, #define HANGUP_FAIL L"HangUpFail"
z drugiej zaś sama architektura jest typowym #define GETMSGCONFIG_SUCCESS L"GetMsgConfigSuccess"
przykładem event-driven programming. #define GETMSGCONFIG_FAIL L"GetMsgConfigFail"
Chcąc skorzystać z dobrodziejstw dostar- #define SENDMSG_SUCCES L"SendMsgSuccess"
czanych przez Radio Interface Layer, musisz w #define SENDMSG_FAIL L"SendMsgFail"
pierwszej kolejności zarejestrować połączenie z
RIL Proxy. Robi się to przy użyciu funkcji RIL_ enum RilFunctions {HANGUP,GETMSGCONFIG,SENDMESSAGE};
Initialize. Funkcja ta przyjmuje jako pierw-
szy argument numer Proxy, do którego chcemy HRESULT InitializeCallRejecter();
się podłączyć (nie wiem, jaka jest prawidłowość, HRESULT DeinitializeCallRejecter();
ale w tym miejscu działa przeważnie tylko war- BOOL IsOnBlockedList(LPTSTR lpszNumber);
tość 1). Kolejne dwa parametry to wskaźniki na BOOL UpdateConfiguration();
funkcje, za pomocą których RIL Proxy będzie VOID RilNotifyCallback(DWORD dwCode, const void* lpData, DWORD cbData, DWORD dwParam );
się komunikowało z naszą aplikacją, następnie VOID RilResultCallback(DWORD dwCode, HRESULT hrCmdID, const void* lpData, DWORD
musimy podać klasę notyfikacji, które chcieli- cbData, DWORD dwParam);
byśmy otrzymywać. Następny parametr typu HRESULT GetMsgConfig();
DWORD to wartość, którą będziemy przekazy- HRESULT SendSMS(RILADDRESS raDest);
wać do dwóch wspomnianych wcześniej funk- #endif
cji – pamiętaj, że podobnie jak w przypadku

R E K L A M A

www.sdjournal.org 19
Programowanie Windows Mobile

Listing 8. RilHelper.cpp
#include "stdafx.h" rm.msgOutSubmit.dwProtocolID = RIL_MSGPROTOCOL_SMETOSME;
#include "RilHelper.h" rm.msgOutSubmit.raDestAddress.cbSize = raDest.cbSize;
#include "RegistryHelper.h" rm.msgOutSubmit.raDestAddress.dwNumPlan =
HRESULT hrResults[3]={E_FAIL,E_FAIL,E_FAIL}; raDest.dwNumPlan;
HANDLE hEvents[6]={NULL,NULL,NULL,NULL,NULL}; rm.msgOutSubmit.raDestAddress.dwParams = raDest.dwParams;
HRIL hRil=NULL; rm.msgOutSubmit.raDestAddress.dwType = raDest.dwType;
LPRILMSGCONFIG g_lprmcMsgConfig = NULL; _tcscpy_s(rm.msgOutSubmit.raDestAddress.wszAddress,256
CALLREJECTERSETTINGS g_crsSettings={}; ,raDest.wszAddress);
HRESULT GetMsgConfig() rm.msgOutSubmit.rmdDataCoding.cbSize = sizeof(RILMSGDCS);
{ rm.msgOutSubmit.rmdDataCoding.dwParams = RIL_PARAM_
HRESULT hr = E_FAIL; MDCS_TYPE|RIL_PARAM_MDCS_ALPHABET;
DWORD dwEvent=0; rm.msgOutSubmit.rmdDataCoding.dwType = RIL_DCSTYPE_
if (hRil) GENERAL;
{ rm.msgOutSubmit.rmdDataCoding.dwAlphabet = RIL_
hr = RIL_GetMsgConfig(hRil); DCSALPHABET_DEFAULT;
hrResults[GETMSGCONFIG] = hr; rm.msgOutSubmit.rmdDataCoding.dwMsgClass = RIL_
if(SUCCEEDED(hr)) DCSMSGCLASS_0;
{ rm.msgOutSubmit.rmdDataCoding.dwFlags = RIL_DCSFLAG_NONE;
dwEvent = WaitForMultipleObjects(2,&hEvents[2*GETMS
GCONFIG],FALSE,100*RIL_TIMEOUT); SYSTEMTIME curTime;
switch(dwEvent) GetLocalTime(&curTime);
{ rm.msgOutSubmit.stVP.wYear = curTime.wYear;
case WAIT_OBJECT_0: rm.msgOutSubmit.stVP.wMonth = curTime.wMonth;
hr = S_OK; rm.msgOutSubmit.stVP.wDayOfWeek = curTime.wDayOfWeek;
break; rm.msgOutSubmit.stVP.wDay = curTime.wDay;
default: rm.msgOutSubmit.stVP.wHour = curTime.wHour;
hr = E_FAIL; rm.msgOutSubmit.stVP.wMinute = curTime.wMinute;
} rm.msgOutSubmit.stVP.wSecond = curTime.wSecond;
} rm.msgOutSubmit.stVP.wMilliseconds =
} curTime.wMilliseconds;
return hr; rm.msgOutSubmit.cchMsgLength = wcstombs(reinterpret_
} cast<CHAR *>(rm.msgOutSubmit.rgbMsg),g
_crsSettings.tcMessage,256);
HRESULT SendSMS(RILADDRESS raDest) hr = RIL_SendMsg(hRil,&rm,RIL_SENDOPT_NONE);
{ hrResults[SENDMESSAGE] = hr;
RILMESSAGE rm={}; if(SUCCEEDED(hr))
HRESULT hr = E_FAIL; {
DWORD dwEvent = 0; dwEvent = WaitForMultipleObjects(2,&hEvents[2*SENDM
ESSAGE],FALSE,RIL_TIMEOUT);
if (hRil) switch(dwEvent)
{ {
rm.cbSize = sizeof(RILMESSAGE); case WAIT_OBJECT_0:
rm.dwParams = RIL_PARAM_M_SVCCTRADDRESS | RIL_PARAM_ hr = S_OK;
M_TYPE | RIL_PARAM_M_DESTADDRESS break;
| RIL_PARAM_M_PROTOCOLID |RIL_PARAM_ default:
M_MSGLENGTH | RIL_PARAM_M_FLAGS | hr = E_FAIL;
RIL_PARAM_M_VPFORMAT | RIL_PARAM_ }
M_DATACODING | RIL_PARAM_M_VP | }
RIL_PARAM_M_MSG; }
rm.raSvcCtrAddress.cbSize = g_lprmcMsgConfig- return hr;
>raSvcCtrAddress.cbSize; }
rm.raSvcCtrAddress.dwNumPlan = g_lprmcMsgConfig->raSvc
CtrAddress.dwNumPlan; VOID RilResultCallback(DWORD dwCode, HRESULT hrCmdID, const void*
rm.raSvcCtrAddress.dwParams = g_lprmcMsgConfig->raSvcC lpData, DWORD cbData, DWORD dwParam)
trAddress.dwParams; {
rm.raSvcCtrAddress.dwType = rm.raSvcCtrAddress.dwType; if (dwCode = RIL_RESULT_OK)
_tcscpy_s(rm.raSvcCtrAddress.wszAddress,256,g_ {
lprmcMsgConfig->raSvcCtrAddress.wszAd if (hrCmdID==hrResults[HANGUP])
dress); {
rm.dwType = RIL_MSGTYPE_OUT_SUBMIT; SetEvent(hEvents[2*HANGUP]);
rm.dwFlags = RIL_MSGFLAG_NONE; }

20 SDJ Extra 34 Biblia


RIL API – w sercu telefonu

Listing 8 cd. RilHelper.cpp


if (hrCmdID==hrResults[GETMSGCONFIG]) hEvents[2*HANGUP]=CreateEvent(NULL,FALSE,FALSE,HANGUP_
{ SUCCESS);
if (lpData) hEvents[2*HANGUP]=CreateEvent(NULL,FALSE,FALSE,HANGUP_FAIL);
{ hEvents[2*GETMSGCONFIG]=CreateEvent(NULL,FALSE,FALSE,GET
g_lprmcMsgConfig = reinterpret_cast<LPRILMSGCONF MSGCONFIG_SUCCESS);
IG>(LocalAlloc(LHND,cbData)); hEvents[2*GETMSGCONFIG+1]=CreateEvent(NULL,FALSE,FALSE,G
if (g_lprmcMsgConfig) ETMSGCONFIG_FAIL);
{ hEvents[2*SENDMESSAGE]=CreateEvent(NULL,FALSE,FALSE,SEND
CopyMemory(g_lprmcMsgConfig,lpData,cbData); MSG_SUCCES);
SetEvent(hEvents[2*GETMSGCONFIG]); hEvents[2*SENDMESSAGE+1]=CreateEvent(NULL,FALSE,FALSE,SE
} NDMSG_FAIL);
} hr = RIL_Initialize(1,RilResultCallback,RilNotifyCallback
} ,RIL_NCLASS_SUPSERVICE,NULL,&hRil);
if (hrCmdID==hrResults[SENDMESSAGE]) if (SUCCEEDED(hr))
{ {
DWORD dwResult=reinterpret_cast<DWORD>(lpData); hr = GetMsgConfig();
SetEvent(hEvents[2*SENDMESSAGE]); }
} return hr;
} }
else
{ HRESULT DeinitializeCallRejecter()
if (hrCmdID==hrResults[HANGUP]) {
{ HRESULT hr = E_FAIL;
SetEvent(hEvents[2*HANGUP+1]);
} for (DWORD i=0;i<6;i++)
if (hrCmdID==hrResults[GETMSGCONFIG]) {
{ CloseHandle(hEvents[i]);
SetEvent(hEvents[2*GETMSGCONFIG+1]); }
} g_lprmcMsgConfig = reinterpret_cast<LPRILMSGCONFIG>(Loc
if (hrCmdID==hrResults[SENDMESSAGE]) alFree(reinterpret_cast<HLOCAL>(g_
{ lprmcMsgConfig)));
SetEvent(hEvents[2*SENDMESSAGE+1]); hr = RIL_Deinitialize(hRil);
} return hr;
} }
}
BOOL UpdateConfiguration()
VOID RilNotifyCallback(DWORD dwCode, const void* lpData, {
DWORD cbData, DWORD dwParam ) memset (&g_crsSettings,0,sizeof(CALLREJECTERSETTINGS));
{ return ReadSettings(&g_crsSettings);
switch (dwCode)
{ }
case RIL_NOTIFY_CALLERID: BOOL IsOnBlockedList(LPTSTR lpszNumber)
{ {
LPRILREMOTEPARTYINFO lpRilRemotePartyInfo = BOOL bResult = FALSE;
reinterpret_cast<LPRILREMOTEPARTYINFO if (g_crsSettings.dwBlockAll!=0)
>(const_cast<LPVOID>(lpData)); {
if (IsOnBlockedList(lpRilRemotePartyInfo- bResult = TRUE;
>raAddress.wszAddress)) }
{ else
if (SUCCEEDED(RIL_Hangup(hRil))) {
{ for (DWORD i=0;i<g_crsSettings.dwBlockedCount;i++)
SendSMS(lpRilRemotePartyInfo->raAddress); {
} if(!_tcscmp(lpszNumber,g_crsSettings.tcBlockedList[i]))
} {
} bResult = TRUE;
} break;
} }
}
HRESULT InitializeCallRejecter() }
{ return bResult;
HRESULT hr = E_FAIL; }

www.sdjournal.org 21
Programowanie Windows Mobile

serwisów, możesz w tym miejscu rzutować na sposób: Każdorazowo gdy wywołujemy funkcję składa się oprócz składowych prostych rów-
typ DWORD chociażby bufor, w którym będziesz asynchroniczną z zestawu RIL API, zwraca ona nież ze struktur (np. struktury odpowiada-
przekazywał dodatkowe argumenty lub do któ- natychmiast wartość typu HRESULT. Jeśli wartość jącej za konfigurację centrum obsługi SMS),
rego powinien być zapisany wynik. Ostatnim ta jest ujemna, to możemy ją zinterpretować jako oraz jednej unii (zawierającej struktury cha-
parametrem jest wskaźnik do uchwytu do RIL nieudane wywołanie i odszukać powód niepo- rakterystyczne dla różnych typów wiadomo-
Proxy, który zostanie nam zwrócony. Uchwytu wodzenia w tabeli błędów. Jeśli zaś wartość jest ści). Dochodzą do tego jeszcze różnorakie sta-
tego będziemy używać, aby wywoływać funk- nieujemna, to będzie ona stanowiła identyfika- łe (jak np. RIL_MSGTYPE_OUT_SUBMIT – odpo-
cje RIL. Należy pamiętać, aby uchwyt ten bez- tor przekazany do funkcji RilResultCallback wiadająca wiadomości przeznaczonej do wy-
względnie zwolnić w momencie, kiedy nie bę- właśnie jako hrCmdID. Znaczenie pozostałych słania, czy RIL_DCSMSGCLASS_0 – określająca,
dzie już potrzebny. Robi się to przy użyciu funk- parametrów jest zbieżne z parametrami przeka- że wiadomość ma zostać wyświetlona bezpo-
cji RIL_Deinitialize. zanymi do funkcji RilNotifyCallback. średnio na wyświetlaczu telefonu). W przy-
Dwie funkcje, o których wspomniałem Asynchroniczność funkcji często jest po- padku każdej struktury trzeba jeszcze okre-
przed chwilą, w kontekście parametrów prze- wodem do narzekań, można ją jednak łatwo ślić, które jej składowe są znaczące (i znów
kazanych do funkcji RIL_Initialize to ujarzmić. Jako przykład niech posłuży stwo- przy użyciu stałych). Efekt jest taki, że bez
RilNotifyCallback oraz RilResultCallback. rzona przeze mnie funkcja GetMsgConfig. Jej otwartej cały czas dokumentacji nie jesteśmy
Pierwsza z nich odpowiada za przetwarzanie no- zadaniem jest odczytać konfigurację centrum w stanie tego zrobić. Na szczęście wszystko
tyfikacji, na których nasłuchiwanie zarejestro- SMS, tak abyśmy mogli wysyłać później nasze zostało udokumentowane w sposób profesjo-
waliśmy się, podając klasę notyfikacji w wywo- wiadomości. Funkcja ta tak naprawdę jest opa- nalny (tego typu dokumentacji zazdroszczą
łaniu funkcji RIL_Initialize. Funkcja ta przyj- kowaniem funkcji RIL_GetMsgConfig, mają- nam nawet najzagorzalsi zwolennicy Symbia-
muje jako parametry cztery wartości. Pierwszą cym na celu właśnie ominięcie asynchronicz- na - odnośnik znajdziesz w ramce W Sieci), a
jest kod notyfikacji, druga to wskaźnik na bu- ności. Po wywołaniu tej funkcji oczekujemy wraz z nabywaniem wprawy, dzięki podpo-
for, który zawiera dodatkowe informacje niesio- bowiem na przyjście zdarzenia sygnalizujące- wiedziom InteliSensa i własnej intuicji, czas
ne wraz z notyfikacją, trzecia to rozmiar tego bu- go. Przyjmując rozsądny czas oczekiwania, je- tworzenia nowych rozwiązań drastycznie
fora w bajtach, ostatnia zaś to wartość zadeklaro- steśmy w stanie po jego upływie z dużą dozą ulega skróceniu.
wana podczas inicjalizacji jako przekazywana do prawdopodobieństwa stwierdzić, że ponieśli-
wywołań callbacków. W naszym przykładzie za- śmy porażkę i nie ma sensu dalej czekać. Cała Podsumowanie
rejestrowaliśmy się na nasłuchiwanie notyfika- faktyczna obsługa rezultatu wywołania funk- W niniejszym artykule starałem się pokazać,
cji z grupy RIL_NCLASS_SUPSERVICE. Jed- cji RIL_GetMsgConfig ma oczywiście miej- że RIL pomimo swojego ogromu i pozorne-
ną z nich jest notyfikacja RIL_NOTIFY_CAL- sce w funkcji RilResultCallback. Jeśli funk- go skomplikowania, daje się jednak ujarz-
LERID, która zostaje wysłana w momencie przy- cja powiedzie się, to otrzymamy wskaźnik na mić. Oczywiście to, co zostało tu omówio-
chodzącego połączenia. Wskaźnik lpData wska- strukturę typu RILMSGCONFIG, zawierają- ne, to zaledwie wierzchołek góry lodowej.
zuje na strukturę typu RILREMOTEPARTYINFO , cą potrzebne nam ustawienia. Skoro strukturę Nie omówiłem tu chociażby funkcji pozwa-
której jedną ze składowych jest numer, z które- taką będziemy już posiadać, możemy wykonać lających na dostęp do informacji zawartych
go przychodzi do nas połączenie. zdarzenie oznaczające powodzenie. na karcie SIM czy też bardzo ciekawej funk-
Znając tę wartość, możemy porównać ją z Kolejnym przykładem synchronicznego cji RIL_DevSpecific, która pozwala na do-
listą blokowanych numerów i podjąć decyzję wrappera jest funkcja SendSMS, choć w tym stęp do ukrytych przez producenta sterow-
o odrzuceniu takiego połączenia. Połączenie wypadku i tak nie troszczymy się o rezul- ników modemu funkcjonalności (funkcje
możemy odrzucić przy pomocy funkcji RIL_ tat. Funkcja ta wykorzystuje rilowską funk- te jednakże są indywidualne dla urządzeń i
HangUp, która jako jedyny parametr przyjmu- cję RIL_SendMsg. W tym miejscu pora na ma- niestety, jeśli nie współpracujesz z produ-
je uchwyt do RIL Proxy, potem możemy wy- łą uwagę. Ponieważ mimo dostarczenia jed- centem telefonów, to prawdopodobnie nie
słać SMS z informacją o tym, dlaczego połącze- nolitego API faktyczny ciężar implementa- uda Ci się do nich dotrzeć). Mam jednak-
nie odrzuciliśmy. Na potrzeby tej przykłado- cji funkcjonalności i tak leży po stronie do- że nadzieję, że udało mi się zachęcić do sa-
wej aplikacji stworzyłem w tym celu funkcję stawcy sterownika modemu, może się zda- modzielnego zgłębiania tajników RIL'a i nie
SendSms, której przyjrzymy się później. rzyć, że na niektórych urządzaniach część skreślania go na starcie.
Druga z funkcji (RilResultCallback) słu- funkcji nie będzie działać poprawnie. Tak
ży jako punkt powrotu z wywołań asynchro- jest właśnie w przypadku RIL_SendMsg. Je-
nicznych funkcji wchodzących w skład RIL śli na twoim urządzeniu funkcja ta nie bę- PRZEMYSŁAW NOGAJ
API. Przyjmuje ona o jeden parametr wię- dzie dawała żadnego efektu, spróbuj ją zastą- Pracuje na stanowisku Junior C++ Developer
cej niż RilNotifyCallback – jest to hrCmdID. pić przez wywołanie funkcji SmsSendMessage w firmie BLStream, wchodzącej w skład Grupy
Pierwszy z parametrów tej funkcji niesie ze so- wchodzącej w skład SMS API. Przyjrzyjmy BLStream. Przemek specjalizuje się w technolo-
bą informację na temat tego, czy asynchronicz- się więc przez chwilę ciału funkcji SendSMS. giach związanych z produkcją oprogramowania
na funkcja, którą wywoływaliśmy, zakończyła Ujawnia się tu dodatkowy minus związa- na platformę Windows Mobile. W swojej dotych-
się powodzeniem (wówczas przyjmie on war- ny z RILem, czyli wszechobecna koniecz- czasowej pracy zajmował się również tworze-
tość RIL_RESULT_OK). Drugi z parametrów po- ność wypełniania skomplikowanych struk- niem aplikacji w technologii Flash.
zwala na identyfikację, którą z funkcji wywoły- tur. W tym konkretnym przypadku musi- Kontakt z autorem:
waliśmy. Mechanizm ten działa w następujący my wypełnić strukturę RILMESSAGE, która przemyslaw.nogaj@blstream.com

W Sieci
• http://msdn.microsoft.com/en-us/library/aa912217.aspx – informacje na temat rejestrów systemowych;
• http://msdn.microsoft.com/en-us/library/aa450223.aspx – informacje na temat systemu serwisów;
• http://support.microsoft.com/kb/815065 – tworzenie bibliotek łączonych dynamicznie(DLL);
• http://msdn.microsoft.com/en-us/library/aa920441.aspx – dokumentacja RIL API.

22 SDJ Extra 34 Biblia


Programowanie Windows Mobile

RAPI
Współpraca komputerów desktop z Windows CE i Mobile

Czasami podłączamy urządzenie mobilne wyposażone wWindows CE/Mobile


do komputera stacjonarnego, aby skopiować pliki, połączyć się z Internetem
czy wygodnie wysłać z klawiatury kilka SMS-ów. W świecie mobilnych
urządzeń Microsoftu te i wiele innych ciekawych funkcji dostępnych jest za
pośrednictwem biblioteki RAPI. Nie tylko dla MS Windows.

przez kabel USB do komputera stacjonar-


Dowiesz się: Powinieneś wiedzieć: nego, najczęściej potwierdzić kilka mniej
• Jak połączyć się z Windows CE i Mobile z po- • Jak programować w C; lub bardziej zrozumiałych pytań za pomo-
ziomu Windows i Linuksa; • Jak posługiwać się kompilatorami Visual C i cą OK, definiując niniejszym tzw. partner-
• Jak napisać i skompilować aplikację C wyko- GCC w środowisku Windows i Linux; stwo telefonu i komputera, i już za pomo-
rzystującą RAPI; • Przynajmniej w podstawowym zakresie, jak cą wbudowanego w system operacyjny eks-
• Jak napisać i skompilować prostą aplika- pisać makefile-e dla nmake.exe oraz gmake; ploratora plików możemy przeciągać i upusz-
cję w C dla Windows CE i Mobile za pomocą • Jak wygląda prosta aplikacja Win32. czać pliki między komputerem i PDA czy, w
kompilatora Microsoftu oraz cegcc. zależności od konfiguracji, synchronizować
pocztę i terminy między zainstalowanym na
urządzeniu MS Outlookiem oraz zainstalo-
dziecie tu opis implementacji dwóch krót- wanym na komputerze stacjonarnym, jaka
Poziom trudności kich programów dla Windows CE – pierw- niespodzianka, również MS Outlookiem.
szy wysyła SMS poprzez telefon wyposażony Wszystkie te operacje pracują w oparciu o
w Windows Mobile, drugi tworzy zrzut ekra- funkcje RAPI i jeśli np. nie mamy proble-
nu urządzenia mobilnego – będących chyba mów z kopiowaniem plików za pomocą eks-

R
API (patrz również link [1]) – Remo- najbardziej reprezentatywnym – oprócz ko- ploratora, oznacza to, że nasz komputer z
te API – to zestaw funkcji dla kom- piowania plików – przykładem wykorzysta- systemem MS Windows Vista w zupełno-
puterów typu desktop umożliwiają- nia RAPI. ści sprosta wymaganiom przedstawionego w
cych operacje na plikach urządzeń przeno- tym artykule kodu.
śnych pracujących pod kontrolą Windows Instalacja Inne systemy MS Windows wymagają in-
CE, dostęp do rejestru, baz danych, zdalne stalacji programu MS Active Sync, z reguły
uruchomienie programu czy nawet wywo- MS Windows dostarczanego wraz z telefonem na towarzy-
łanie odpowiednio spreparowanej funkcji z Microsoft Windows Vista nie wymaga żad- szącej mu płytce CD.
biblioteki DLL urządzenia. Dzięki projek- nej specjalnej instalacji, aby mieć dostęp do Nieszczęśliwcy nie posiadający takiej
towi SynCE ([2]) biblioteka RAPI stała się funkcji RAPI. Wystarczy podłączyć telefon płytki (lepiej niech się nie przyznają, skąd
również dostępna dla garstki systemów ty-
pu Unix, obecnie dla Linuksa oraz FreeBSD.
Szczególnie źródła biblioteki librapi wraz z
kodem źródłowym kilku programów rapi2-
tools będących częścią projektu SynCE oka-
zały się kopalnią wiedzy na temat możliwo-
ści zdalnego wykorzystania PDA z poziomu
komputera stacjonarnego. Dzięki RAPI mo-
żemy nie tylko kopiować pliki z i na urządze-
nie, możemy również oglądać i modyfikować
zawartość rejestru czy zdalnie uruchamiać
tam programy. Poniżej pragnę przedstawić
kilka bardziej przydatnych funkcji RAPI, któ-
re w ten sam sposób możemy wykorzystać za-
równo pod Windowsami, jak i pod Linuksem
czy innymi systemami operacyjnymi wspiera-
nymi przez projekt SynCE. Dodatkowo znaj- Rysunek 1. Interfejs rndis0 do komunikacji z Windows CE

24 SDJ Extra 34 Biblia


RAPI

mają telefon, lub który przedstawiciel han- Aby uzyskać adres IP poprzez klienta Vista, zabierzmy się za napisanie pierw-
dlowy sobie na nich zaoszczędził) mogą DHCP, wystarczy zwykle uruchomić pro- szego prostego programu. Każdy program
spróbować załadować program z [3], przy gram dhclient lub dhcpcd z nazwą inter- korzystający z RAPI powinien zaczynać się
czym należy wyraźnie wspomnieć, że ser- fejsu sieciowego jako jednym z parame- wywołaniem funkcji CeRapiInit() i koń-
wis Microsoftu dostępny pod tym adre- trów linii poleceń. Współczesne Linuksy czyć wywołaniem CeRapiUninit(). Zada-
sem w czasie pisania artykułu wyraźnie fa- preferują jednak zdefiniowanie takiego za- niem naszego pierwszego programu bę-
woryzuje Polaków, nie wymagając od nas, chowania w stosownym pliku konfigura- dzie przedstawienie zawartości pliku z te-
w przeciwieństwie do innych nacji, nawet cyjnym, pozostawiając wywołanie klienta lefonu na standardowym wyjściu (czy-
zarejestrowania się, aby umożliwić ścią- DHCP któremuś ze skryptów uruchamia- li ekranie terminala lub konsoli cmd.exe)
gnięcie programu. Po instalacji MS Acti- nych przez hald w momencie pojawienia komputera biurkowego. W tym celu za
ve Sync i zapewne po ponownym urucho- się interfejsu rndis0, czyli w momencie pomocą funkcji CeCreateFile() otwie-
mieniu komputera podłączamy urządze- podłączenia PDA do komputera przez ka- ramy plik o podanej nazwie na telefonie
nie PDA, za pomocą klikania na OK i Dalej bel USB. W przypadku OpenSuSE należa- i funkcją CeReadFile() wczytujemy pew-
definiujemy znane już w MS Windows Vi- ło zdefiniować plik /etc/sysconfig/network/ ne porcje bajtów do bufora i zapisujemy
sta partnerstwo, po czym za pomocą wbu- ifcfg-rndis0 z zawartością przedstawioną
dowanego w MS Active Sync eksploratora w Listingu 1. Listing 1. /etc/sysconfig/network/ifcfg-rndis0
możemy zabrać się za przeciąganie i upusz- Ostatecznie należało uaktywnić w telefo-
czanie plików między PDA i komputerem nie tzw. rozszerzone funkcje sieciowe dla po- BOOTPROTO='dhcp'
biurkowym. łączenia USB w ustawieniach połączeń pa- BROADCAST=''
nelu sterowania, aby umożliwić komunika- ETHTOOL_OPTIONS=''
Linux cję IP oraz uaktywnić serwer DHCP telefo- IFPLUGD_PRIORITY='0'
Instalacja pod Linuksami to już nieco więk- nu (Settings/Connections/USB to PC/Enable IPADDR=''
sza przygoda. Pod adresem [4] szczegóło- advanced network functionality). Dopie- MTU=''
wo opisano instalację dla różnych dystrybu- ro teraz możemy pozwolić sobie na podłą- NAME='RNDIS'
cji Linuksa oraz dla FreeBSD. Jako szczęśli- czenie urządzenia do komputera za pomo- NETMASK=''
wy posiadacz OpenSuSE 11.1 mogłem sko- cą kabla USB, co powinno skutkować po- NETWORK=''
rzystać z gotowych pakietów RPM, przy jawieniem się w systemie interfejsu siecio- REMOTE_IPADDR=''
czym zainstalować należy przynajmniej: ste- wego rndis0 z przydzielonym mu adresem STARTMODE='ifplugd'
rownik usb-rndis-lite-*-samsung, synce- IP 169.254.2.2. Adres 169.254.2.1 należy USERCONTROL='no'
hal, libsynce0, libsynce-devel, librapi2, do telefonu (Rysunek 1). Aby ostatecznie
librapi-devel. Przydatne mogą być również upewnić się, że wszystko działa jak należy, Listing 2. Funkcja wyświetlająca zawartość
rapi2-tools oraz źródła pakietu librapi2, możemy uruchomić jeden z programów za- pliku PDA na ekranie komputera biurkowego
które często będziemy używać jako ściągaw- wartych w pakiecie rapi2-tools, np. pls /, psta- #include <stdio.h>
ki. Nic nie stoi na przeszkodzie zainstalowa- tus itd. (Rysunek 2). #include <rapi.h>
nia pozostałych pakietów i wyposażenia sys-
temu w przeróżne wtyczki do KDE, GNO- Pierwszy program void wcat(LPCWSTR aFileName) {
ME i bardziej znanych programów poczto- Po nacieszeniu się możliwościami progra- char buf[1024];
wych. mów z rapi2-tools, MS Active Sync lub na- HRESULT hr;
Komunikacja między PDA i komputerem rzędziami dostępnymi pod MS Windows HANDLE hf;
stacjonarnym odbywa się za pomocą proto- DWORD nread;
kołu IP poprzez interfejs sieciowy rndis0.
Aby to zadziałało, musimy przed podłącze- hr = CeRapiInit();
niem telefonu do komputera wykonać kilka if (!FAILED(hr)) {
istotnych czynności, porównajcie proszę rów- hf = CeCreateFile(aFileName,
nież opis instalacji dla Waszego systemu pod GENERIC_READ,
adresem [4]. W przypadku mojego OpenSu- 0,
SE musiałem wykonać następujące kroki na NULL,
komputerze biurkowym: OPEN_EXISTING,
FILE_ATTRIBUTE_
• usunąłem wszystkie pakiety instalacyj- NORMAL,
ne Open Sync; 0);
• zabroniłem ładowania się modu- if (hf != INVALID_HANDLE_VALUE) {
łu ipaq, dodając blacklist ipaq do /etc/ while(CeReadFile(hf, buf, 1024,
modprobe.d/blacklist, bez tego komu- &nread, NULL) &&
nikacja IP między posiadanym przeze nread > 0)
mnie HTC3300, znanym również jako fwrite(buf, 1, nread,
T-Mobile MDA III, i Linuksem działa- stdout);
ła tylko wtedy, kiedy na telefonie włą- fflush(stdout);
czony był Internet sharing, co niemiło CeCloseHandle(hf);
odzwierciedliło się w rachunku telefo- }
nicznym; CeRapiUninit();
• zdefiniowałem uzyskiwanie adresu IP } /* if (!FAILED(hr)) */
na interfejsie sieciowym rndis0 poprzez Rysunek 2. Wynik działania programu pstatus z } /* wcat() */
klienta DHCP. pakietu rapi2-tools

www.sdjournal.org 25
Programowanie Windows Mobile

Listing 3. Pełny kod programu rapicat.c wykorzystującego funkcję wcat()

#include <stdio.h> }
#include <rapi.h> }
#ifdef _WIN32 return -1;
typedef char t_ucs2le; }
#else
# include <iconv.h> void ucs2le_close(t_ucs2le *aHandle) {
# include <langinfo.h> if (aHandle->from != _ICWRONG && aHandle->from != NULL)
# include <locale.h> iconv_close(aHandle->from);
# include <string.h> if (aHandle->to != _ICWRONG && aHandle->to != NULL)
iconv_close(aHandle->to);
typedef struct { memset(aHandle, 0, sizeof(t_ucs2le));
iconv_t from; }
iconv_t to;
} t_ucs2le; static size_t _ucs2le_len(LPCWSTR aWStr) {
#endif LPCWSTR ptr;
size_t retval;
const WCHAR WCHAR0 = (WCHAR) 0;
static const size_t _WRONG = (size_t) -1; retval = 0; for(ptr = aWStr; *ptr != WCHAR0; ptr++)
retval++;
#ifdef _WIN32 return retval;
int ucs2le_open(t_ucs2le *theHandle) { return 0; } }

void ucs2le_close(t_ucs2le *theHandle) { } int ucs2le_from(const t_ucs2le *aHandle,


char *theString,
int ucs2le_from(const t_ucs2le *aHandle, LPCWSTR aWStr,
char *theString, size_t aMaxNum) {
LPCWSTR aWStr, iconv_t h;
size_t aMaxNum) { size_t inbytesleft, outbytesleft, iret;
if (wcstombs(theString, aWStr, aMaxNum) != _WRONG) { char *inbuf, *outbuf;
theString[aMaxNum] = '\0'; int retval;
return 0;
} retval = -1;
return -1; if ((h = aHandle->from) != NULL && h != _ICWRONG) {
} inbytesleft = _ucs2le_len(aWStr);
if (aMaxNum < inbytesleft) inbytesleft = aMaxNum;
int ucs2le_to(const t_ucs2le *aHandle, inbytesleft *= 2;
LPWSTR theWString, outbytesleft = aMaxNum;
const char *anStr, inbuf = (char *) aWStr;
size_t aMaxNum) { outbuf = theString;
if (mbstowcs(theWString, anStr, aMaxNum) != _WRONG) { do {
theWString[aMaxNum] = WCHAR0; iret = iconv(h, &inbuf, &inbytesleft, &outbuf,
return 0; &outbytesleft);
} } while (iret != _WRONG && inbytesleft > 0);
return -1; *outbuf = '\0';
} if (inbytesleft <= 0) retval = 0;
}
#else return retval;
}
static const iconv_t _ICWRONG = (iconv_t) -1;
int ucs2le_to(const t_ucs2le *aHandle,
int ucs2le_open(t_ucs2le *theHandle) { LPWSTR theWString,
static const char UCS2LE[] = "UCS-2LE"; const char *anStr,
const char *curcp; size_t aMaxNum) {
iconv_t h;
setlocale(LC_ALL, ""); size_t inbytesleft, outbytesleft, iret;
curcp = nl_langinfo(CODESET); char *inbuf, *outbuf;
if (curcp != NULL && *curcp != '\0') { LPWSTR ptr;
theHandle->from = iconv_open(curcp, UCS2LE); int retval;
if (theHandle->from != _ICWRONG) {
theHandle->to = iconv_open(UCS2LE, curcp); retval = -1;
if (theHandle->to != _ICWRONG) return 0; if ((h = aHandle->to) != NULL && h != _ICWRONG) {

26 SDJ Extra 34 Biblia


RAPI

zawartość bufora na standardowe wyj- raczej użycie pliku nagłówkowego Win- dla urządzeń przenośnych pracują wyłącz-
ście (stdout) naszego programu aż napo- dows.h, w którym dołączenie do WTypes.h nie w oparciu o wchar_t. Za pomocą funk-
tkamy koniec pliku. Ostatecznie zamyka- jest również zawarte. Pod systemami unik- cji standardowej biblioteki C mbstowcs() i
my plik funkcją CeCloseHandle(), patrz sowymi wyposażonymi w librapi2 typy te wcstombs() można zamienić łańcuch zna-
Listing 2. Czytelnicy, którzy już otarli zdefiniowane są w pliku nagłówkowym ków char na wchar_t lub odwrotnie. Ani
się o programowanie WINAPI, zapewne synce_types.h, należącym do pakietu lib- typ wchar_t, ani obydwie funkcje do kon-
od razu zauważą, że program pisany dla synce-devel i dołączanym w pliku nagłów- wersji nie są tworami specyficznymi dla
biurkowej wersji MS Windows wyglądał- kowym rapi.h. Nieco więcej miejsca bę- WINAPI i są doskonale znane w świecie in-
by niemal identycznie. Z przedstawione- dziemy musieli poświęcić typowi LPCWSTR, nych systemów operacyjnych. W zależno-
go listingu należałoby jedynie usunąć wy- który pod MS Windows odpowiada typo- ści od systemu typ wchar_t jest jednak róż-
wołania CeRapiInit() i CeRapiUninit() wi wskaźnikowemu const wchar_t *, nie odzwierciedlany. Różnice zaczynają się
oraz skorzystać z funkcji CreateFile(), czyli wskaźnikowi na łańcuch tzw. szero- już przy samym rozmiarze typu; o ile pod
ReadFile() i CloseHandle() zamiast z za- kich znaków, zawierających w swoich ze- Linuksem są to 4 bajty, to pod MS Win-
stosowanych funkcji z przedrostkiem Ce. stawach narodowe litery wielu języków, dows już tylko 2. Dalej różnice mogą doty-
Pozostałym czytelnikom należy się jednak absolutny obowiązek dla każdej nowocze- czyć kolejności bajtów – BIG i LITTLE EN-
kilka słów wyjaśnienia. DWORD, HRESULT i snej aplikacji wielojęzykowej. Biblioteka DIAN, nie wspominając już o samych stro-
HANDLE to stosowane powszechnie w świe- WINAPI dla systemów biurkowych niemal nach kodowych znaków. Jeśli pod np. Li-
cie MS Windows typy danych odpowiada- w całości pracuje w oparciu o szeroki ze- nuksem weźmiemy łańcuch znaków char
jące int32_t lub uint32_t. Pod MS Win- staw znaków, jeśli program skompilowano w aktualnej stronie kodowej i przetłuma-
dows typy te zdefiniowane są w pliku na- ze zdefiniowanym makrem UNICODE (opcja czymy go za pomocą mbstowcs na łańcuch
główkowym WTypes.h, choć zalecane jest -DUNICODE kompilatora), wersje Windows wchar_t, to ze 100% pewnością otrzyma-

Listing 3 cd. Pełny kod programu rapicat.c wykorzystującego funkcję wcat()

inbytesleft = strlen(anStr); > 0)


if (aMaxNum < inbytesleft) inbytesleft = aMaxNum; fwrite(buf, 1, nread, stdout);
outbytesleft = aMaxNum * 2; fflush(stdout);
inbuf = (char *) anStr; CeCloseHandle(hf);
outbuf = (char *) theWString; }
do { }
iret = iconv(h, &inbuf, &inbytesleft, &outbuf,
&outbytesleft); int main(int argc, char **argv) {
} while (iret != _WRONG && inbytesleft > 0); WCHAR wfn[FILENAME_MAX+1];
ptr = (LPWSTR) outbuf; t_ucs2le hw;
*ptr = WCHAR0; HRESULT hr;
if (inbytesleft <= 0) retval = 0; int retval, i;
}
return retval; memset(&hw, 0, sizeof(hw));
} hr = CeRapiInit();
if (FAILED(hr)) { retval = -1; goto end; }
#endif if ((retval = ucs2le_open(&hw)) != 0) goto end;
for(i = 1; i < argc; i++) {
void wcat(LPCWSTR aFileName) { if ((retval = ucs2le_to(&hw, wfn, argv[i], FILENAME_
char buf[1024]; MAX)) != 0) goto end;
HANDLE hf; wcat(wfn);
DWORD nread; }
end:
hf = CeCreateFile(aFileName, GENERIC_READ, 0, NULL, ucs2le_close(&hw);
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, if (!FAILED(hr)) CeRapiUninit();
0); return retval;
if (hf != INVALID_HANDLE_VALUE) { }
while(CeReadFile(hf, buf, 1024, &nread, NULL) && nread

Listing 4. Ustawienie środowiska dla Microsoft Visual C wywoływanego z linii komend

@ECHO OFF inc;%INCLUDE%


SET VC=%ProgramFiles%\Microsoft Visual Studio 9.0\VC SET LIB=%VC%\lib;%MSSDK%\lib;%ACTIVESYNC%\lib;%LIB%
SET MSSDK=%ProgramFiles%\Microsoft SDKs\Windows\v6.1 SET PATH=%VC%\bin;%VC%\..\Common7\IDE;%MSSDK%\
SET ACTIVESYNC=%VC%\SmartDevices\SDK\ActiveSync bin;%SystemRoot%\Microsoft.NET\
SET TARGETCPU= Framework\v2.0.50727;%PATH%
SET INCLUDE=%VC%\include;%MSSDK%\include;%ACTIVESYNC%\

www.sdjournal.org 27
Programowanie Windows Mobile

ny łańcuch nie będzie mógł zostać zasto-


Listing 5. Fragment programu rapicat.c w wersji bez rapi.h i bez rapi.lib sowany do wywoływanych za pomocą RA-
#include <stdio.h> PI funkcji Windows CE. Taki numer przej-
#include <windows.h> dzie tylko wtedy, jeśli tworzymy i kompilu-
typedef HRESULT (*t_cerapiinit)(void); jemy nasz program na biurkowej wersji MS
typedef HRESULT (*t_cerapiuninit)(void); Windows. To, co Microsoft nazywa szero-
typedef HANDLE (*t_cecreatefile)(LPCWSTR lpFileName, kim zestawem znaków, naprawdę jest stro-
DWORD dwDesiredAccess, ną kodową UCS-2LE, na szczęście dosko-
DWORD dwShareMode, nale znaną dostępnej chyba na każdej plat-
LPSECURITY_ATTRIBUTES lpSecurityAttributes, formie bibliotece GNU Iconv. Aby wywo-
DWORD dwCreationDisposition, łać pod Linuksem przedstawioną na Listin-
DWORD dwFlagsAndAttributes, gu 2 funkcję wcat(), musimy zdefiniować
HANDLE hTemplateFile); nazwę pliku jako łańcuch znaków char, a
typedef BOOL (*t_cereadfile)(HANDLE hFile, następnie za pomocą funkcji iconv() za-
LPVOID lpBuffer, mienić go z aktualnej strony kodowej sys-
DWORD nNumberOfBytesToRead, temu na łańcuch znaków w stronie kodo-
LPDWORD lpNumberOfBytesRead, wej UCS-2LE. Pod MS Windows możemy
LPOVERLAPPED lpOverlapped); z powodzeniem zamiast iconv() wywołać
typedef BOOL (*t_ceclosehandle)(HANDLE hObject); standardową funkcję mbstowcs().
t_cerapiinit pCeRapiInit; Na Listingu 3 przedstawiono nieco
t_cerapiuninit pCeRapiUninit; uproszczony kod programu wykorzystują-
t_cecreatefile pCeCreateFile; cego funkcję wcat() do wyświetlania za-
t_cereadfile pCeReadFile; wartości pliku. Poprzez odpytanie, czy
t_ceclosehandle pCeCloseHandle; zdefiniowano makro _WIN32, podzielono
void wcat(LPCWSTR aFileName) { program na dwie części; jedną dla Micro-
char buf[1024]; soft Windows i drugą dla pozostałych sys-
HANDLE hf; temów operacyjnych, _WIN32 zdefiniowane
DWORD nread; jest standardowo w bodaj każdym kompila-
hf = pCeCreateFile(aFileName, GENERIC_READ, 0, NULL, torze C dla platformy Microsoftu. Funkcja
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); ucs2le_open() przeprowadza inicjaliza-
if (hf != INVALID_HANDLE_VALUE) { cję środowiska do tłumaczenia łańcuchów
while(pCeReadFile(hf, buf, 1024, &nread, NULL) && nread > 0) znaków z i na stronę kodową UCS-2LE. W
fwrite(buf, 1, nread, stdout); wersji dla Windows funkcja ta nie musi ro-
fflush(stdout); bić nic, ponieważ korzystamy ze standar-
pCeCloseHandle(hf); dowych narzędzi, nie wymagających żad-
} nych inicjalizacji. Choć definiowanie i ko-
} rzystanie z pustych funkcji może wydawać
int main(int argc, char **argv) { się nieco rozrzutne, to jednak bardzo nam
WCHAR wfn[FILENAME_MAX+1]; to ułatwia kod. Poza tym w dobrze zorgani-
t_ucs2le hw; zowanym programie funkcja ta będzie wy-
HRESULT hr; wołana tylko raz i nie stracimy na nią zbyt
HMODULE hdll; wiele czasu. W wersji dla pozostałych sys-
int retval, i; temów operacyjnych ucs2le_open() ładu-
memset(&hw, 0, sizeof(hw)); je za pomocą iconv_open() tablice kon-
if ((hdll = LoadLibrary(TEXT("rapi.dll"))) != NULL) { wersji z aktualnej strony kodowej systemu
pCeRapiInit = (t_cerapiinit) GetProcAddress(hdll, TEXT("CeRapiInit")); na UCS-2LE i odwrotnie. Stąd też różnice
pCeRapiUninit = (t_cerapiuninit) GetProcAddress(hdll, TEXT("CeRapiUninit")); w typie danych t_ucs2le, który w wypad-
pCeCreateFile = (t_cecreatefile) GetProcAddress(hdll, TEXT("CeCreateFile")); ku wykorzystania biblioteki iconv zawie-
pCeReadFile = (t_cereadfile) GetProcAddress(hdll, TEXT("CeReadFile")); ra dwa uchwyty do tablic kodowych, pod-
pCeCloseHandle = (t_ceclosehandle) GetProcAddress(hdll, TEXT("CeCloseHandle")); czas gdy pod Windows może on być do-
memset(&hw, 0, sizeof(hw)); wolny, np. króciutki typ char, ponieważ i
hr = pCeRapiInit(); tak nie jest on tam wykorzystywany. Funk-
if (FAILED(hr)) { retval = -1; goto end; } cja ucs2le_close() zwalnia zasoby zare-
if ((retval = ucs2le_open(&hw)) != 0) goto end; zerwowane przez ucs2le_open(), czyli
for(i = 1; i < argc; i++) { pod Windowsem jest to ponownie pusta i
if ((retval = ucs2le_to(&hw, wfn, argv[i], FILENAME_MAX)) != 0) goto end; bezczynna funkcja, podczas gdy na pozo-
wcat(wfn); stałych systemach operacyjnych wywoły-
} wana jest iconv_close(), zwalniająca pa-
end: mięć zajmowaną przez tablice konwersji.
ucs2le_close(&hw); Faktyczną zamianę kodowania znaków
if (!FAILED(hr)) pCeRapiUninit(); zrealizowano w funkcjach ucs2le_from()
FreeLibrary(hdll); oraz ucs2le_to(). Dla Windows wykorzy-
} stano standardowe funkcje wcstombs() i
mbstowcs(). Pozostałe systemy mozolnie

28 SDJ Extra 34 Biblia


RAPI

wywołują funkcję iconv() aż do przekon-


wertowania całego łańcucha znaków lub Listing 5 cd. Fragment programu rapicat.c w wersji bez rapi.h i bez rapi.lib
wyczerpania się przewidzianego dla doce- else {
lowego łańcucha rozmiaru bufora. W funk- fprintf(stderr, "Can't find rapi.dll\n");
cji main() dla każdej z nazw plików poda- retval = -1;
nej w linii komend w aktualnej stronie ko- }
dowej systemu dokonywana jest konwer- return retval;
sja na stronę kodową UCS-2LE, której wy- }
nik umieszczany jest w buforze wfn. Na-
stępnie dla nazwy pliku z wfn wywoływa- Listing 6. Funkcja kopiująca plik z PDA na komputer stacjonarny
na jest znana nam już funkcja wcat(). Pro- #include <stdio.h>
szę zwrócić uwagę, że funkcja wcat() zo- #include <rapi.h>
stała nieco zmodyfikowana, wywołania do void wget(LPCWSTR aPDAFileName, const char *aLocFileName) {
CeRapiInit() oraz CeRapiUninit() prze- char buf[1024];
niesiono do funkcji main(). Omawiany FILE *f;
program może zostać wywołany z wieloma HRESULT hr;
parametrami linii poleceń, czyli dla wie- HANDLE hf;
lu plików. Wywołując wcat() dla każdego DWORD nread;
z plików, bez tej zmiany za każdym razem hr = CeRapiInit();
dokonywalibyśmy inicjalizacji i zwalniania if (!FAILED(hr)) {
środowiska RAPI, co prawdopodobnie nie hf = CeCreateFile(aPDAFileName, GENERIC_READ, 0, NULL,
pozostałoby bez wpływu na szybkość dzia- OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
łania programu. if (hf != INVALID_HANDLE_VALUE) {
Przedstawione na listingach przykłady są f = fopen(aLocFileName, "wb");
z racji ograniczonego miejsca mocno okrojo- if (f != NULL) {
nymi i często zmienionymi wersjami progra- while(CeReadFile(hf, buf, 1024, &nread, NULL) && nread > 0)
mów dostępnych na stronie domowej opisy- fwrite(buf, 1, nread, f);
wanego projektu [0]. W razie wątpliwości fclose(f);
uprzejmie proszę brać pod uwagę kod źró- }
dłowy ze strony projektu, a nie przedstawio- CeCloseHandle(hf);
ne okrojone przykłady. }
CeRapiUninit();
Kompilacja }
Kompilacja pod Linuksem jest bajecznie pro- } /* wget() */
sta i sprowadza się do wywołania
Listing 7. Funkcja kopiująca plik z komputera stacjonarnego na PDA
gcc -o rapicat rapicat.c -lrapi #include <stdio.h>
#include <rapi.h>
zakładając, że źródła programu z Listingu 3 void wput(LPCWSTR aPDAFileName, const char *aLocFileName) {
zapisaliśmy w pliku rapicat.c. char buf[1024];
Kompilacja pod MS Windows mogłaby być FILE *f;
niemal równie prosta: HANDLE hf;
size_t nread;
cl.exe -D_CRT_SECURE_NO_DEPRECATE DWORD nwritten, remaining;
rapicat.c rapi.lib const char *ptr;
if ((f = fopen(aLocFileName, "rb")) != NULL) {
jeśli spełnione zostałyby następujące warunki: hf = CeCreateFile(aPDAFileName, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
• posiadamy MS Visual Studio w wersji if (hf != INVALID_HANDLE_VALUE) {
Standard lub droższej z zainstalowanym while(!feof(f) && (nread = fread(buf, 1, 1024, f)) > 0) {
Windows Mobile SDK; ptr = buf;
• mamy dobrze poustawiane ścieżki w remaining = (DWORD) nread;
zmiennych środowiskowych PATH, LIB while(remaining > 0 && CeWriteFile(hf, ptr, remaining, &nwritten, NULL)) {
oraz INCLUDE. remaining -= nwritten;
ptr += nwritten;
Ostatni warunek jest z reguły spełniony, } /* while */
jeśli korzystamy z okna konsoli MS Vi- } /* while */
sual Studio, w razie wątpliwości może- CeCloseHandle(hf);
my skorzystać ze skryptu przedstawione- }
go na Listingu 4. Skrypt należy wywołać fclose(f);
w oknie cmd.exe, zanim zaczniemy wywo- }
ływać kompilator cl.exe, program konsoli- } /* wput() */
dujący link.exe czy interpreter plików ma-
ke nmake.exe. W zależności od posiadanej

www.sdjournal.org 29
Programowanie Windows Mobile

wersji i sposobu instalacji MS Visual Stu- my ją zwolnić za pomocą funkcji Win32 niki do funkcji, przedstawiono na Listingu
dio konieczne mogą być drobne zmiany w FreeLibrary. Fragment zmienionego pro- 5. Typy t_cerapiinit, t_cerapiuninit,
skrypcie. gramu rapicat.c, wykorzystującego wskaź- t_cecreatefile, t_cereadfile oraz t_
Dużo trudniej jest spełnić warunek
pierwszy. MS Visual Studio Standard Edi- Listing 8. Specjalne katalogi na PDA
tion to pakiet komercyjny, wcale nie tani i
– delikatnie mówiąc – nieco zbyt duży i #include <stdio.h>
skomplikowany jak na nasze skromne po- #include <rapi.h>
trzeby skompilowania małej aplikacji kon-
solowej. Wprawdzie pod adresem [8] moż- int main() {
na ściągnąć bezpłatnie ok. 1.5GB obraz t_ucs2le ucs2le;
płyty instalacyjnej DVD zawierającej za- WCHAR wpath[MAX_PATH+1];
równo najnowszy kompilator MS Visual char path[MAX_PATH+1];
C, jak i niezbędny Microsoft SDK, brak w HRESULT hr;
nim jednak narzędzi dla urządzeń mobil- size_t i;
nych, w tym plików rapi.h oraz rapi.lib. Pli-
ki te zawarte są w pakietach Windows Mo- if (ucs2le_open(&ucs2le) == 0) {
bile SDK, np. Windows Mobile 6 SDK Re- hr = CeRapiInit();
fresh.msi, również bezpłatnie dostępnym if (!FAILED(hr)) {
w strefie download Microsoftu, który in- for (i = 0; i < 64; i++) {
staluje się tylko wtedy, kiedy mamy już path[0] = '\0';
zainstalowaną odpowiednią płatną wer- if (CeGetSpecialFolderPath(i, MAX_PATH, wpath)) {
sję MS Visual Studio. Można wprawdzie wpath[MAX_PATH] = 0;
pokusić się o rozpakowanie pliku insta- if (ucs2le_from(&ucs2le, path, wpath, MAX_PATH) == 0)
lacyjnego MSI za pomocą np. programu fprintf(stdout, "%04x %s\n", i, path);
dark.exe zawartego w pakiecie WiX [6], }
ale zdaje się, że otrzemy się wtedy o grani- }
cę legalności. CeRapiUninit();
Dodatkowo pliki rapi.lib oraz rapi.h zo- }
stały stworzone z myślą o kompilatorze i ucs2le_close(&ucs2le);
środowisku programistycznym Microso- }
ftu i wcale nie jest pewne, że uda nam się return 0;
je bez problemu zastosować np. z kompi- }
latorem MinGW [5]. Zamiast narażać Was
na rozterki prawne i poruszanie się po nie-
pewnym gruncie konsolidacji oprogramo-
wania różnych producentów, pragnę za-
proponować inne rozwiązanie, polegające
na samodzielnym ładowaniu funkcji bez-
pośrednio z biblioteki dynamicznej ra-
pi.dll za pomocą standardowych funkcji
Win32 LoadLibrary oraz GetProcAddress.
LoadLibrary ładuje bibliotekę dynamicz-
ną z podanego pliku, zwracając uchwyt
do niej. Za pomocą GetProcAddress mo-
żemy natomiast otrzymać wskaźnik do
funkcji o podanej nazwie w bibliotece dy-
namicznej z uchwytu zwróconego przez
LoadLibrary.
Jak już nie potrzebujemy żadnych funk-
cji z biblioteki dynamicznej, to może-

Rysunek 3. Program z Listingu – uruchomiony z


poziomu Linuksa na T-Mobile MDA III (HTC3000)
z niemiecką wersją Windows Mobile 6 Rysunek 4. Przykładowe programy uruchomione na MS Windows Vista

30 SDJ Extra 34 Biblia


RAPI

ceclosehandle to typy wskaźników na powiednio pCeRapiInit, pCeRapiUninit, Zamiast 1.5GB wersji MS Windows SDK
funkcje, odpowiadające de facto defini- pCeCreateFile, inicjalizowanych w możemy skorzystać ze zgrabnego kompila-
cjom samych funkcji RAPI, które moż- funkcji main() za pomocą wywołań do tora gcc zawartego w pakiecie MinGW [5].
na znaleźć w nieużywanym teraz przez GetProcAddress(). Taki program może- Minimalna instalacja MinGW polega na
nas pliku nagłówkowym rapi.h. Do stwo- my już skompilować w dużo prostszy spo- ściągnięciu pakietów gcc, binutils, Win32
rzenia takich definicji z braku tego pliku sób, używając API oraz opcjonalnie make, rozpakowa-
pod Windowsem możemy posłużyć się niu ich do jednego katalogu i ustawieniu
albo dokumentacją MSDN [1], albo pli- cl.exe -D_CRT_SECURE_NO_DEPRECATE rapicat.c ścieżki poszukiwań na podkatalog bin. Po-
kiem rapi.h zawartym w pakiecie libra- tem możemy nasz program skompilować,
pi2 [2]. Zamiast funkcji CeRapiInit(), które to wywołanie bez problemu działa używając:
CeRapiUninit(), CeCreateFile() itd. również na bezpłatnych wersjach kompilato-
używamy zmiennych wskaźnikowych, od- rów Microsoftu. gcc.exe -o rapicat.exe rapicat.c

Listing 9. Wyświetlanie zawartości katalogów PDA

#include <stdio.h> char c;


#include <string.h>
#include <rapi.h> num = 0;
list = NULL;
static void _print_listfile(const t_ucs2le *ucs2le, const if (aPath == NULL || (len = strlen(aPath)) == 0)
CE_FIND_DATA *anEntry) { strncpy(path, "*", MAX_PATH);
long long int fsiz; else {
size_t fsiz1; c = aPath[len-1];
DWORD flags; if (c == '/' || c == '\\') {
char at[11], filename[MAX_PATH+1]; strncpy(path, aPath, MAX_PATH-1); strcat(path, "*");
}
if (anEntry != NULL) else if (c != '*' &&
strncpy(at, "----------", 10); ucs2le_to(ucs2le, wpath, aPath, MAX_PATH) ==
flags = anEntry->dwFileAttributes; 0 &&
if ((flags & FILE_ATTRIBUTE_ARCHIVE) != 0) at[0] = 'A'; (CeGetFileAttributes(wpath) & FILE_ATTRIBUTE_
if ((flags & FILE_ATTRIBUTE_COMPRESSED) != 0) at[1] = DIRECTORY) != 0) {
'C'; strncpy(path, aPath, MAX_PATH-2); strcat(path, "\\*");
if ((flags & FILE_ATTRIBUTE_DIRECTORY) != 0) at[2] = 'D'; }
if ((flags & FILE_ATTRIBUTE_HAS_CHILDREN) != 0) at[3] = else strncpy(path, aPath, MAX_PATH);
'+'; }
if ((flags & FILE_ATTRIBUTE_HIDDEN) != 0) at[4] = 'H'; path[MAX_PATH] = '\0';
if ((flags & FILE_ATTRIBUTE_INROM) != 0) at[5] = 'O'; if (ucs2le_to(ucs2le, wpath, path, MAX_PATH) == 0 &&
if ((flags & FILE_ATTRIBUTE_NORMAL) != 0) at[6] = 'N'; CeFindAllFiles(wpath, FAF, &num, &list) && list !=
if ((flags & FILE_ATTRIBUTE_READONLY) != 0) at[7] = 'R'; NULL)
if ((flags & FILE_ATTRIBUTE_SYSTEM) != 0) at[8] = 'S'; for(i = 0, ptr = list; i < num; i++, ptr++) _print_
if ((flags & FILE_ATTRIBUTE_TEMPORARY) != 0) at[9] = 'T'; listfile(ucs2le, ptr);
at[10] = '\0'; CeRapiFreeBuffer(list);
fsiz = anEntry->nFileSizeHigh; }
fsiz <<= 32;
fsiz |= anEntry->nFileSizeLow; int main(int argc, char **argv) {
if(ucs2le_from(ucs2le, filename, anEntry->cFileName, t_ucs2le ucs2le;
MAX_PATH) == 0) { HRESULT hr;
fsiz1 = (size_t) fsiz; int i;
fprintf(stdout, "%s %10ld %s\n", at, fsiz1, filename);
} if (ucs2le_open(&ucs2le) == 0) {
} hr = CeRapiInit();
} if (!FAILED(hr)) {
for(i = 1; i < argc; i++) _ls(&ucs2le, argv[i]);
static void _ls(const t_ucs2le *ucs2le, const char *aPath) { CeRapiUninit();
static const DWORD FAF = 0x00ff; }
WCHAR wpath[MAX_PATH+1]; ucs2le_close(&ucs2le);
char path[MAX_PATH+1]; }
CE_FIND_DATA *list; return 0;
const CE_FIND_DATA *ptr; }
size_t len;
DWORD num, i;

www.sdjournal.org 31
Programowanie Windows Mobile

Aby nie marnować zbytnio miejsca i nie łam do źródeł modułu rapiwrap.c i rapiw- plików do obsłużenia za pomocą programu
komplikować opisu, pozwolę sobie dalsze rap.h. rapicat.
przykłady przedstawić w wersji bez dy- Działanie programu możemy sprawdzić,
namicznie ładowanych funkcji, czyli tak, wywołując np. rapicat "/Windows/Menu Kolejne programy
jak byśmy mieli zarówno plik nagłówko- Start/Programs/Python.lnk" lub innego pli- Jeśli potrafimy wyświetlić zawartość pli-
wy rapi.h, jak i niezbędną dla MS Visual ku. W dalszej części artykułu dowiemy się ku z PDA na komputerze stacjonarnym, to
C bibliotekę rapi.lib. Kod źródłowy do ar- również, jak wyświetlić zawartość katalogu również potrafimy skopiować ten plik. Wy-
tykułu, dostępny pod adresem [0] , może Windows CE. starczy w tym celu wywołać rapicat plik_
być jednak kompilowany na obydwa sposo- Dopóki się tego nie nauczymy, musimy pda > plik_lokalny. Dużo korzystniej by-
by, w zależności od zdefiniowania makra posłużyć się programem pls z pakietu ra- łoby jednak zastąpić stałą stdout zmienną
RAPI _ SDK (opcja kompilatora -DRAPI _ pi2-tools lub eksploratorem Active Sync pod typu FILE*, która reprezentowałaby plik
SDK) podczas kompilacji. Ciekawych odsy- Windows w celu znalezienia ciekawszych otwarty za pomocą funkcji fopen(). Li-
sting 6 przedstawia funkcję wget() – pro-
Listing 10. Zdalne uruchomienie programu na PDA szę nie mylić ze znanym programem GNU
– do kopiowania pliku z PDA na komputer
#include <rapi.h> stacjonarny.
#include <stdio.h> W ramach ćwiczenia możecie spró-
#include <string.h> bować znaleźć 5 szczegółów różniących
#include "ucs2le.h" funkcję wget() od przedstawionej na Li-
int main(int argc, char **argv) { stingu 2 funkcji wcat(). Program do ko-
WCHAR wprogname[MAX_PATH+1], wparams[512]; piowania pliku z komputera stacjonarne-
t_ucs2le ucs2le; go na PDA przedstawiono na Listingu 7.
PROCESS_INFORMATION info; Podstawowa różnica w stosunku do po-
HRESULT hr; przedniego programu to inne parametry
const WCHAR *pwparams; przekazane do funkcji CeCreateFile():
int retval; GENERIC_WRITE i CREATE_ALWAYS zamiast
short isok; GENERIC_READ i OPEN_EXISTING, oraz uży-
retval = -1; cie funkcji CeWriteFile() do zapisu da-
memset(&info, 0, sizeof(PROCESS_INFORMATION)); nych na PDA zamiast CeReadFile() do
if (ucs2le_open(&ucs2le) == 0) { ich odczytu. Za pomocą funkcji RAPI
if (argc > 1) { CeDeleteFile(LPCWSTR *aPDAFileName)
if (ucs2le_to(&ucs2le, wprogname, argv[1], MAX_PATH) == 0) { można skasować plik na PDA. Parametr
isok = 0; aPDAFileName zarówno funkcji wput(),
if (argc > 2) { wget(), jak i CeDeleteFile() to wskaźnik
if (ucs2le_to(&ucs2le, wparams, argv[2], 511) == 0) { na łańcuch znaków w omawianej już stro-
pwparams = wparams; nie kodowej UCS-2LE, który można utwo-
isok = 1; rzyć za pomocą przedstawionej na Listingu
} 2 funkcji ucs2le_to().
} Listing 8 przedstawia zastosowanie funk-
else { cji CeGetSpecialFolderPath() zwracającej
pwparams = NULL; nazwy specjalnych ścieżek dostępnych na
isok = 1; PDA. Identyfikatorami specjalnych ścieżek
} są stałe takie jak CSIDL_PROGRAMS, CSIDL_
if (isok) { PERSONAL, CSIDL_STARTUP czy CSIDL_
hr = CeRapiInit(); RECENT zdefiniowane w rapi.h (librapi2)
if (!FAILED(hr)) { lub ShlObj.h (Windows, ten plik nagłówko-
if (CeCreateProcess(wprogname, pwparams, wy dołączany jest również do windows.h),
NULL, NULL, FALSE, 0, w programie zamiast stałych zastosowano
NULL, NULL, NULL, po prostu pętlę poprzez wszystkie ewentu-
&info)) { alnie możliwe wartości takich identyfikato-
if (CeCloseHandle != NULL) { rów. Wynik działania programu dla niemiec-
CeCloseHandle(info.hProcess); CeCloseHandle(info.hThread); kiej wersji Windows Mobile przedstawiono
} na Rysunku 3.
} Funkcja CeFindAllFiles(LPCWSTR dir,
CeRapiUninit(); DWORD flags, LPDWORD num, LPLPCE_FIND_
} DATA contents) zwraca w tablicy wskazy-
} wanej przez contents zawartość katalo-
} gu dir. Zwracanych jest maksymalnie tyle
} plików i podkatalogów dir, na ile pozwa-
else la zmienna wskazywana przez num, przy
} czym po wywołaniu funkcji znajduje się
return retval; w niej dokładna liczba elementów zwró-
} conych w contents. Parametr flags mo-
że zawierać połączone logicznym "lub" sta-

32 SDJ Extra 34 Biblia


RAPI

łe FAF_ATTRIBUTES, FAF_CREATION_TIME,
FAF_LASTACCESS_TIME itd., patrz również Listing 11. Typy danych oraz typy wskaźników do funkcji stosowanych do wysłania SMS z
Windows Mobile
definicje w pliku nagłówkowym rapi.h (li-
brapi2) lub rapitypes.h (Windows), dzięki struct _sms_address {
którym możemy ograniczyć ilość zwraca- int type;
nych informacji, wartość 255 umieszczona TCHAR address[256];
w parametrze powoduje zwrócenie wszyst- };
kich dostępnych informacji. Parametr dir struct _provider_specific {
to ścieżka do pliku lub katalogu, o którym DWORD options;
chcemy otrzymać informacje. Proszę pa- int class;
miętać, że jeśli parametr dir będzie zawie- BYTE not_needed[680];
rał nazwę katalogu bez znaku gwiazdki na };
końcu, to funkcja zwróci informacje tylko typedef
o tym katalogu, nie zaś o jego zawartości. HRESULT (*t_SmsOpen)(LPCTSTR protocol,
Parametr contents to wskaźnik na tabli- DWORD mode,
cę elementów typu CE_FIND_DATA, do któ- DWORD *smsHandle,
rego funkcja zwraca informacje o znajdu- HANDLE *not_used);
jących się w katalogu plikach i podkatalo- typedef
gach. Poniżej przedstawiono co ciekawsze HRESULT (*t_SmsSendMessage)(DWORD smsHandle,
pola struktury CE_FIND_DATA: const struct _sms_address *smscAddress,
const struct _sms_address *destAddress,
• to nazwa pliku lub podkata-
dFileName const SYSTEMTIME *validity,
logu, zakodowane rzecz jasna jako UCS- const BYTE *data,
2LE. DWORD datasize,
• nFileSizeHigh, nFileSizeLow to wiel- const BYTE *provider,
kość pliku w bajtach. Obydwa pola to DWORD providersize,
liczby typu int (DWORD w nomenklatu- int encoding,
rze Microsoftu), które razem symulu- DWORD options,
ją typ 64-o bitowy, pozwalający przed- DWORD *msgid);
stawić w dniu dzisiejszym niewyobra- typedef HRESULT (*t_SmsClose)(DWORD smsHandle);
żalny rozmiar pliku 17179869184 GB.
Aby przetłumaczyć obydwa pola na
rozmiar pliku, należy wykonać opera-
cję (((long long) nFileSizeHigh) <<
32) | nFileSizeLow.
• flags to zestaw połączonych logicznym
"lub" znaczników określających typ ele-
mentu, np. czy element jest plikiem
czy katalogiem, jeśli plikiem, to czy jest
skompresowany, ukryty, systemowy,
tylko do odczytu itp. Proszę sprawdzić
stałe FILE _ ATTRIBUTE _ DIRECTORY,
FILE _ ATTRIBUTE _ CO M PR ESSED,
FILE _ ATTRIBUTE _ HIDDEN, FILE _
ATTRIBUTE _ SYSTEM i inne zdefiniowa-
ne w pliku nagłówkowym rapi.h (libra-
pi2) lub WinNT.h (Windows).
• Trzy pola typu FILETIME określające
czas utworzenia pliku (ftCreation- Rysunek 5. GNUWINCE

W Sieci
• [0] Strona opisywanego projektu, http://cetoys.sourceforge.net;
• [1] Dokumentacja MSDN RAPI, http://msdn.microsoft.com/en-us/library/aa458022.aspx;
• [2] Strona projektu SynCE, http://www.synce.org;
• [3] Microsoft Active Sync, http://www.microsoft.com/windowsmobile/en-us/help/synchronize/ActiveSync-download.mspx;
• [4] http://www.synce.org/moin/SynceInstallation;
• [5] MinGW, http://www.mingw.org, informacje o instalacji pakietu http://www.mingw.org/wiki/HOWTO_Install_the_MinGW_GCC_Compiler_Suite;
• [6] WiX, http://wix.sourceforge.net;
• [7] CeGCC, http://cegcc.sourceforge.net;
• [8] Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1: BETA, adres aktualny dnia 2009-02-09, http://www.microsoft.com/
downloads/details.aspx?FamilyID=a91dc12a-fc94-4027-b67e6bab7c5226c&DisplayLang=en;
• [9] Introduction To Application Programming With SMS, http://msdn.microsoft.com/en-us/library/ms838228.aspx;
• [10] GNUWINCE, http://win-ce.voxware.com:28575/Development Tools.

www.sdjournal.org 33
Programowanie Windows Mobile

Time), ostatniego dostępu do pliku 116444736000000000LL) / 10000000, Kolejne zastosowanie RAPI to odczyt oraz
(ftLastAccessTime) oraz ostatniej gdzie 116444736000000000 to, jak ła- manipulacja rejestrem podłączonego do kom-
modyfikacji pliku (ftLastWriteTime). two się domyślić, ilość nanosekund putera urządzenia przenośnego. Doskona-
Windowsowy typ FILETIME to ilość na- między 1 stycznia 1601 i 1 stycznia łą kopalnią wiedzy o używaniu funkcji reje-
nosekund, która upłynęła od 1 stycz- 1970. Programiści API Win32 mo- stru są źródła dostępnego w ramach pakie-
nia 1601 roku, przedstawiona jako gą posłużyć się w zamian funkcją tu librapi2 [2] programu synce-registry. Za-
dwie liczby typu int dwHighDateTime FileTimeToSystemTime(). awansowani programiści API Win32 szybko
i dwLowDateTime symulujące razem odnajdą tam funkcje znane z programowa-
liczbę 64-o bitową. Zwykły czas ty- Przykładowy program wyświetlający zawar- nia wyposażonych w Windows komputerów
pu time _ t (ilość sekund od 1 stycz- tość katalogu na standardowym wyjściu biurkowych, tyle że w nazwach funkcji znaj-
nia 1970) możemy otrzymać, wykonu- przedstawiono na Listingu 9, na Rysunku 4 duje się przedrostek Ce. Ponieważ listingi pro-
jąc (((((long long) dwHighDateTime) przedstawiono zaś wynik jego działania pod gramów korzystających z rejestrów są z natu-
<< 32) | dwLowDateTime) - Windows Vista. ry rzeczy duże, zamiast namawiać wydawnic-
two na zadrukowywanie płacht papieru nie-
Listing 12. Funkcja wysyłająca SMS pod podany numer zrozumiałym kodem, postanowiłem odesłać
zainteresowanych do wspomnianych źródeł
HRESULT sendsms(const TCHAR *aNumber, const TCHAR *aMessage) { synce-registry lub do implementacji funkcji
struct _sms_address number; _reg() i _reg1() znajdujących się w module
struct _provider_specific p; rapi/rapi.c źródeł omawianego programu do-
t_SmsOpen pSmsOpen; stępnych pod adresem [0].
t_SmsSendMessage pSmsSendMessage; Pierwszym krokiem w dostępie do re-
t_SmsClose pSmsClose; jestru jest otwarcie klucza za pomocą
HMODULE hdll; CeRegOpenKeyEx(), np.
DWORD hsms, id;
HRESULT retval; HKEY key;
CeRegOpenKeyEx(HKEY_LOCAL_MACHINE,
retval = S_FALSE; TEXT("\Software\Microsoft"), 0, 0,
hdll = NULL; &key);
if (aNumber == NULL || aMessage == NULL) { retval = E_INVALIDARG; goto err; }
hdll = LoadLibrary(TEXT("sms.dll")); HKEY _ LOCAL _ MACHINE, podobnie jak HKEY _
if (hdll == NULL) { retval = E_HANDLE; goto err; } CURRENT _ USER czy HKEY _ CLASSES _ ROOT,
pSmsOpen = (t_SmsOpen) GetProcAddress(hdll, TEXT("SmsOpen")); to nazwy tzw. kluczy root różnych części reje-
if (pSmsOpen == NULL) { retval = E_FAIL; goto err; } stru, użytkownikom Windows zapewne zna-
pSmsSendMessage = (t_SmsSendMessage) GetProcAddress(hdll, ne jako skróty HKLM, HKCU czy HKCR .
TEXT("SmsSendMessage")); \Software\Microsoft to nazwa klucza w
if (pSmsSendMessage == NULL) { retval = E_FAIL; goto err; } części rejestru, oczywiście zakodowana jako
pSmsClose = (t_SmsClose) GetProcAddress(hdll, TEXT("SmsClose")); UCS-2LE. W zmiennej key funkcja zwraca
if (pSmsClose == NULL) { retval = E_FAIL; goto err; } uchwyt klucza, w powyższym przykładzie jest
retval = pSmsOpen(TEXT("Microsoft Text SMS Protocol"), 2, &hsms, NULL); to klucz HKLM\Software\Microsoft. Funkcja
if (retval != S_OK) goto err; CeReqQueryInfoKey() zwraca bliższe infor-
memset(&number, 0, sizeof(struct _sms_address)); macje na temat klucza: nazwy wszystkich
number.type = 1; /* International number type */ wartości oraz nazwy wszystkich podkluczy.
wcsncpy(number.address, aNumber, 255); number.address[256] = TEXT('\0'); CeRegEnumValue() odczytuje wartość klu-
memset(&p, 0, sizeof(struct _provider_specific)); cza o podanej nazwie. Wszystkie podklucze
p.options = 2; /* status report */ możemy potraktować rekurencyjnie poprzez
p.class = 1; /* message class */ ponowne wywołanie CeRegOpenKeyEx() i
retval = pSmsSendMessage(hsms, CeReqQueryInfoKey() dla tego samego klu-
NULL, /* default SMS center */ cza root i nazwy pełnej ścieżki podklucza (np.
&number, \Software\Microsoft\Windows CE Services,
NULL, zakładając, że funkcja zwróciła nazwę pod-
(PBYTE) aMessage, klucza Windows CE Services).
wcslen(aMessage) * sizeof(TCHAR), Po skorzystaniu z klucza, czyli po sko-
(PBYTE) &p, rzystaniu z funkcji CeRegOpenKeyEx() i
sizeof(struct _provider_specific), CeReqQueryInfoKey(), należy zamknąć
0, klucz funkcją CeRegCloseKey().
0, Funkcja CeCreateProcess() uruchamia
&id); zdalnie program na PDA. Pierwszym parame-
if (retval != S_OK) { pSmsClose(hsms); goto err; } trem funkcji jest nazwa lub ścieżka uruchamia-
retval = pSmsClose(hsms); nego programu, drugim parametrem są ewen-
err: tualne parametry linii poleceń. Programy Win-
if (hdll != NULL) FreeLibrary(hdll); dows, w odróżnieniu od zwykłych programów
return retval; C uruchamianych za pomocą funkcji main(),
} otrzymują wszystkie parametry jako jeden łań-
cuch znaków, a nie jako tablicę stringów.

34 SDJ Extra 34 Biblia


RAPI

Ostatni parametr używany jest do Oczywiście nie ma nic w tym złego, szczę- pomocą LoadLibrary() i GetProcAddress()
otrzymania deskryptorów uruchomio- śliwi użytkownicy oprogramowania Micro- oraz na samodzielnym i mocno uproszczo-
nego procesu i wątku, które za pomo- softu mogą z powodzeniem przerwać czy- nym zdefiniowaniu typów danych wymaga-
cą CeCloseHandle() należy po skorzysta- tanie rozdziału w tym miejscu i spojrzeć nych przez SmsSendMessage(), patrz Listing
niu z funkcji CeCreateProcess() pozamy- na dostarczony wraz z MS Windows Mobi- 11. Pierwszym niezbędnym typem zmien-
kać. Listing 10 przedstawia program, któ- le SDK plik przykładowy Samples/Common/ nej jest struktura _sms_address, określają-
rego pierwszy parametr linii poleceń ozna- cpp/Win32/CellCore/sms/HelloSMS/main.cpp ca w polu address numer telefonu, pod któ-
cza nazwę programu na PDA, drugi para- (zawarty przynajmniej w MS Windows Mo- ry chcemy wysłać SMS. Najrozsądniejszą war-
metr linii poleceń przekazywany jest nato- bile 6 SDK Refresh) oraz rzecz jasna na do- tością dla pola type wydaje się 1, oznacza-
miast do uruchomionego programu jako je- kumentację MSDN funkcji. jąca międzynarodowy numer telefonu (np.
go parametry. Wszystkim pozostałym chciałbym zapropo- +48123456789). Drugim typem jest struk-
Praktyczny przykład zastosowania zdal- nować rozwiązanie bazujące na dynamicznym tura _provider_specific, określająca typ
nego uruchomienia procesu na PDA ładowaniu wspomnianych trzech funkcji za wiadomości w polu class (1 dla SMS). Pole
przedstawiono w skrypcie shell-owym
synce-install-cab dostarczanym wraz z Listing 13. Program wykorzystujący funkcję sendsms()
pakietem rapi2-tools lub znajdującym się
wśród źródeł librapi2, służącym do insta- int WINAPI WinMain(HINSTANCE hInstance,
lacji tzw. plików typu cabinet na urządze- HINSTANCE hPrevInstance,
niu przenośnym z poziomu komputera LPWSTR szCmdLine,
biurkowego. int iCmdShow) {
Skrypt ten kopiuje plik instalacyjny ca- TCHAR number[256];
binet za pomocą programu pcp (działające- const TCHAR *cp;
go podobnie do tego przedstawionego na TCHAR *p;
Listingu 7) do pliku /Windows/AppMgr/ int i;
Install/synce-install.cab i zdalnie uruchamia
za pomocą prun – podobnego do opisywa- for(i = 0, cp = szCmdLine, p = number;
nego tu programu z Listingu 10 – aplikację i < 255 &&
Windows CE wceload.exe. Podobnie zdalne *cp != TEXT('\0') &&
uruchomienie unload.exe z nazwą pakietu *cp != TEXT(' ') &&
jako parametrem linii poleceń spowoduje je- *cp != TEXT('\t');
go odinstalowanie (spójrz również na skrypt i++, cp++, p++)
synce-remove-program). Listę zainstalowa- *p = *cp;
nych na urządzeniu programów możemy *p = TEXT('\0');
znaleźć, przeglądając zawartość klucza reje- if (*cp != TEXT('\0')) cp++;
stru HKLM, Software\Apps. sendsms(number, cp);
return 0;
SMS z komputera PC } /* WinMain() */
Wiemy już, jak uruchomić zdalnie program
na urządzeniu przenośnym z poziomu kom- Listing 14. Kompilacja programu do wysyłania SMS za pomocą CeGCC
putera PC (Listing 10), choć należy przy-
znać, że ilość programów nadających się /opt/cegcc/bin/arm-wince-cegcc-gcc \
do zdalnego uruchamiania nie jest aż taka -DUNDER_CE -D_WIN32_IE=0x0400 \
oszałamiająca. Spróbujmy uzupełnić te bra- -o sms.exe sendsms.c smsapp.c
ki dwoma własnymi aplikacjami i zacznijmy
od programu wysyłającego SMS pod telefon Listing 15. Kompilacja programu do wysyłania SMS za pomocą Embedded Visual C++ 4
i z treścią podanymi w linii poleceń.
SMS można wysłać za pomocą funkcji clarm.exe -nologo -c -D_UNICODE -DUNICODE -DARM -D__ARM__ -DUNDER_CE=420 -D_WIN32_
SmsSendMessage(), poprzedzając jej wywo- WCE=420 -DWCE_PLATFORM_STANDARDSDK sendsms.c
łanie funkcją SmsOpen() oraz kończąc ca- clarm.exe -nologo -c -D_UNICODE -DUNICODE -DARM -D__ARM__ -DUNDER_CE=420 -D_WIN32_
ły proces wywołaniem funkcji SmsClose(). WCE=420 -DWCE_PLATFORM_STANDARDSDK smsapp.c
Funkcje te znajdują się w zainstalowanej bo- link.exe -nologo -subsystem:WINDOWSCE -machine:THUMB -out:sms.exe smsapp.obj
daj na każdym telefonie z Windows Mobi- sendsms.obj
le bibliotece dynamicznej sms.dll, defini-
cje moglibyśmy znaleźć w pliku nagłówko- Listing 16. Zmienne środowiskowe dla kompilatora Embedded Visual C++ 4
wym sms.h, który wraz z plikiem sms.lib uży-
wany jest do kompilacji programu za pomo- SET EVC=%ProgramFiles%\Microsoft eMbedded C++ 4.0
cą Microsoft Visual Studio wyposażonego w SET TARGETCPU=ARMV4I
Windows Mobile SDK. Zanim zdecydujecie SET EVCSDK=%ProgramFiles%\Windows CE Tools\wce400\STANDARDSDK
się na wykorzystanie tych dwóch plików, pa- SET INCLUDE=%EVCSDK%\include\%TARGETCPU%;%EVCSDK%\MFC\include;%EVSDK%\ATL\
miętajcie, że po pierwsze potrzebowalibyśmy include;%INCLUDE%
płatną wersję MS Visual Studio w wersji przy- SET LIB=%EVCSDK%\lib\%TARGETCPU%;%EVCSDK%\MFC\lib\%TARGETCPU%;%EVCSDK%\ATL\lib\
najmniej Standard, po drugie raczej dozgon- %TARGETCPU%;%LIB%
nie związalibyśmy się z kompilatorem i zesta- SET PATH=%EVC%\Common\EVC\Bin;%EVC%\EVC\WCE400\BIN;%PATH%
wem plików nagłówkowych Microsoftu.

www.sdjournal.org 35
Programowanie Windows Mobile

options, jeśli zawiera wartość 2, powoduje, Pozostałe pola struktury, kompletnie niecie- zadania, zostały na listingu przedstawione ja-
że otrzymamy raport o dostarczeniu SMS-a. kawe z punktu widzenia naszego skromnego ko tablica o nazwie not_needed i rozmiarze ta-
kim, jak suma rozmiarów reszty pól oryginal-
Listing 17. Zrzut ekranu do mapy bitowej typu HBITMAP nej struktury.
Listing 12 przedstawia kod funkcji
HBITMAP screenshot() { sendsms() wysyłającej SMS o treści podanej
HBITMAP hBitmap; w parametrze aMessage pod numer telefo-
HDC hDesktopDC, hBitmapDC; nu z parametru aNumber. Na początku za po-
int nH, nW; mocą duetu LoadLibrary i GetProcAddress
hBitmap = NULL; funkcja uzyskuje adresy niezbędnych funk-
hBitmapDC = NULL; cji do obsługi SMS z biblioteki dynamicz-
retval = SS_UNKNOWN; nej sms.dll. Następnie za pomocą funkcji
hDesktopDC = GetWindowDC(HWND_DESKTOP); SmsOpen, lub dokładniej za pomocą wskaźni-
nW = GetSystemMetrics(SM_CXSCREEN); ka do tej funkcji zapamiętanego w zmiennej
nH = GetSystemMetrics(SM_CYSCREEN); pSmsOpen, otwierany jest serwis SMS. Pierw-
hBitmap = CreateCompatibleBitmap(hDesktopDC, nW, nH); szy parametr funkcji określa nazwę protoko-
hBitmapDC = CreateCompatibleDC(hDesktopDC); łu, zastosowaną tu wartość "Microsoft Text
SelectBitmap(hBitmapDC, hBitmap); SMS Protocol" można znaleźć w dokumen-
BitBlt(hBitmapDC, 0, 0, nW, nH, hDesktopDC, 0, 0, SRCCOPY); tacji MSDN np. pod adresem [9]. Wartość 2
DeleteDC(hBitmapDC); (SMS_MODE_SEND) w drugim parametrze okre-
ReleaseDC(HWND_DESKTOP, hDesktopDC); śla, że chcemy użyć API do wysyłania wia-
return hBitmap; domości, a nie do ich odbioru (SMS_MODE_
} RECEIVE, wartość 1). Trzeci parametr funk-
cji SmsOpen() to zwracany uchwyt do serwi-
Listing 18. Konwersja mapy bitowej na format DIB su, stosowany w późniejszych wywołaniach
static int _bytesPerLine(int nWidth, int nBitsPerPixel) { do SmsSendMessage() i SmsClose().
int retval; Ostatni parametr określałby wydarzenie
obsługujące asynchronicznie nadchodzące
retval = nWidth * nBitsPerPixel; wiadomości. Omawiana funkcja tylko wysyła
retval = ((retval + 31) & (~31)) / 8; wiadomości, nie potrzebuje uchwytu wyda-
return retval; rzenia i stosuje dla ostatniego parametru war-
} tość NULL. Po otwarciu serwisu funkcja prze-
chodzi do wysłania wiadomości za pomocą
void dibitmap(BITMAPINFO *theBI, LPVOID *theBuf, HBITMAP hBitmap) { SmsSendMessage(), również za pośrednic-
BITMAP bm; twem wskaźnika do tej funkcji w sms.dll za-
HDC hdc, hmemdc, hcopydc; pamiętanego w zmiennej pSmsSendMessage.
HBITMAP htmpbmp; Drugi parametr to numer centrum ob-
BITMAPINFOHEADER *bih; sługi SMS, opakowany w strukturę _sms_
address. Zastosowana wartość NULL zaleca
hdc = NULL; pobranie standardowego numeru centrum
hmemdc = NULL; skonfigurowanego w telefonie. Kolejny, trze-
hcopydc = NULL; ci już parametr, to numer, pod który chcemy
ZeroMemory(theBI, sizeof(BITMAPINFO)); wysłać wiadomość, rzecz jasna również opa-
GetObject(hBitmap, sizeof(bm), &bm); kowany w strukturę _sms_address.
if (bm.bmHeight < 0) bm.bmHeight = -bm.bmHeight; Dalsze dwa parametry to treść wiadomo-
bih = &(theBI->bmiHeader); ści jako tekst UNICODE oraz jego rozmiar
bih->biSize = sizeof(BITMAPINFOHEADER); w bajtach. Dalej następują dane ze struktu-
bih->biWidth = bm.bmWidth; ry _provider_specific, określające, że wy-
bih->biHeight = bm.bmHeight; syłamy wiadomość tekstową (class = 1)
bih->biPlanes = 1; oraz że chcemy dostać raport o dostarcze-
bih->biBitCount = bm.bmBitsPixel; niu tej wiadomości (options = 2). Osta-
bih->biCompression = BI_RGB; tecznie serwis zamykany jest za pomocą
bih->biSizeImage = _bytesPerLine(bm.bmWidth, bm.bmBitsPixel) * bm.bmHeight; wskaźnika do funkcji SmsClose() i wywo-
hdc = GetDC(NULL); łaniem FreeLibrary() zwalniana jest bi-
htmpbmp = CreateDIBSection(hdc, theBI, DIB_RGB_COLORS, theBuf, NULL, 0); blioteka dynamiczna sms.dll.
hmemdc = CreateCompatibleDC(hdc); Listing 13 przedstawia uproszczoną wer-
hcopydc = CreateCompatibleDC(hdc); sję głównej funkcji programu wysyłającego
SelectObject(hmemdc, hBitmap); SMS za pomocą sendsms(). Główną funk-
SelectObject(hcopydc, htmpbmp); cją programów graficznych MS Windows
BitBlt(hcopydc, 0, 0, bm.bmWidth, bm.bmHeight, hmemdc, 0, 0, SRCCOPY); nie jest funkcja main(), tylko WinMain().
DeleteDC(hmemdc); Programy dla Windows CE nie stano-
DeleteDC(hcopydc); wią w tym względzie wyjątku. Trzeci pa-
} rametr tej funkcji szCmdLine to łańcuch
znaków parametrów linii poleceń zasto-

36 SDJ Extra 34 Biblia


RAPI

sowanych przy wywołaniu. W omawianej możemy wysłać SMS, wywołując na kom- Zrzut ekranu PDA
implementacji założono, że w linii pole- puterze biurkowym: Generowanie zrzutu ekranu urządzenia
ceń będzie znajdował się numer telefonu przenośnego to chyba najbardziej wdzięczne
oraz treść wiadomości, oddzielone od sie- rapirun /Temp/sms.exe "+48123456789 zadanie, z którym możemy sobie poradzić za
bie znakiem spacji lub tabulatora. Dość Treść wiadomości tekstowej" pomocą zdalnego wywołania RAPI. W tym
długa pętla for na początku funkcji kopiu- celu przygotowujemy program dla Windows
je numer telefonu z początku linii pole- przy czym program powinien sobie bez pro- CE nieposiadający interfejsu użytkownika,
ceń do tablicy number oraz ustawia wskaź- blemu poradzić również z polskimi znakami mogącego niepotrzebnie przesłonić interesu-
nik cp na początek treści wiadomości w li- w treści wiadomości. jący nas obszar ekranu. Program jako para-
nii poleceń szCmdLine. Ostatecznie wywo-
ływana jest funkcja sendsms(), załatwiają- Listing 19. Konwersja mapy bitowej na format DIB
ca bolesne szczegóły związane z wysłaniem
wiadomości. static void _write(HANDLE hFile, const LPVOID pBuf, DWORD nBytes) {
W kodzie źródłowym programów ([0]) const BYTE* ptr;
omawiany program znajduje się w modu- DWORD remaining, len;
łach sendsms.c oraz smsapp.c. Program można
obecnie skompilować za pomocą CeGCC [7] ptr = (const BYTE *) pBuf; remaining = nBytes;
oraz niestety niewspieranej już wersji Micro- while(WriteFile(hFile, ptr, remaining, &len, NULL) != 0 && remaining > 0) {
soft Embedded Visual C 4. remaining -= len; ptr += len;
Obecnie dostępne są binarne wersje }
CeGCC dla Linuksa oraz dla MS Windows }
wyposażonego we w miarę aktualny pakiet
cygwin. void savebmp(HBITMAP hBitmap, const TCHAR *szFileName) {
Można oczywiście pokusić się o samo- HANDLE hf;
dzielną kompilację pakietu CeGCC ze źró- BITMAPFILEHEADER bmpFileHeader;
deł na teoretycznie dowolny system, choć BITMAPINFO bmpInfo;
mnie osobiście nie udało się z sukcesem za- LPVOID pBuf;
kończyć kompilacji na moim OpenSuSE WORD w;
11.1 x86_64. Na szczęście binarne 32-bi-
towe pakiety rpm dla Mandrivy mandri- ZeroMemory(&bmpInfo, sizeof(BITMAPINFO));
va-cegcc-cegcc-0.55-1.i586.rpm i mandri- ZeroMemory(&bmpFileHeader, sizeof(BITMAPFILEHEADER));
va-cegcc-mingw32ce-0.55-1.i586.rpm, do- pBuf = NULL;
stępne na stronie projektu, pozwoliły się hf = INVALID_HANDLE_VALUE;
bezbłędnie zainstalować i uruchomić rów- dibitmap(&bmpInfo, &pBuf, hBitmap);
nież pod 64-o bitowym OpenSuSE. bmpFileHeader.bfReserved1 = 0;
Kod źródłowy omawianego programu bmpFileHeader.bfReserved2 = 0;
wystarczy potem skompilować za pomo- bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
cą wywołania podobnego do tego z Listin- bmpInfo.bmiHeader.biSizeImage;
gu 14, proszę porównać plik makefile.cegcc w = 'M'; w <<= 8; w += 'B'; /* w = 'MB'; */
dołączony do źródeł programu. Aby pro- bmpFileHeader.bfType = w;
gram skompilowany za pomocą CeGCC za- bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
działał na urządzeniu przenośnym, należy hf = CreateFile(szFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL,
dołączyć do niego bibliotekę dynamiczną CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
cegcc.dll, zainstalowaną w katalogu arm- _write(hf, &bmpFileHeader, sizeof(BITMAPFILEHEADER));
wince-cegcc/lib pakietu. Szczęśliwcy, któ- _write(hf, &(bmpInfo.bmiHeader), sizeof(BITMAPINFOHEADER));
rym udało się we właściwym czasie załado- _write(hf, pBuf, bmpInfo.bmiHeader.biSizeImage);
wać bezpłatną wersję kompilatora Micro- CloseHandle(hf);
soft Embedded Visual C++ 4, mogą w za- }
mian skompilować program, używając wy-
wołania podobnego do tego z Listingu 15, Listing 20. Główna funkcja programu do zrzutu ekranu
proszę również porównać plik makefile.evc
dołączony do źródeł. Kompilacji można do- int WINAPI WinMain(HINSTANCE hInstance,
konać z okna konsoli pakietu kompilatora HINSTANCE hPrevInstance,
lub ustawiając zmienne środowiskowe tak, LPWSTR szCmdLine,
jak to pokazano na Listingu 16. Nie próbo- int iCmdShow) {
wałem kompilacji za pomocą aktualnych HBITMAP hBitmap;
wersji Visual Studio, choć nie powinna się
ona zbytnio różnić od tej przeprowadzonej hBitmap = NULL;
za pomocą Embedded Visual C++ 4. Zakła- hBitmap = screenshot();
dając, że program z Listingu 10 skompilo- savebmp(hBitmap, szCmdLine);
wany został jako rapirun, a opisywany tu if (hBitmap != NULL) DeleteObject(hBitmap);
program do wysyłania wiadomości teksto- return 0;
wych nazwano sms.exe i skopiowano do ka- }
talogu \Temp na urządzeniu przenośnym,

www.sdjournal.org 37
Programowanie Windows Mobile

metr linii poleceń otrzymuje nazwę pliku, do cję _write() wywołującą WriteFile() aż do mić /bin/rlogind oraz /bin/ftpd, np. klikając na
którego zrzut zapisany zostanie jako mapa bi- zapisania całej zawartości bufora do pliku. te programy w oknie eksploratora plików PDA.
towa. Brak graficznego interfejsu użytkowni- Listing 20 przedstawia uproszczoną wersję Pracę tych demonów można potem zakończyć
ka, przeszkoda nie do ominięcia na większo- głównej funkcji WinMain() programu do two- np. w sesji rlogin, wywołując polecenie kill,
ści urządzeń przenośnych nie wyposażonych rzenia zrzutu ekranu. Funkcja screenshot() przy czym identyfikator procesu można zna-
w program linii poleceń cmd.exe, nie będzie umieszcza obraz ekranu w obiekcie mapy bi- leźć za pomocą – jakżeby inaczej – polecenia
stanowił żadnego problemu dla zdalnego wy- towej wskazywanym przez uchwyt hBitmap. ps. Przykład sesji rlogin z Linuksa na moim
wołania z komputera stacjonarnego. Za pomocą savebmp() obiekt konwertowany wyposażonym w Windows Mobile 6 telefonie
Listing 17 przedstawia uproszczoną wer- jest do formatu DIB i zapisywany do pliku o HTC3300 przedstawiłem na Rysunku 5.
sję funkcji do generowania zrzutu ekranu do nazwie podanej w linii poleceń.
obiektu mapy bitowej typu HBITMAP. Za po- Program skompilować możemy w sposób po- Na koniec
mocą GetWindowDC() uzyskiwany jest w hDesk- dobny do przedstawionego przy opisanej wcze- Mam nadzieję, że udało mi się przybliżyć Wam
topDC kontekst dla podanej jako parametr sta- śniej aplikacji do wysyłania wiadomości teksto- procesy zachodzące w tle programów typu Mi-
łej HWND_DESKTOP, czyli dla całego ekranu. Funk- wych. Ponieważ w programie wykorzystano je- crosoft Active Sync i zachęcić do eksperymen-
cja CreateCompatibleBitmap() tworzy w hBit- dynie API Win32, nie powinno być problemu z towania z własnymi programami. Przedstawio-
map mapę bitową o rozmiarze i parametrach doborem kompilatora. Mnie powiodła się kom- ne w artykule proste aplikacje doskonale nadają
uzyskanego kontekstu. CreateCompatibleDC() pilacja zarówno za pomocą CeGCC, jak i Mi- się do zastosowania w skrypcie shell-owym lub
tworzy jeszcze jeden kontekst, tym razem służą- crosoft Embedded Visual C++ 4. pliku wsadowym MS Windows do automatyza-
cy do tworzenia obrazu w mapie bitowej. Obraz Cały czas podkreślam, że listingi przedsta- cji kopiowania plików z i na PDA, instalacji pa-
w mapie bitowej tworzony jest poprzez skopio- wiają jedynie uproszczone wersje kodu, w kietów programowych czy po prostu do poszpe-
wanie obrazu z hDesktopDC za pomocą funk- pełni dostępnego pod adresem [0] w pliku rania po urządzeniu, którego standardowe na-
cji BitBlt(). W ten sposób otrzymujemy w źródłowym ce/scrsht.c. Listingi i tak są długie, rzędzia ani nie pokazują wszystkich plików ani
hBitmap mapę bitową zawierającą obraz okna a uwzględnienie w nich obsługi błędów oraz nie umożliwiają dostępu np. do rejestru. Doku-
HWND_DESKTOP, czyli obraz całego ekranu. Od drobnych różnić między programami pisany- mentacja RAPI pod adresem [1] opisuje wiele
mapy bitowej HBITMAP do mapy bitowej DIB mi dla Windows CE oraz dla biurkowych wer- innych przydatnych funkcji, jak choćby dostęp
spotykanej w plikach z rozszerzeniem bmp jest sji MS Windows (Przedstawiony tu sposób ge- do baz danych czy okien urządzenia oraz po-
jednak jeszcze daleka droga. Przedstawiona na nerowania zrzutów ekranu może być zastoso- zwalających na uruchomienie specjalnie spre-
Listingu 18 funkcja dibitmap() przekształca wany również na biurkowych wersjach syste- parowanej biblioteki dynamicznej, umożliwia-
mapę bitową hBitmap na format DIB zwraca- mu MS Windows) dodatkowo zwiększyłoby jącej np. wysyłanie strumieni danych między
ny w buforze theBuf. ich rozmiar i zmniejszyło czytelność. Zacie- komputerem biurkowym i urządzeniem. Dzię-
Dodatkowo zwraca w strukturze wska- kawionych zapraszam do przestudiowania ki projektowi SynCE programy komunikujące
zywanej przez theBI informacje o mapie źródeł opisywanych programów. się z Windows CE i Mobile przestały być do-
bitowej, konieczne do późniejszego stwo- Zakładając, że w pliku /Temp/scrsht.exe na meną biurkowych wersji systemu firmy Micro-
rzenia pliku. Sercem konwersji jest funk- urządzeniu przenośnym mamy już odpo- soft i można je z powodzeniem implementować
cja CreateDIBSection() tworząca pustą wiednio skompilowaną wersję programu oraz również pod Linuksem i FreeBSD. Ani ilość sys-
mapę bitową w formacie DIB. Za pomo- że wciąż mamy omawiany wcześniej program temów operacyjnych wspieranych przez Syn-
cą znanego już tricku utworzenia poprzez rapirun oraz program rapiget wykorzystujący CE ani ilość zaimplementowanych tam funk-
CreateCompatibleDC() kontekstu dla ma- funkcję wget() z Listingu 6, to zrzut ekranu cji RAPI nie jest jeszcze imponująca, tego typu
py bitowej oraz skopiowania obrazu z jedne- możemy wykonać np. za pomocą następują- projekty rozwijają się jednak dosyć szybko i kto
go kontekstu do drugiego za pomocą funkcji cych poleceń: wie, być może ktoś z Was zachęcony artykułem
BitBlt() otrzymujemy w htmpbmp przemie- jeszcze trochę je przyspieszy (i poprawi np. bar-
nioną mapę bitową. Jednym z parametrów rapirun /Temp/scrsht.exe /Temp/plik.bmp dzo nieekonomicznie zaimplementowany spo-
wywołania CreateDIBSection był wskaźnik rapiget /Temp/plik.bmp plik.bmp sób konwersji z i na UCS-2LE, który – jak sobie
do bufora mapy bitowej (konkretnie wskaź- przynajmniej wmawiam - zaimplementowałem
nik na wskaźnik, ponieważ wskaźnik na two- Zdalnie bez RAPI nieco lepiej).
rzony bufor jest wartością zwracaną), zawie- Pod adresem [10] znajduje się projekt GNU- Zachęcam również wszystkich do odwie-
rającego obraz już prawie w takiej postaci, ja- WINCE, zawierający skompilowane dla Win- dzenia strony opisywanego tu projektu [0], za-
kiej oczekuje się od plików bmp. dows CE podstawowe narzędzia znane z sys- wierającej oprócz wersji źródłowych również
Ostatnim krokiem jest zapisanie mapy bi- temu UNIX, jak ls, cp itd. Dzięki demonowi skompilowane wersje binarne dla OpenSuSE
towej DIB do pliku. Listing 19 przedstawia rlogind oraz interpreterowi powłoki ush moż- 11.1, MS Windows oraz dla Windows CE. Za-
funkcję savebmp() zapisującą mapę bitową liwe jest zalogowanie się na urządzeniu za po- warte w źródłach pliki makefile wykorzystu-
w hBitmap podawaną w takim formacie, jak mocą rlogin oraz zdalne uruchamianie pro- ją zarówno kompilatory firmy Microsoft jak
ją wyprodukowano w funkcji screenshot() gramów. Projekt dostarcza nawet kompilator i bezpłatny kompilator GCC, dla wszystkich
do pliku o nazwie podanej w szFileName. gcc, dzięki któremu można tworzyć progra- wspieranych systemów operacyjnych. I, last
Funkcja savebmp() najpierw wywołuje opi- my konsolowe, uruchamiane z takiej właśnie but not least, namawiam do rozwijania (i rzecz
saną funkcję dibitmap() w celu przekonwer- sesji rlogin. Za pomocą demona ftpd oraz zwy- jasna poprawiania błędów) projektu, nie po-
towania mapy bitowej na format DIB, zacho- kłego programu ftp można transferować pliki przestawajcie tylko na cichym wyśmiewaniu
wując wskaźnik na bufor otrzymanej mapy w obydwie strony. Aby zainstalować GNU- się z niedociągnięć.
bitowej w zmiennej pBuf. Następnie tworzo- WINCE na urządzeniu przenośnym, należy
ny jest nagłówek pliku bmp, który zapisywa- w najprostszym wypadku ściągnąć ze strony
ny jest do pliku tuż przed zapisaniem bajtów [10] plik winceos-092603.tar.bz2, rozpakować DANIEL STOIŃSKI
z bufora wskazywanego przez pBuf. Funkcja go i zawarte tam pliki wykonywalne skopio- Informatyk w firmie Nagler & Company
WriteFile() nie gwarantuje zapisania poda- wać do katalogu /bin urządzenia przenośne- (http://www.nagler-company.com)
nej ilości bajtów, dlatego zdefiniowano funk- go. Następnie na urządzeniu proszę urucho- Kontakt z autorem: stoyac@gmx.de

38 SDJ Extra 34 Biblia


Dla naszego klienta, światowego lidera w branży telekomunikacyjnej (przy projekcie obejmującym rozwój aplikacji nawigacyjnej

Rekrutacyjny.pl
oraz lokalizacyjnej na urządzenia mobilne) poszukujemy:

Symbian Developer
Miejsce pracy: Berlin
Nr ref.: Symbian/Berlin

Warunki projektu:
Wynagrodzenie: 18.000 PLN / m-c
Czas trwania: 9 - 12 m-cy - z opcją przedłużenia (po zakończeniu kontraktu firma oferuje udział w innych równie ciekawych projektach)
Rozpoczęcie: Rekrutacja ciągła
Miejsce: Berlin
Tryb pracy: Pełny etat
Forma współpracy / rozliczenie: Faktura VAT / umowa o dzieło / umowa zlecenie.
Oczekiwania firmy:
• Wykształcenie wyższe informatyczne lub pokrewne
• Min. 2-letnie doświadczenie w tworzeniu oprogramowania na platformie Symbian s60
• Doświadczenie zawodowe (development): min. 3 lata
• Bardzo dobra znajomość C++
• Bardzo dobra znajomość języka angielskiego
Mile widziane:
• Doświadczenie w tworzeniu aplikacji pod J2ME lub Windows Mobile
Nasz klient zapewnia:
• Praca przy bardzo ciekawym projekcie
Oraz:
Opcja 1:
• Wynagrodzenie 18.000 PLN / M-c
• Pomoc w znalezieniu mieszkania w Berlinie
• Opiekę naszego pracownika na miejscu

Opcja 2:
• Wynagrodzenie 13.000 PLN / M-c
• Zakwaterowanie w komfortowym mieszkaniu
• Dzienne diety
• Zwrot kosztów podróży Polska <=> Berlin
• Opiekę naszego pracownika na miejscu

Osoby zainteresowane naszą ofertą prosimy o przesłanie CV w języku angielskim, z klauzulą o zgodzie na przetwarzanie danych osobowych na ad-
res: praca@rekrutacyjny.pl
W temacie aplikacji prosimy o wpisanie nazwy stanowiska lub numeru referencyjnego oferty.
Prosimy o załączenie następującej klauzuli: „Wyrażam zgodę na przetwarzanie moich danych osobowych zawartych w mojej ofercie pracy dla po-
trzeb niezbędnych do realizacji procesu rekrutacji (zgodnie z Ustawą o Ochronie Danych Osobowych z dn. 29.08.1997 Dz. U. Nr 133 poz. 883)”

Dla naszego klienta, współpracującego ze światowym liderem w branży telekomunikacyjnej (przy projekcie obejmującym rozwój

Rekrutacyjny.pl
aplikacji nawigacyjnej oraz lokalizacyjnej na urządzenia mobilne) poszukujemy:

Senior J2ME Developer


Miejsce pracy: Berlin
Nr ref.: J2ME/Berlin

Warunki projektu:
Wynagrodzenie: 18.000 PLN / m-c
Czas trwania: 9 - 12 m-cy - z opcją przedłużenia (po zakończeniu kontraktu firma oferuje udział w innych równie ciekawych projektach)
Rozpoczęcie: Rekrutacja ciągła
Miejsce: Berlin
Tryb pracy: Pełny etat
Forma współpracy / rozliczenie: Faktura VAT / umowa o dzieło / umowa zlecenie.
Oczekiwania firmy:
• Wykształcenie wyższe informatyczne lub pokrewne
• Min. 3-letnie doświadczenie w programowaniu obiektowym
• Min. 2-letnie doświadczenie w programowaniu pod platformę J2ME
• Wszechstronne doświadczenie w Javie oraz świadomość ograniczeń JavaME i MIDP 2.0
• Doświadczenia z Socket’ami’ oraz protokołem HTTP na platformach mobilnych
• Doświadczenia w formatach wymiany danych, takich jak XML i JSON
• Dobre zrozumienie specyficznych ograniczeń pamięci i wydajności
• Bardzo dobra znajomość języka angielskiego
Mile widziane:
• Doświadczenie w aplikacjach lokalizacyjnych (tzw. Location Based Services) lub nawigacyjnych
• Znajomość Eclipse / EclipseME; Ant
• Znajomość zwinnych metodyk wytwarzania oprogramowania (Extreme Programming, SCRUM)
Nasz klient zapewnia:
• Praca przy bardzo ciekawym projekcie
Oraz:
Opcja 1:
• Wynagrodzenie 18.000 PLN / M-c
• Pomoc w znalezieniu mieszkania w Berlinie
• Opiekę naszego pracownika na miejscu

Opcja 2:
• Wynagrodzenie 13.000 PLN / M-c
• Zakwaterowanie w komfortowym mieszkaniu
• Dzienne diety
• Zwrot kosztów podróży Polska <=> Berlin
• Opiekę naszego pracownika na miejscu

Osoby zainteresowane naszą ofertą prosimy o przesłanie CV w języku angielskim, z klauzulą o zgodzie na przetwarzanie danych osobowych na ad-
res: praca@rekrutacyjny.pl
W temacie aplikacji prosimy o wpisanie nazwy stanowiska lub numeru referencyjnego oferty.
Prosimy o załączenie następującej klauzuli: „Wyrażam zgodę na przetwarzanie moich danych osobowych zawartych w mojej ofercie pracy dla po-
trzeb niezbędnych do realizacji procesu rekrutacji (zgodnie z Ustawą o Ochronie Danych Osobowych z dn. 29.08.1997 Dz. U. Nr 133 poz. 883)”
Programowanie Windows Mobile

Rozpocznij Przygodę
z Windows Mobile
Zobacz, jak to się robi w technologii Microsoft
Firma Microsoft od wielu lat wspiera programistów aplikacji mobilnych,
dając im coraz nowsze i lepsze narzędzia. Z roku na rok programiści mają
dostęp do większej ilości zasobów urządzeń mobilnych, a prostota, z jaką
można tworzyć takowe aplikacje w chwili obecnej, nie odstrasza już nowych
adeptów tej sztuki.
Jest to bardzo intuicyjne okno, z lewej
Dowiesz się: Powinieneś wiedzieć: strony wybieramy typ projektu, w naszym
• Jak stworzyć oraz uruchomić swoją pierw- • Jak obsługiwać narzędzie Microsoft Visual przypadku jest to Smart Device. Po prawej
szą aplikację na urządzeniu z systemem Studio. stronie pozostawimy wszystko bez zmian,
Windows Mobile; gdyż dalszej konfiguracji dokonamy w ko-
• Jakie możliwości daje nam .NET Compact lejnym oknie. W tym oknie ważne jest po-
Framework. danie nazwy projektu oraz jego lokaliza-
cji. Informacje te możemy wpisać u dołu
okna. Jeśli uzupełniliśmy wszystkie infor-
W artykule tym zostanie pokazane, jak macje, możemy kliknąć przycisk OK., by
można łatwo i szybko zacząć pracę z urzą- przejść dalej.
Poziom trudności dzeniami mobilnymi, a co najważniejsze, W kolejnym oknie (Rysunek 2) doko-
na samym początku nie potrzebujemy fi- nujemy dokładnej specyfikacji naszej apli-
zycznego urządzenia, gdyż Microsoft Vi- kacji. W pierwszej kolejności wybieramy
sual Studio dostarcza nam odpowiednie platformę, na której będzie uruchamiana

W
dobie XXI wieku urządze- emulatory. nasza aplikacja. Po domyślnej instalacji Mi-
nia mobilne odgrywają bardzo Ważnym elementem są wymagania. Aby crosoft Visual Studio 2008 mamy dostęp
ważną rolę. Dostęp do takich zacząć pracę z urządzeniami mobilnymi, do Pocket PC 2003, Windows CE, Win-
urządzeń jest praktycznie nieograniczony, będziemy potrzebowali Microsoft Visu- dows Mobile 5.0 Pocket PC SDK oraz Win-
a gamma systemów operacyjnych pozwa- al Studio 2008 w wersji Standard lub wyż- dows Mobile 5.0 Smartphone SDK. Py-
la na zróżnicowanie rynku. Firma Micro- szej. Darmowe narzędzie Microsoft Visu- tanie, co jeśli chcemy stworzyć aplikację
soft jest jedną z firm, która oferuje produ- al Studio 2008 Express nie wspiera apli- przystosowaną dla Windows Mobile 6.0?
centom urządzeń mobilnych system swo- kacji mobilnych, przy czym, by przetesto- Musimy w takim przypadku doinstalować
jej produkcji. Urządzenia z systemem Win- wać, jak wygląda programowanie aplika- odpowiednie SDK, link do nich znajduje
dows Mobile zyskują coraz większą po- cji mobilnych, możemy pobrać za darmo się u dołu okna Download additional emu-
pularność poza strefą biznesową. Jedną z wersję 90-dniową ze stron firmy Micro- lator images and smart devices SDKs…. Od-
przyczyn takiej sytuacji jest ciągle obniża- soft: http://msdn.microsoft.com/en-us/vstudio/ nośnik przekieruje nas na strony firmy Mi-
jąca się cena, a także zmieniające się zapo- default.aspx. crosoft, z której możemy pobrać dodatko-
trzebowanie użytkowników. we SDK, jak również przeczytać więcej nt.
Mimo to, urządzenia takie jak PocketPC Nasz Pierwszy Program systemu Windows Mobile. Na tym etapie
świetnie spisują się w innych sytuacjach, Mógłbym tu opowiadać o historii linii pro- nie musimy pobierać dodatkowego SDK i
niż biznesowe czy rozrywka. Dużą rolę od- duktów dla programistów firmy Microsoft, możemy wybrać Windows Mobile 5.0 Po-
grywają w przemyśle czy medycynie, gdzie jak również o tym, jak zmieniały się dostęp- cket PC SDK (ekran dotykowy) lub Win-
wspierane przez dodatkowe oprogramowa- ne elementy urządzeń mobilnych z wersji dows Mobile 5.0 Smartphone SDK (ekran
nie wspierają, a jednocześnie przyspiesza- na wersję, ale w ten sposób zwiększy się niedotykowy). Osobiście proponuję wybór
ją pracę. przewaga nudnej historii, która i tak niko- tego pierwszego, gdyż możemy tutaj prze-
Urządzenia takie mogą być rozszerzane go nie nauczy programować, dlatego od ra- testować, jak pisać aplikacje z dotykowym
o inne elementy, np. drukarki mobilne czy zu rozpoczniemy przygodę z programowa- ekranem.
czytniki kodów kreskowych. To wszystko niem. Po uruchomieniu Microsoft Visual Jako Target platform wybraliśmy Windows
sprawia, iż programowanie aplikacji mo- Studio 2008 klikamy w File\New\Project. Mobile 5.0, ale nie przeszkadza nam nic, by
bilnych przynosi ogromną satysfakcję pro- Pojawi się nowe okno New Project podobne uruchomić taką aplikację na urządzeniu z
gramistom. do tego przedstawionego na Rysunku 1. systemem Windows Mobile 6.0. Firma Mi-

40 SDJ Extra 34 Biblia


Rozpocznij przygodę z Windows Mobile

crosoft dołożyła wszelkich starań, by każdy pimy się na tych, które przydadzą się w na- • ForeColor – kolor czcionki.
system nowszy z rodziny Mobile był kom- szym projekcie. • FormFactor – pozwala na zmianę sposo-
patybilny z wersją niższą. Jeśli jednak użyje- bu wyświetlania formatki względem te-
my elementów pochodzących z wersji 6.0, to • AutoScroll – jeśli jest ustawione na go, na jakie urządzenie jest przeznaczo-
nie zawsze będzie możliwe uruchomienie ta- True (wartość domyślna), to mamy pew- na nasza aplikacja.
kiej aplikacji na urządzeniu z wersją 5.0 Win- ność, że w przypadku, gdy nasze kontro- • MinimizeBox – (domyślna wartość True)
dows Mobile. lki nie mieszczą się na formie, zostanie – pozwala ustawić, czy okno ma być mi-
Kolejnym etapem jest wybór odpowiednie- dodany suwak. nimalizowane, czy zamykane (False).
go Framework`a. Zalecane jest, by wybierać • BackColor – ustawia kolor formatki. W przypadku systemu Windows XP, Vi-
jak najnowszy, gdyż mamy dostęp do wielu • Font – czcionka i styl stosowane na format- sta czy 7 każde okno u góry po prawej
nowych elementów, jak choćby Microsoft LI- ce (element Font nie jest dziedziczony dla ma czerwony X, który jest równoznacz-
NQ, który jest dostępny od wersji 3.5 Micro- kontrolek umieszczanych na formatce). ny z poleceniem zamknij aplikację. W
soft .NET Compact Framework.
Ostatnim etapem jest wybór szablonu
(Template). Do wyboru mamy:

• Device Application – dostajemy przygo-


towany projekt, który będzie korzystał z
formatek oraz będzie posiadał reprezen-
tację graficzną.
• Class Library – projekt, w którym zo-
staną przygotowane klasy na późniejszy
użytek innych projektów.
• Console Application – mamy możliwość
tworzenia aplikacji konsolowej. Z ra-
cji tego, iż system Windows Mobile nie
posiada konsoli, nie jesteśmy w stanie
przedstawić w sposób wizualny efektów
naszej pracy.
• Control Library – projekt, w którym zo-
staną przygotowane kontrolki, które bę-
dziemy mogli umieszczać na naszych
formatkach w głównym projekcie.
• Empty Project – całkowicie pusty projekt.

Wybieramy Device Application, gdyż nie


chcemy zaczynać od zera, lecz wykorzystać Rysunek 1. Okno dodawania nowego projektu
już pewne elementy, a Visual Studio przy-
gotuje całe środowisko za nas. Naszym za-
daniem będzie dodanie pozostałych ele-
mentów.
Ostatecznie klikamy OK., po czym nasz
projekt zostanie przygotowany. Na początku
załaduje się czysta formatka, która będzie oto-
czona skórką przykładowego urządzenia, ma-
jąca w pewien sposób ułatwić nam sposób wy-
obrażenia sobie, jaki będzie efekt końcowy.
Pierwszym elementem, który warto zmie-
nić, to nazwa formatki. Przechodzimy do
Solution Explorer, klikając w View\Solution
Explorer, i na liście klikamy prawym klawi-
szem myszy na Form1.cs. Nazwę proponuję
zmienić na MainForm.cs, ale może ona być
dowolna.
Kolejnym elementem będzie zmiana usta-
wień naszej formatki, w tym celu klikamy
gdzieś w środku formatki (wewnątrz skór-
ki urządzenia), a następnie z menu wybiera-
my Properties. Okno ustawień formatki po-
winno być podobne do tego przedstawione-
go na Rysunku 3.
W tym miejscu możemy dokonać wszel-
kich modyfikacji naszej formatki. Nie będzie-
my tutaj wymieniać wszystkich opcji, a sku- Rysunek 2. Dodanie większej ilości informacji nt. projektu

www.sdjournal.org 41
Programowanie Windows Mobile

przypadku Windows Mobile nie jest do- Może to być domyślnie pionowy, jak rów- tecznie załadowany nasz projekt na urządze-
konywane polecenie zamknij, a zmini- nież poziomy. Do zadań programisty należy nie mobilne. Nasza przykładowa aplikacja
malizuj. By była możliwość zamykania zaprojektowanie formatki, by dostosowywała może wyglądać jak ta przedstawiona na Ry-
formatki, musimy zmienić tę opcję na się automatycznie do rodzaju urządzenia. W sunku 4.
False. Jeśli teraz popatrzymy na okno dalszej części artykułu zostaną dodane różne W taki oto sposób stworzyliśmy format-
formatki, to zauważymy, że nie ma przy- elementy na formatkę i zostanie dokładnie kę pod pierwszy program. Teraz dodamy ele-
cisku X, lecz ok., które pozwala zamknąć omówione, co należy zrobić, by zaprojekto- menty menu oraz kilka kontrolek, które na-
okno formatki. wać bardzo dobry interfejs. stępnie oprogramujemy.
• Text – pozwala na ustawienie tekstu Możemy uznać, iż nasza aplikacja jest go-
wyświetlanego na pasku u góry for- towa do uruchomienia. Aplikacja domyślnie Konfiguracja Menu
matki. uruchamia się w symulatorze, co w naszym W celu dodania elementu do menu klikamy
• WindowState – domyślnie jest ustawio- przypadku jest jak najbardziej odpowied- w jasnoniebieski pasek u dołu skórki. Efek-
ne Normal, co jest równoznaczne z tym, nie. Uruchomić aplikację możemy na kilka tem będzie pojawienie się po lewej stronie
co widzimy na przykładowej formatce. sposobów: klikając w zieloną strzałkę na pa- podświetlonego tekstu Type Here, co jest za-
Możemy jednak zmienić na Maximized, skach Visual Studio lub klikając klawisz [F5] chętą, by kliknąć w to miejsce. Klikamy w na-
co spowoduje usunięcie górnej części (są także inne sposoby, ale nie będziemy ich pis i dodajemy własny: Zamknij. W tym miej-
okna formatki. Uzyskujemy więcej miej- teraz omawiać). scu będzie możliwość zamknięcia naszego
sca, lecz funkcję zamknij czy zminimali- Po kliknięciu klawisza [F5] lub przyciśnię- programu. Warto wspomnieć, iż w progra-
zuj musimy teraz osadzić w menu. ciu zielonej strzałki zostanie skompilowany mach na urządzenia mobilne od wersji szó-
nasz projekt, uruchomiony emulator, a osta- stej Windows Mobile został wprowadzony
Jest jeszcze jedno bardzo ważne ustawie-
nie formatki. Klikając prawym klawiszem Listing 1. Podstawowa część kodu
myszy w środku skórki, z menu wybieramy
Rotate Left lub Rotate Right. W efekcie skór- using System.Windows.Forms;
ka zostanie przystosowana do innego usta- namespace SDJStartWithWindowsMobile
wienia urządzenia mobilnego. W przypad- {
ku urządzeń mobilnych mamy bardzo du- public partial class MainForm : Form
że zróżnicowanie nie tylko w wielkości i {
rozdzielczości ekranu, ale także w jego po- public MainForm()
łożeniu. {
InitializeComponent();
}

private void menuItem1_Click(object sender, System.EventArgs e)


{
Close();
}
}
}

Listing 2. Dodanie komunikatu powitania po kliknięciu przycisku „Witaj” w menu


using System.Windows.Forms;

namespace SDJStartWithWindowsMobile
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}

private void menuItem1_Click(object sender, System.EventArgs e)


{
Close();
}

private void menuItem6_Click(object sender, System.EventArgs e)


{
MessageBox.Show("Witaj " + textBox1.Text, "Powitanie");
}
}
}
Rysunek 3. Właściwości głównej formatki

42 SDJ Extra 34 Biblia


Rozpocznij przygodę z Windows Mobile

umowny standard, aby najczęściej używana czynając od Label, a prawe krawędzie rozsze- W tym miejscu zakończymy wstęp do two-
funkcja znajdowała się po lewej stronie me- rzamy do prawej krawędzi formatki. Obróć- rzenia aplikacji mobilnych. Teraz przejdzie-
nu, a reszta ukryta jako podmenu po prawej my formatkę w lewo lub prawo. my i przyjrzymy się elementom, które do-
stronie, pod klawiszem MENU. Rysunek 5 Szerokość kontrolki TextBox nie powięk- starcza nam SDK, a więc klasom, które dadzą
prezentuje przykładowe menu. sza się wraz ze zmianą położenia formatki, nam dostęp do najciekawszych miejsc urzą-
Jeśli zobaczymy na Rysunku 5, że poza co jest złą praktyką, dlatego też przejdźmy te- dzeń mobilnych, które mamy możliwość mo-
skórkę z prawej strony wystaje jeszcze jeden raz do właściwości Anchor kontrolki TextBox. nitorować, jak również, co ważniejsze, zmie-
przycisk zachęty, do kontynuowania rozsze- Domyślnie mamy tutaj Top,Left, dodajemy niać. Będzie trochę więcej elementów za-
rzania menu. Nie jest zalecane i nie należy jeszcze Right. Obracamy ponownie naszą awansowanych, skupimy się także bardziej na
do najlepszych praktyk, by menu było więk- formatkę i mamy bardzo ładnie zaprojekto- stronie kodowej.
sze, niż dwa przyciski umieszczone w pasku waną formatkę, która automatycznie dosto-
menu, zagnieżdżone podmenu również nie sowuje się do urządzenia mobilnego. Elementy SDK
są zalecane. Rysunek 6 prezentuje źle zapro- Teraz chcielibyśmy, by po wpisaniu tekstu Wszystkie klasy pochodzące z przestrzeni
jektowane menu. w kontrolkę TextBox pojawił się komunikat nazw System są częścią .NET Compact Fra-
Skoro już mamy nasze menu gotowe, to do- powitalny. Jeśli jest urządzenie z dotykowym mework, natomiast wszystkie należące do Mi-
dajmy teraz kod pod przycisk Zamknij, by po ekranem, tak jak w naszym przypadku, to crosoft to elementy SDK. Przyjrzymy się teraz
naciśnięciu tego klawisza menu nasza aplika- możemy pokusić się o umieszczenie przyci- wybranym klasom w przestrzeni nazw Micro-
cja się zamykała. Klikamy dwukrotnie w przy- sku na formatce, co nie do końca jest dobrym soft.WindowsMobile. W tabelce zawarto prze-
cisk Zamknij, Visual Studio automatycznie rozwiązaniem, gdyż użytkownik będzie zmu- strzenie nazw, które zostaną dokładnie omó-
przeniesie nas do kodu, a do tego doda nie- szony do obsługi urządzenia dwiema rękami, wione, oraz ich znaczenie w projekcie.
zbędny blok kodu, który będzie wywoływa- co nie jest korzystne, dlatego wszelkie tego ty- W celu wykorzystania którejkolwiek z
ny za każdym razem, gdy zostanie naciśnię- pu operacje powinny być umieszczane w me- klas, należy dodać referencje do projektu. W
ty przycisk Zamknij. Między nawiasem {„, a nu. Jeśli byłaby to aplikacja przeznaczona dla tym celu otwieramy SolutionExplorer i klika-
„} wpisujemy Close();, co jest równoznacz- urządzeń bez ekranu dotykowego, to wręcz my prawym klawiszem myszy w References,
ne z zamknięciem formatki. Listing 1 przed- koniecznym jest umieszczenie operacji powi- a następnie Add Reference. Pojawi się kolejne
stawia część kodową naszej formatki. tania w menu. okno, w którym mamy możliwość wyboru
Możemy teraz przetestować naszą aplika- Do menu dodajemy przycisk Witaj, a na- odpowiednich referencji. Wybieramy wszyst-
cję, uruchamiając i testując klawisz Zamknij. stępnie klikamy w niego dwukrotnie, co spo- kie wymienione, a dodatkowo dodajemy Mi-
Wróćmy teraz do naszej formatki (przej- woduje przejście do kodu. Tutaj uzupełniamy crosoft.WindowsMobile.
ście między częścią kodową a wizualną może- kod o dodatkowe elementy. Przykład został za- Poniżej zostaną omówione wybrane prze-
my dokonać, klikając w klawisz [F7]) i dodaj- prezentowany na Listingu 2, natomiast efekt strzenie nazw, jak również pokazane zostaną
my kilka kontrolek. działania programu prezentuje Rysunek 6. wybrane elementy tychże przestrzeni.

Dodajmy Kontrolki Do Menu


Kontrolki dodajemy z Toolbox`a, którego
wywołujemy poprzez View\Toolbox. Na for-
matkę przeciągamy Label oraz TextBox. Do
właściwości Text kontrolki Label wpisujemy
Proszę podać imię:. Obie kontrolki ustawiamy
w lewym górnym rogu, jedna pod drugą, za-

Rysunek 5. Przykładowe, poprawne MENU Rysunek 6. Źle zaprojektowane MENU

Tabela 1. Przestrzenie nazw zawarte w WindowsMobile


WindowsMobile.Configuration Mamy dostęp do elementów konfiguracyjnych urządze-
nia mobilnego.
WindowsMobile.Forms Dzięki tej przestrzeni nazw mamy dostęp do elementów
takich jak wybór kontaktu czy zdjęcia.
WindowsMobile.PocketOutloook Dostęp do kontaktów oraz do elementów sterujących
wysyłaniem SMS`ów.
WindowsMobile.Status Możemy kontrolować status urządzenia mobilnego.
WindowsMobile.Telephony Elementy związane z dokonywaniem rozmów telefo-
Rysunek 4. Pierwsza przykładowa aplikacja nicznych.

www.sdjournal.org 43
Programowanie Windows Mobile

Listing 3. Dodanie odwołań do poszczególnych przestrzeni nazw


W części kodu dodajemy odpowiednie od-
wołania do przestrzeni nazw. Listing 3 poka-
using Microsoft.WindowsMobile.Forms; zuje, jak to zrobić.
using Microsoft.WindowsMobile.Telephony;
WindowsMobile.Forms
using Microsoft.WindowsMobile.PocketOutlook; Przy pomocy tej przestrzeni nazw możemy
using Microsoft.WindowsMobile.Status; wybierać konkretny kontakt z naszej listy
kontaktów czy też wybrać zdjęcie. By najle-
Listing 4. Możliwość wybrania zdjęcia oraz kontaktu piej przedstawić działanie tejże przestrzeni
private void menuItemZdjecie_Click(object sender, System.EventArgs e) nazw, do menu dodamy dwa podmenu: Zdję-
{ cie oraz Kontakt, a dla nich utworzymy część
var selectPictureDialog = new SelectPictureDialog(); kodową, klikając w każdy dwukrotnie. Li-
var dialogResult = selectPictureDialog.ShowDialog(); sting 4 przedstawia kod, który należy zamie-
ścić w każdej z metod.
if(dialogResult == DialogResult.OK) Pierwsza część kodu umożliwia wybór
{ zdjęcia, a następnie przypisanie jego nazwy
label1.Text = selectPictureDialog.FileName; do właściwości Text kontrolki Label, któ-
} ra została umieszczona na formatce w po-
} przedniej części artykułu.
Druga część kodu jest bardzo podobna do
private void menuItemKontakt_Click(object sender, System.EventArgs e) poprzedniej, z tym że teraz pozwalamy na
{ wybór kontaktu z listy kontaktów, a następ-
var contactDialog = new ChooseContactDialog(); nie przypisujemy nazwę kontaktu do właści-
var dialogResult = contactDialog.ShowDialog(); wości Text kontrolki Label.
if(dialogResult == DialogResult.OK) Jak można zauważyć, klasy te są do siebie
{ bardzo podobne, co świadczy o doskonałym
label1.Text = contactDialog.SelectedContactName; zaprojektowaniu tychże klas przez projektan-
} tów z firmy Microsoft.
}
WindowsMobile.Status
Listing 5. Dostęp do statusów Działanie tej przestrzeni nazw najlepiej
private void menuItemStatus_Click(object sender, System.EventArgs e) zaprezentować na przykładzie, który zo-
{ stał umieszczony na Listingu 5. Klasa
label1.Text = SystemState.OwnerName; SystemState jest bardzo bogata, a w przy-
} kładzie pobieramy nazwę właściciela urzą-
dzenia mobilnego i przypisujemy ją do wła-
Listing 6. Aplikacja działająca w tle. ściwości Text kontrolki Label. Jeśli dokład-
private SystemState _st; nie się przyjrzymy metodom, jakie ofe-
private static readonly string _id = "SDJ"; ruje klasa SystemState, to szybko może-
private void MainForm_Load(object sender, EventArgs e)
{
if (SystemState.IsApplicationLauncherEnabled(_id))
{
_st = new SystemState(_id);
_st.Changed+=new ChangeEventHandler(_st_Changed);
}
else
{
_st = new SystemState(SystemProperty.TasksActive);
_st.Changed += new ChangeEventHandler(_st_Changed);
_st.EnableApplicationLauncher(_id);
}
}

void _st_Changed(object sender, ChangeEventArgs args)


{
MessageBox.Show("Dodano nowe zadanie.");
}

private void MainForm_Closed(object sender, EventArgs e)


{
_st.Dispose();
}
Rysunek 7. Przykład działania programu

44 SDJ Extra 34 Biblia


Rozpocznij przygodę z Windows Mobile

my stwierdzić, iż jest to jedna z bardziej działanie aplikacji w tle, i dodamy nowe za- Kod wydaje się trywialnie prosty, gdyż
przydatnych klas, gdyż możemy dokład- danie, to nasza aplikacja zostanie ponownie tworzymy nowy obiekt Phone, który jest bez-
nie sprawdzić każdy stan w systemie Win- włączona wraz z komunikatem, pomimo iż parametrowy, a następnie wywołujemy jego
dows Mobile i zareagować w odpowiedni szybciej dokonaliśmy jej zamknięcia. metodę Talk, która może przyjmować jeden
sposób. Zobaczmy, jak przy pomocy Sys- lub dwa parametry. Pierwszym jest numer te-
temState możemy uruchomić naszą apli- WindowsMobile.PocketOutlook lefonu, natomiast drugi określa, czy ma zo-
kację w tle. Listing 6 pokazuje przykłado- W PocketOutlook mamy dostęp do takich stać wyświetlone zapytanie przed wykona-
we rozwiązanie tego problemu. Do naszej elementów jak kontakty, zadania, oraz spo- niem rozmowy.
formatki dodajemy obsługę Event`u Load tkania. Możemy je dodawać, jak również czy- Podczas uruchamiania tej części kodu pro-
oraz Closed, po czym w kodzie tworzymy ścić, a co najważniejsze reagować w odpo- szę zwrócić uwagę, by aplikacja była urucha-
prywatne obiekty _st, oraz _id. W częście wiedni sposób w przypadku zmiany któregoś miana w emulatorze Windows Mobile 6 Pro-
MainForm_Closed dodajemy obsługę za- z elementów. Listing 6 przedstawia przykład, fessional.
mknięcia obiektu SystemState, natomiast jak można w prosty sposób dodać nowy kon-
w części MainForm_Load dodamy waru- takt do listy kontaktów. Podsumowanie
nek, który będzie sprawdzał, czy została Na początku tworzymy nowy obiekt Urządzenia mobilne mają coraz to now-
uruchomiona aplikacja o podanym iden- OutlookSession, następnie tworzymy nowy sze zastosowania i zaczynają wypierać stan-
tyfikatorze. Jeżeli nie, to przechodzimy do kontakt oraz dodajemy go do listy kontak- dardowe urządzenia, a ich wszechobecność
części else, w której tworzymy nowy obiekt tów zawartych w OutlookSession. Dalej do- umożliwia nam, jako programistom, pisa-
SystemState, który będzie reagował na do- dajemy elementy kontaktu oraz aktualizuje- nie aplikacji, które trafią do dużej ilości
danie nowego zadania, o czym poinformu- my kontakt o nowe informacje. Ostatecznie użytkowników.
je w nowym oknie, oraz wskaże, iż aplikacja zamykamy OutlookSession. W artykule tym zostały przedstawione in-
będzie miała dany identyfikator, jeśli jed- formacje wstępne, które pozwolą zasmako-
nak warunek zostanie spełniony, to zosta- WindowsMobile.Telephony wać programowania aplikacji mobilny dla
nie stworzony nowy obiekt SystemState z Jest to dość konkretna przestrzeń nazw, a jej systemu Windows Mobile. Wiedzę należało-
podanym identyfikatorem oraz zostanie przeznaczenie jest dość oczywiste. Za jej po- by poszerzyć o połączenie z mobilną bazą da-
wyświetlona informacja. Po uruchomie- mocą możemy dokonywać rozmów telefo- nych czy synchronizację bazy mobilnej z ba-
niu programu, oraz dodaniu nowego za- nicznych. Do naszego menu dodajemy no- zą stacjonarną.
dania urządzenie mobilne wyświetli nasz we podmenu Zadzwoń i klikamy dwukrot- Oprogramowanie Microsoft Visual Stu-
program wraz z komunikatem. Jeśli teraz nie, oraz dodajemy część kodu zawartą na Li- dio 2008 dostarcza bardzo dużo narzędzi
zamkniemy naszą aplikację, co spowoduje stingu 8. programistycznych, które można wykorzy-
stać na różne sposoby, lecz podczas two-
Listing 7. Dodanie nowego kontaktu. rzenia projektu ważny jest także czas pisa-
nia aplikacji. Autor poleca dodatek do Vi-
private void menuItemPocketOutlook_Click(object sender, EventArgs e) sual Studio firmy JetBrains o nazwie Re-
{ Sharper, który przyspieszy pracę programi-
var outlookSession = new OutlookSession(); sty, jak również pozwoli utrzymać czytelny
var contact = outlookSession.Contacts.Items.AddNew(); kod projektu. Program można zamówić ze
contact.FirstName = "SDJ"; strony producenta: http://www.jetbrains.com/
var uri = new Uri("http://www.sdjournal.org"); resharper.
contact.WebPage = uri; Na końcu chciałbym wspomnieć o Win-
contact.Update(); dows Mobile Marketplace (http://developer.w
indowsmobile.com/Marketplace.aspx). Jest to
outlookSession.Dispose(); swego rodzaju sklep internetowy, który ofe-
} ruje aplikacje firm trzecich, co oznacza, że
możemy napisać aplikację i dodać ją do bazy
Listing 8. Metoda pozwalająca wykonać rozmowę oprogramowania, a dowiedzą się o niej klien-
private void menuItemZadzwon_Click(object sender, System.EventArgs e) ci z całego świata. Polska jest na liście krajów,
{ które jako pierwsze będą mogły udostępniać
var p = new Phone(); sprzedaż aplikacji za pomocą Windows Mo-
p.Talk("1234", true); bile Marketpace. Koszt rejestracji to 99$,
} co jest sumą niewielką, licząc fakt, iż firma
otrzymuje 70% zysku i jest reklamowana na
całym świecie.

W Sieci DANIEL DUDEK


Pracuje w firmie LGBS Software na stanowisku
• Strona dla programistów aplikacji mobilnych dla urządzeń z systemem Windows Mobile
.NET Developer. Zajmuje się programowaniem
http://developer.windowsmobile.com;
• Strona poświęcona programowaniu aplikacji mobilnych, zawarta jako część MSDN http:/ aplikacji mobilnych na systemach Microsoft Win-
/msdn.microsoft.com/pl-pl/windowsmobile/default(en-us).aspx; dows Mobile. Jest liderem Śląskiej Regionalnej
• Blog autora artykułu http://geekswithblogs.net/dand; Grupy Microsoft. Autor jest także prelegentem
• Witryna poświęcona urządzeniom mobilnym https://www.microsoft.com/windowsmobile/ konferencji społecznościowych:
pl-pl/default.mspx; ht tp: //ms- groups.pl/speakerbiuro/Strony/
• Windows Mobile Marketplace – globalny sklep z aplikacjami Windows Mobile http://deve-
loper.windowsmobile.com/Marketplace.aspx. DanielDudek.aspx
Kontakt z autorem: d.dudek@lgbs.pl

www.sdjournal.org 45
Programowanie Windows Mobile

MS SQL SERVER CE 3.5


Jak wykorzystać w swojej aplikacji silnik SQL Servera CE 3.5.

Dzięki nowej wersji SQL Servera CE możesz zarządzać danymi poprzez


zapytania bez konieczności instalacji dodatkowych usług i bez dostępu do
Internetu. Ułatwia to dystrybucję oprogramowania. W artykule znajdziesz
informacje, które pozwolą Ci rozpocząć pisanie aplikacji wykorzystujących
SQL Server CE.
za będzie zawierać 30000 części. Wszystko
Dowiesz się: Powinieneś wiedzieć: będzie wczytywane z 1 płyty CD. Wykorzy-
• Jakie możliwości niesie ze sobą SQL Server • Znajomość podstawowych zapytań języka stanie do tego SQL Servera jest przesadą. In-
CE 3.5; T-SQL; nym rozwiązaniem może być Linq to XML.
• Jak go wykorzystywać w swoich aplikacjach; • Podstawowa wiedza z zakresu .NET CF 3.5; Jednak tak jak pokazały testy, najszybszym
• Jak pracować z nim w Visual Studio; • Podstawowa wiedza z zakresu C# 3.5. rozwiązaniem będzie wykorzystanie wła-
• Jakie są dostępne narzędzia wspierające; śnie SQL Server CE for desktop (patrz roz-
• Jakie są rodzaje replikacji; dział testy). Właśnie po to powstał SQL Se-
• Jaka jest wydajność tego rozwiązania. rvera CE (desktop i mobile). Jest to pomost
pomiędzy olbrzymią ilością danych w du-
żych firmach, gdzie wykorzystuje się pełną
jest opisany tutaj SQL Server CE. Ciekawą wersję Microsoft SQL Server, a bardzo małą
cechą tego rozwiązania jest wykorzystanie ilością danych – parsowanie XMLa.
Poziom trudności tej wersji serwera również na desktopach. Oczywiście każde rozwiązanie ma swój
Początkowo SQL Server CE był stworzony obszar zastosowań. Wszystko zależy od
tylko dla urządzeń mobilnych. Od niedaw- oczekiwań programisty – i na tej podsta-
na powstała specjalna dystrybucja tego opro- wie powinniśmy wybrać odpowiednie na-

Z
a sprawą Visual Studio oraz platfor- gramowania umożliwiająca
my .NET pisanie aplikacji pod Win- wczytywanie bazy danych z
dows Mobile coraz bardziej przypo- pliku bez wykorzystania ja-
mina pisanie oprogramowania na desktopy. kiejkolwiek usługi SQL Se-
Z każdą nową wersją .NET Compact Fra- rvera również na kompute-
mework rośnie liczba dostępnych klas. Pisa- rach PC. Fizycznie SQL Se-
nie aplikacji staje się coraz łatwiejsze, szcze- rver CE (zarówno mobi-
gólnie jeśli piszemy aplikacje w języku Visu- le, jak i desktop) jest zbio-
al C# czy Visual Basic.NET. Microsoft stara rem bibliotek, a cała baza da-
się również ujednolicać narzędzia programi- nych składowana jest w po-
styczne takie jak Visual Studio. Dzięki te- staci jednego pliku z rozsze-
mu, kiedy raz przyzwyczaimy się do niego rzeniem sdf. Wszystkie zapy-
oraz nauczymy się .NETa, możemy pisać w tania, połączenia i narzędzia
nich aplikacje Windows Forms, Web Forms, SQL Server CE w wersji mo-
Windows Mobile czy pisać interaktywne bilnej i desktopowej są ze so-
aplikacje w Silverlight. Oczywiście każde z bą kompatybilne. Ta ostatnia
tych rozwiązań również różni się od siebie. wersja (desktop) szczególnie
Przykładowo, pisząc aplikację pod Windows może się przydać, gdy rekor-
Mobile, należy zdawać sobie sprawę, że dów w bazie nie jest na tyle
urządzenie mobilne nie ma takiej mocy ob- dużo, aby zaistniała koniecz-
liczeniowej co komputer desktopowy. Z ko- ność instalacji pełnej wersji
lei na pewno cechą wspólną wszystkich po- Microsoft SQL Server. Przy-
wstających aplikacji jest wykorzystywanie i kładowo programista tworzy
przetwarzanie w nich danych. O ile na desk- aplikację – katalog produk-
topach powstało do tego celu wiele rozwią- tów danej firmy. Załóżmy, że
zań, o tyle na Windows Mobile sprawa nie będzie to katalog drobnych Rysunek 1. Scenariusz wykorzystania całej rodziny produktów SQL
wygląda tak dobrze. Jednym z rozwiązań części samochodowych. Ba- Server

46 SDJ Extra 34 Biblia


MS SQL Server CE 3.5

rzędzie. Podczas projektowania oprogramo-


wania należy zastanowić się, jak duże ilości
�������
danych będzie przechowywała aplikacja, czy
klient będzie miał dostęp do Internetu, czy
baza danych będzie rozproszona na jednost- ��������� ���������
ki itp. Na Rysunku 1 przedstawione zosta- ������� �������
ły typowe scenariusze wykorzystania bazy
SQL Server Express oraz SQL Server CE w
obydwu wersjach (desktop, mobile). ����
������������ ������������
��������

Porównanie wersji
Należy również zdawać sobie sprawę z istot-
nych różnic między SQL Server Compact ���������������

Edition a wersją Express. Każdy z nich ma


swoje zastosowanie i żaden z nich nie wyklu-
cza drugiego. Można powiedzieć, że Com-
pact Edition zawiera podzbiór funkcjonal-
ności, jaką daje SQL Server. Rysunek 2. Architektura ADO.NET dla SQL Server CE
W Tabeli 1 zostały zebrane informacje do-
tyczące rodziny produktów SQL Server. Pi- uzasadnienie w wydajności aplikacji. Funk- Jak zacząć
sząc Compact Edition, mam na myśli oby- cje te w znaczny sposób obciążałyby zaso- Zawsze najtrudniej jest rozpocząć. W tym
dwie wersje (desktop oraz mobile). by urządzenia mobilnego – dlatego też zde- przypadku mamy o tyle prościej, że wszyst-
Wielu programistów może być zawie- cydowano się z nich zrezygnować. Jednak- ko, co jest potrzebne, to Visual Studio 2008
dzionych z powodu braku wsparcia dla ta- że wcale nie jest powiedziane, że z pewny- Express oraz .NET Compact Framework
kich elementów T-SQL , jak procedury skła- mi ograniczeniami nie pojawią się w kolej- 3.5. Wszystkie kreatory i komponenty są do-
dowane, widoki czy triggery. Ma to swoje nych wersjach. stępne bezpośrednio w środowisku – dzięki

Tabela 1. Zestawienie funkcjonalności SQL Server CE oraz SQL Server Express


Cecha SQL Server Compact Edition SQL Server
Express Edition
Instalacja / Dystrybucja Instalacja / Dystrybucja Instalacja / Dystrybucja
Wsparcie dla ClickOnce + +
Uruchamiane razem z aplikacją + -
Wymagane prawa administratora - +
Uruchomienie na Windows Mobile + -
Możliwość dołączenia do pliku MSI + +
Działanie w procesie + -
Wsparcie dla 64-bit Dotyczy CE 3.5 w wersji Desktop z SP1 + Windows on Windows (WOW)
- Version 3.1
+ Natywnie 64 bitowy w przyszłych wersjach
Działanie jako usługa - +
Pliki Pliki Pliki
Format plików Pojedyńczy - .SDF Wiele formatów
Maksymalny rozmiar bazy 4GB 4GB
Przechowywanie XML + jako nText +
Programowanie Programowanie Programowanie
Transact-SQL + +
Proceduralny T-SQL, - +
składnia Select Case, If
Remote Data Access (RDA) + -
ADO.NET Sync Framework + Dostępne z Visual Studio 2008 - Planed support with a future version
Merge replication + +
Proste transakcje + +
Rozproszone transakcje - +
Natywna obsługa XML, XQuery/QPath - +
Stored procedures, views, triggers - +
Podział na role w zabezpieczeniach - +
Ilość jednoczesnych połączeń 256 Unlimited

www.sdjournal.org 47
Programowanie Windows Mobile

czemu aplikację można dosłownie wyklikać. frowana. Wszystkie te atrybuty zostały wane zgodnie z ich typem, a nie kon-
Oczywiście można, ale nie trzeba – mamy opisane w podrozdziale bezpieczeństwo. wertowane do łańcuchów znaków. Na-
również odpowiedni zestaw klas, by same- Najczęściej jednak connection string leży pamiętać również, że po modyfi-
mu łączyć się z bazą i wykonywać na niej za- będzie wyglądał tak jak na Listingu 1. kacjach zbioru należy samemu wymu-
pytania. Oba rozwiązania postaram się przy- • SqlCeDataAdapter - obiekty DataAdap- sić zapisanie zmian. Odbywa się to po-
bliżyć w dalszej części artykułu. Dodatko- ter definiują, jak dane mają być przeka- przez wywołanie metody AcceptChan-
wo można doinstalować różne narzędzia, zywane do obiektu DataSet i z obiek- ges obiektu DataSet. Ciekawą cechą te-
takie jak SQL Management Studio. Istotna tu DataSet. Konfiguracja obiektu Da- go obiektu jest możliwość jego seria-
jest wersja tego ostatniego. Tylko najnow- taAdapter polega zwykle na wskazaniu lizacji i wysłania poprzez usługi Web
sza– 2008 (również express) wspiera wersję poleceń SQL stosowanych do odczy- Services.
SQL Server CE 3.5. Na płycie CD znajduje tu, zapisu i modyfikacji danych. Każdy • SqlCeCommand – reprezentuje zapy-
się kopia pliku bazy danych wykorzystywa- obiekt DataAdapter służy do wymiany tanie, które jest wykonywane bezpo-
nej w artykule. danych pomiędzy jedną tabelą źródła średnio na pliku bazy. Zapytaniem
danych a pojedynczym obiektem Da- może być insert, select lub update. Nie-
Połączenie z bazą danych taTable. Obiekt ten znajduje się w Da- stety, jak wspomniałem w poprzed-
Osoby dobrze znające ADO.NET będą mile taSet. Jeśli obiekt DataSet zawiera wie- niej części artykułu, nie ma możliwo-
zaskoczone. Wykorzystanie SQL Server CE le tabel, to najczęściej stosowanym roz- ści wykonywania procedur składowa-
w swojej aplikacji opiera się właśnie na tej wiązaniem jest tworzenie wielu obiek- nych. Cała składnia oprócz wcześniej
technologii. Rysunek 2 przedstawia archi- tów DataAdapter, pobierających, zapi- wspomnianych konstrukcji jest zgod-
tekturę ADO.NET dla SQL Server CE. sujących i modyfikujących dane w poje- na z t-sql. Najczęściej instancja tego
dynczych tabelach źródeł danych. obiektu tworzona jest poprzez wyko-
• SqlCeConnection – odpowiada za fi- • SqlCeDataSet – jest to magazyn da- nanie metody ExecuteReader obiektu
zyczne połączenie z bazą danych. Połą- nych, który przechowuje w pamię- SqlCeConnection.
czenie wymaga odpowiedniego connec- ci wszystkie rekordy uzyskane po- • Data Consumer – jest to każdy obiekt,
tion stringa, w którym zawarta jest in- przez obiekt DataAdapter. Dzięki te- który wykorzystuje dane do prezenta-
formacja dotycząca tego, który plik ma mu obiektowi spora ilość kodu gene- cji lub dalszego ich przetwarzania.
zostać wczytany. Może zawierać rów- rowana jest automatycznie. Mówimy • SqlCeResultSet, który nie został pokaza-
nież opcjonalne atrybuty, takie jak ha- również, że DataSet jest silnie typowa- ny na obrazku, jest połączeniem funk-
sło do bazy oraz to, czy baza jest zaszy- ny, gdyż wszystkie pola są przechowy- cjonalności DataSet – modyfikacje da-
nych oraz SqlCeDataReader. W przy-
Listing 1. Generyczny connection string do bazy padku DataSet w pamięci tworzona jest
string connStr = "data source=\secure.sdf;" kopia danych zawartych w bazie. Dla
dużych baz może to przysporzyć kłopo-
Listing 2. Ścieżka do pliku dla connection stringa obiektu SqlCeConnection tu. SqlCeResultSet operuje bezpośred-
("Data Source =" + (System.IO.Path.GetDirectoryName( nio na fizycznym pliku sdf, dzięki cze-
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) + mu nie pobiera tyle pamięci. Instancja
"\\bazadanych.sdf;")); obiektu tworzona jest poprzez wywo-
łanie metody ExecuteResultSet obiektu
Listing 3. Szablonowe zapytanie z parametrem SqlCeCommand.
SELECT * from Customers where [Customer ID] = @custID
Tworzenie bazy może odbywać się poprzez
Listing 4. Wykonanie szablonu zapytania kreator. Aby to zrobić, należy kliknąć pra-
customersTableAdapter1.GetDataBy("BLONP"); wym przyciskiem na nasz projekt w oknie

Rysunek 4. Okno “Server Explorer” z


Rysunek 3. Okno „Add New Item” ustanowionym połączeniem

48 SDJ Extra 34 Biblia


MS SQL Server CE 3.5

Solution Explorer (domyślnie znajdującym śli nie ma takiego komponentu, należy go Swój szablon nazwałem GetDataBy. W ko-
się po prawej stronie Visual Studio). Jeśli dodać poprzez przeciągnięcie go z ToolBo- dzie wywołanie wygląda tak jak prezentu-
go nie ma, z menu view wybieramy Solution xa(zakładka nazwa_projektu Components). je to Listing 4.
Explorer, co spowoduje jego pojawienie się.
Klikamy Add new item, aby dodać do pro- Tabela 2. Opis metod SqlCeConnection
jektu nową bazę, lub Add existing item, je- Metoda Opis metody
śli chcemy dodać do projektu istniejącą ba- BeginTransaction Rozpoczyna transakcje.
zę danych. W przypadku utworzenia nowej
ChangeDatabase Zmiana aktualnie wykorzystywanej bazy danych.
bazy wyświetli się kreator, w którym klika-
my na pole Database file. Ten krok prezen- Close Zamyka połączenie. Wszystkie dodatkowe zasoby są zwalniane.
tuje Rysunek 3. CreateCommand Tworzy obiekt SqlCeCommand.
Po stworzeniu pliku należy w jego wła- GetSchema Zwraca całą strukturę data source.
ściwościach (prawy klawisz na nim w Solu- Open Otwiera połączenie z bazą danych.
tion Explorer, Properties) upewnić się, że ma-
my zaznaczoną opcję copy if never we właści- Tabela 3. Opis właściwości obiektu SqlCeConnection
wości Copy to Output. Spowoduje to utwo- Properties Property Description
rzenie kopii naszej bazy po jej kompilacji w
ConnectionString Pobiera lub ustawia connectrion stringa.
miejsce, gdzie będzie przechowywany plik
wykonywalny naszej aplikacji. Listing 2 pre- DataBase Pobiera nazwę aktualnej otwartej bazy danych.
zentuje poprawny connection string dla Sql- DataSource Pobiera całą ścieżkę do fizycznego pliku bazy danych.
CeConnection. ServerVersion Zwraca wersję Sql Servera CE.
Niestety connection string typu Data So-
State Zwraca stan, w jakim znajduje się połączenie.
urce = \bazadanych.sdf nie zadziała.
Aby połączyć się z bazą i wykonywać na Tabela 4. Opis metod obiektu SqlCeCommand
niej zapytania w Visual Studio, należy sko-
Metoda Opis metody
rzystać z okna Server Explorer. Dodając po-
łączenie (Add New Connection), wystarczy Cancel Anuluje wykonanie aktualnie przetwarzanego zapytania.
wybrać z Data Source – SQL Server CE 3.5, CreateParameter Tworzy nowy parametr.
wskazać odpowiedni plik i połączenie zosta- ExecuteNonQuery Wykonuje zapytanie do bazy i zwraca ilość zmodyfikowanych wierszy.
nie ustanowione. Widok połączenia z bazą
ExecuteReader Tworzy SqlDataReadera, którym następnie możemy czytać wiersze
prezentuje Rysunek 4. zwrócone z zapytania.
ExecuteScalar Wykonuje zapytanie i zwraca pierwszą kolumnę pierwszego wiersza
Użycie kreatorów
zwróconych przez zapytanie w zbiorze danych.
Wykorzystując kreatory do tworzenia po-
łączeń, wypełniania komponentów dany- Tabela 5. Opis właściwości obiektu SqlCeCommand
mi, możemy zaoszczędzić mnóstwo czasu.
Właściwość Opis
Aplikację można dosłownie wyklikać, gdyż
dodając bazę danych do projektu, od razu CommandText Pobiera lub ustawia tekst zapytania, które będzie wykonane.
kreator zapyta nas, czy wygenerować do te- CommandTimeOut Pobiera lub ustanawia ilość czasu oczekiwania na wykonanie zapyta-
go połączenia data seta. Bezpośrednio w Da- nania nim zostanie wygenerowany błąd.
taSet można dodawać metody, które będą CommandType Pobiera lub ustanawia, jak ma być interperetowane zapytanie.
zawierały gotowe szablony zapytań. Aby to Możliwe opcje to:
zrobić, należy: StoredProcedure – nie wykorzystywane w SQL Server CE.
Text – kiedy przekazujemy zapytanie w formie tekstu.
TableDirect—zwraca wszystkie wiersze i kolumny w tabeli.
• kliknąć prawym klawiszem na North- Command Text zawiera tylko nazwę tabeli, która będzie wyświetlona.
wind.sdf w Solution Explorer i wybrać
Connection Zwraca lub ustawia obiekt SqlCeConnection, który jest przypisany lub
Visual Designer. Możemy sprawdzić tu- ma być przypisany do obiektu.
taj związki między tabelami, usuwać
IndexName Definiuje index, który ma być wykorzystany.
kolumny oraz, jak wspomniałem wcze-
śniej, tworzyć szablon zapytań, nadając Parameters Pobiera listę kolekcji parametrów (SqlCeParameterCollection).
mu swoją nazwę; Transaction Pobiera lub ustawia transakcję, która będzie wykonana.
• kliknąć prawym klawiszem na Table-
Tabela 6. Opis metod SqlCeDataReader
Adapter tabeli Customers i wybrać Add,
wpisując w miejsce zapytania polecenie Metoda Opis metody
z Listingu 3. Close Zamyka DataReadera.
GetFieldType Pobiera typ pola dla określonej kolumny.
Będziemy mieli możliwość wykonania te-
GetName Pobiera nazwę określonej kolumny.
go szablonu jakby był zwykłą metodą z pa-
rametrem, którego typ jest zgodny z ty- GetSchemaTable Zwraca strukturę tebeli.
pem kolumny [Customer ID]. Metodę mo- GetValue Zwraca wartość kolumny.
żemy wywołać poprzez obiekt TableAdap- IsDBNull Zwraca true, false w zależności, czy typ jest Nullem.
ter bazy danych, i wpisując nazwę szablo-
Read Pobiera kolejny record.
nu. Według domyślnej notacji powinien
mieć nazwę customersTableAdapter1. Je- Seek Przechodzi do wierszy określonych w parametrze.

www.sdjournal.org 49
Programowanie Windows Mobile

Tabela 7. Opis właściwości obiektu SqlCeDataReader Bindowanie danych również opiera się na
Właściwość Opis właściwości kreatorach, które zaoszczędzają nam czas.
We właściwościach prawie każdego kompo-
Depth Zwraca głębokość zagnieżdżonego wiersza. Dotyczy podzapytań.
nentu znajdziemy Data Binding.
FieldCount Zwraca ilość kolumn w aktualnym wierszu.
HasRows Zwraca wartość true lub false w zależności, czy baza na skutek zapy- Własne zarządzanie danymi
tania zwróciła jakiś record. Aby móc wykorzystywać poniższe klasy, na-
HiddenFieldCount Zwraca ilość ukrytych pól. leży do sekcji uses dodać przestrzeń nazw
IsClosed Sprawdza, czy Reader jest zamknięty. System.Data.SqlServerCe; klasy te mają peł-
ną funkcjonalność zapytań t-sql zgodnych
RecordsEffected Zwraca ilość wierszy, ile zostało zmodyfikowanych po wykonaniu za-
pytania insert, delete, update. z SQL Server CE 3.5 i to programista ma
pełną kontrolę nad parametrami przekazy-
VisibleFieldCount Zwraca ilość pól, które nie są ukryte.
wanymi do bazy danych. Aby połączyć się z
Tabela 8. Zestawienie zbioru bibliotek dll oraz ich funkcji bazą, należy wykorzystać do tego SqlCeCon-
nection. W Tabeli 2 oraz Tabeli 3 znajduje się
Nazwa biblioteki Funkcjonalność
opis najczęściej wykorzystywanych metod
sqlcese35.dll Silnik przechowywania danych (ang. Storage Engine) tej klasy oraz jej właściwości.
sqlceqp35.dll Procesor zapytań (ang. Query Processor) Po połączeniu chcielibyśmy wykonać za-
sqlceme35.dll Native / Managed Translation Layer pytanie na tabeli. Wykorzystamy do tego
klasę SqlCeCommand. W Tabeli 4 oraz Ta-
System.Data.SqlServerCe.dll Provider do ADO.NET
beli 5 znajduje się opis najczęściej wyko-
sqlcecompact35.dll APIs do kompresji i uaktualnień rzystywanych metod tej klasy oraz jej wła-
sqlceca35.dll API dla Merge Replication oraz RDA ściwości.
sqlceoledb35.dll OleDB API – potrzebne dla C++, VB, oraz Merge/RDA SqlCeDataReader służy do odczytu da-
nych, które zostały zwrócone przez zapyta-
sqlceer35EN.dll Przechowuje nazwy błędów
nie. Dzięki niemu możemy przemieszczać
się po całym zbiorze, odczytując wartości z
Listing 5. Metoda łączenia się z bazą danych określonych kolumn. W Tabeli 6 oraz Tabe-
li 7 znajduje się opis najczęściej wykorzysty-
try wanych metod oraz właściwości klasy SqlCe-
{ DataReader.
SqlCeConnection conn = new SqlCeConnection("Data Source =" + Po wstępie teoretycznym możemy przejść
(System.IO.Path.GetDirectoryName( do praktycznego zastosowania opisanych
System.Reflection.Assembly.GetExecutingAssembly().GetName().CBase) + klas. Wszystkie operacje będą dotyczyły ba-
"\\Northwind.sdf;")); //główne połączenie z bazą danych zy danych Northwind w pliku Northwind.sdf
SqlCeCommand cmd = new SqlCeCommand("Employees", conn); załączonego na płycie CD.
cmd.CommandType = CommandType.TableDirect;// zapytanie jest nazwą tabeli Przykład 1. Uniwersalna metoda, która
conn.Open();//otwórz połączenie czyta dowolny typ, znajduje się w Listin-
SqlCeDataReader rdr = cmd.ExecuteReader();//utwórz instancję DataReadera gu 5.
string infoText = "Wersja Sql Servera CE - " + conn.ServerVersion + "/n"; Kolejny przykład będzie demonstrował
infoText += "Nazwa bazy " + conn.Database + "/n"; zapytanie wstawiające dane(insert). Wyko-
MessageBox.Show(infoText); rzystana zostanie klasa sqlParameters. Ce-
if (rdr.HasRows) chuje ją uniwersalność zastosowania – nie
{ musimy przejmować się np. czy użytkownik
List<object> Employees = new List<object>();//Generyczna lista obiektów podczas wstawiania do pola typu decimal
while (rdr.Read()) (10,2) wstawi jako znak oddzielający część
{ ułamkową od części całych kropkę czy prze-
var Employee = new cinek. Sam parametr zostanie „dopasowany”
{ do odpowiedniego typu, tak aby był zgod-
Nazwisko = rdr["Last Name"].ToString(), ny z typem pola tabeli. Oczywiście zdarza
Imię = rdr["First Name"].ToString() się, że użytkownik wprowadzi takie dane,
};//Typ anonimowy że dopasowanie nie będzie możliwe. Wtedy
Employees.Add(Employee);//nasz kontener pracowników zgłoszony zostanie wyjątek. Listing 6 zawie-
} ra kod, który odczytuje z bazy danych Tabe-
} lę employees i umieszcza odczytane wartości
else w generycznym kontenerze.
MessageBox.Show("Brak rekordów do wyświetlenia"); Przykład 2. Update do bazy
} Za sprawą tego, że baza danych jest na
catch(Exception E) urządzeniu mobilnym, nie mamy możli-
{ wości przeglądania jej przez Server Explorer
MessageBox.Show("Błąd podczas połączenia! Zwrócony został komunikat: w Visual Studio. Może to być trochę mylą-
"+E.Message); ce, ponieważ każda modyfikacja bazy przy
} użyciu IDE Visual Studio będzie miała od-
zwierciedlenie w bazie na urządzeniu mo-

50 SDJ Extra 34 Biblia


MS SQL Server CE 3.5

bilnym. Z kolei po modyfikacji bazy na urzą- ją skomplikowanego systemu instalacji, a dio skompiluje nam nasze rozwiązanie do
dzeniu mobilnym nie będzie śladu w Visu- co więcej nie dysponują dużymi zasoba- trzech plików. Kompilacja do wersji release
al Studio. mi sprzętowymi. Ponieważ SQL Server CE odbywa się poprzez wybranie z menu Build
Załóżmy, że chcemy usunąć Klienta z Ta- jest hostowany w aplikacji, niepotrzebne są � Build Solution. Oczywiście z wybraną opcją
beli Customers. Jednak aby to zrobić, musi- żadne pliki konfiguracyjne czy uruchamia- release w zakładce Build właściwości solucji.
my usunąć najpierw jego zamówienia (Ta- nie dodatkowych usług. Jedynym źródłem Wszystkie pliki przy domyślnych ustawie-
bela Orders). Aby to zrobić, musimy usunąć konfiguracji jest connection string określają- niach powinny znajdować się w katalogu na-
pozycję z Tabeli zagnieżdżonej Order Deta- cy, jak aplikacja ma się łączyć z plikiem bazy zwa solucji\bin\Release. Na te trzy pliki skła-
ils. Przy tych trzech zapytaniach wykonywa- danych. W najprostszym przypadku, kompi- da się baza danych, program exe gotowy do
nych po sobie może dojść do błędu i nie całe lując projekt do wersji release , Visual Stu- uruchomienia bezpośrednio na urządzeniu
zadanie zostanie wykonane. Będzie to kata- Tabela 9. Wyniki dla Desktop
strofalne w skutkach, gdyż pojawią się prze-
Linq to XML Access 2007 Sql Server CE
kłamania w bazie, np. wszystkie szczegóły
dotyczące zamówienia zostaną usunięte, a Nazwa pliku data.xml data.accdb data.sdf
pozostanie informacja o istnieniu zamówie- Rozmiar pliku Przed insertem Nie dotyczy 280KB
nia. Podczas odczytywania danych okaże się, 20KB Po insercie 333 KB
że kwota zamówienia będzie równa 0. Dla- 480KB
tego najlepszym rozwiązaniem jest wprowa- 224KB
dzenie transakcji. W przypadku błędu przy Zapytanie Create table Nie dotyczy 0,4064 sek. 0,1795 sek.
wykonaniu któregokolwiek z zapytań, au- Zapytanie Insert(1014 wierszy) 5,2366 sek. 4,0495 sek. 1,0396 sek.
tomatycznie zostanie przywrócona wersja
Zapytanie Select 0,0305 sek. 0,0664 sek. 0,0413 sek.
sprzed rozpoczęcia operacji.
Przykład 3. Wykorzystanie SqlCeTransact Zapytanie Delete Nie dotyczy 0,0766 sek. 0,1184 sek.

Synchronizacja Listing 6. Przykład zastosowania zapytania Insert


Funkcjonalność SQL Server CE nie ograni-
cza się tylko do lokalnej bazy danych. Moż- try
na również zastosować tzw. replikację, któ- {
ra odbywa się za pośrednictwem Internetu. SqlCeConnection conn = new SqlCeConnection("Data Source =" +
Replikacja umożliwia synchronizację lokal- (System.IO.Path.GetDirectoryName(
nej bazy danych (na urządzeniu mobilnym) System.Reflection.Assembly.GetExecutingAssembly().GetName().Code
oraz globalnej (dostępnej np. w firmie). Wy- Base) +
obraźmy sobie sytuację, w której przedsta- "\\Northwind.sdf;"));//połączenie z bazą danych
wiciel handlowy zbiera zamówienia poza
firmą. Oczywiście chcielibyśmy, aby infor- string[,] Categories = { { "Drinks", "Cola, Pepsi" },
macje o aktualnych zamówieniach były do- { "Tea", "White Tea, Black Tea" },
stępne również w firmie, dla której pracu- { "Alcohols", "Bear, Vine" } };
je. Jednym z rozwiązań może być wykorzy- //lista kategorii, które będziemy wstawiać
stanie usług Web Services do uaktualnienia // nasze zapytanie posiada 2 parametry, które w czasie wykonania będą dodawane
bazy danych zawartej na serwerze. Jednak co dynamicznie
jeśli w danej chwili przedstawiciel handlowy SqlCeCommand cmd = new SqlCeCommand(@"INSERT INTO Categories
nie ma dostępu do Internetu? Jednym z roz- ([Category Name], Description, Picture)
wiązań jest właśnie replikacja, dzięki której VALUES
przedstawiciel zbiera informację do swojej (@categoryName,@description,null)", conn);
lokalnej bazy danych. Z kolei kiedy ma do- int count = 0;
stęp do Internetu, może wysłać uaktualnie- conn.Open();
nie do bazy głównej. Działa to w obie stro- for(int i=0;i<Categories.GetLength(0);i++)
ny, gdyż poprzez główną bazę danych można {
uaktualniać informację dla przedstawiciela cmd.Parameters.Clear();//wyczyść poprzednie parametry
handlowego. Ilustruje to Rysunek 4. cmd.Parameters.AddWithValue("@categoryName", Categories[i, 0]);
Przy czym główną bazą danych musi być cmd.Parameters.AddWithValue("@description", Categories[i, 1]);
SQL Server w dowolnej wersji. Istnieją 4 me- count += cmd.ExecuteNonQuery();//wykonaj i uaktualnij listę dodanych
tody synchronizacji SQL Server z SQL Se- rekordów
rver CE 3.5: }
MessageBox.Show("Wstawionych rekordów - " + count);
• usługi Web Services; }
• Remote Data Access; catch (Exception E)
• Merge Replication; {
• Sync Services for ADO.NET. MessageBox.Show("Błąd podczas połączenia! Zwrócony został komunikat: " +
E.Message);
Dystrybucja oprogramowania }
SQL Server Compact Edition został za- }
projektowany z myślą o łatwej dystrybu-
cji. Urządzenia takie jak Pocket PC nie ma-

www.sdjournal.org 51
Programowanie Windows Mobile

Tabela 10. Wyniki dla PocketPC mobilnym oraz dodatkowy plik wykorzysty-
Linq to XML Sql Server CE wany przez Visual Studio do Debuggingu.
Jak już wspomniałem wcześniej, SQL Se-
Rozszerzenie pliku data.xml data.sdf
rver CE 3.5 fizycznie jest zbiorem biblio-
Rozmiar pliku Przed insertem Nie dotyczy tek dll. Ich nazwy oraz opis prezentuje Ta-
20KB Po insercie bela 8.
333 KB
224KB Bezpieczeństwo
Zapytanie Create table Nie dotyczy 2,4569 sek. Z racji tego, że nasza aplikacja składa się z
Zapytanie Insert(1014 wierszy) 760,9975 sek. 27,9072 sek. pliku wykonywalnego i pliku bazy danych
naturalnie nasuwa się pytanie dotyczące
Zapytanie Select 2,1460 sek. 0,3091 sek.
bezpieczeństwa tego rozwiązania. Przecież
Zapytanie Delete Nie dotyczy 0,3702 sek. każdy ma dostęp do tego pliku i każdy przy

Listing 7. Przykładowe zapytania usuwające dane

SqlCeConnection conn = null;


SqlCeTransaction trans = null;
try
{
conn = new SqlCeConnection("Data Source =" +
(System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) +
"\\Northwind.sdf;"));//połączenie z bazą danych
conn.Open();
trans = conn.BeginTransaction();

//usuwanie rekordów najbardziej zagnieżdżonych


SqlCeCommand cmdOrdDet = new SqlCeCommand(@"delete from [Order Details] where [Order ID] in (select [Order ID]
from Orders where [Customer ID] in

(select [Customer ID] from Customers where Country = N'Venezuela'))", conn);


cmdOrdDet.Transaction = trans;

//usunięcie rekordów, których zamawiający pochodzi z Wenezueli


SqlCeCommand cmdOrd = new SqlCeCommand(@"delete from Orders where [Customer ID] in
(select [Customer ID] from Customers where Country = N'Venezuela')");
cmdOrd.Transaction = trans;

//usunięcie klienta z Wenezueli


SqlCeCommand cmdCust = new SqlCeCommand(@"delete from Customers where
Country = N'Venezuela'", conn);
cmdCust.Transaction = trans;

int count = cmdOrdDet.ExecuteNonQuery();//wykonaj zapytanie na Order Details


count += cmdOrd.ExecuteNonQuery();//wykonaj zapytanie na Order
count += cmdCust.ExecuteNonQuery();//wykonaj zapytanie na Customers
MessageBox.Show("Liczba usuniętych rekordów - " + count);
trans.Commit();//jeśli bez błędu, to zaakceptuj zmiany
}
catch (Exception E)
{
if (trans != null)
trans.Rollback();//jesli błąd, to cofnij zmiany
MessageBox.Show("Błąd podczas połączenia! Zwrócony został komunikat: " + E.Message);
}
finally
{
if (conn != null && conn.State == ConnectionState.Open)
conn.Close();//jeśli połączenie otwarte, to je zamknij
}

52 SDJ Extra 34 Biblia


MS SQL Server CE 3.5

użyciu ogólnodostępnych narzędzi może pliku lub zabezpieczenia hasłem. Wszyst- Podczas połączenia w connection stringu
odczytać dane zawarte w bazie. Oczywiście kie zapytania można wykonywać poprzez należy wprowadzić dodatkowe atrybuty. Li-
wszystko zależy od tego, czy dane są jawne. obiekt SqlCeEngine.CreateDatabase w con- sting 10 prezentuje szablonowy connection
Jeśli tak, to nie musimy się martwić. Co jed- nection stringu, wpisując odpowiednie po- string dla połączenia z zaszyfrowaną i zabez-
nak, kiedy dane są poufne? Microsoft prze- lecenie tworzące. Listing 8 zawiera zapyta- pieczoną hasłem bazą danych.
widział również taki scenariusz. Plik bazy nie, które tworzy plik bazy danych zabezpie- Tworzenie bazy zabezpieczonej jedynie
danych można zabezpieczyć hasłem lub za- czony hasłem. hasłem prezentuje Listing 11.
szyfrować. Oczywiście programista nie musi Z kolei stworzenie zaszyfrowanej oraz Daje to dodatkowy poziom bezpieczeń-
robić wszystkiego ręcznie, a jedynie podczas zabezpieczonej hasłem bazy prezentuje Li- stwa bez obciążania programisty implemen-
tworzenia bazy zaznaczyć opcję szyfrowania sting 9. tacją ręcznego szyfrowania. Niestety w tym

Listing 8. Zapytanie tworzące bazę danych z hasłem dostępu


Create Database "secure.sdf" databasepassword '<myPassword>'

Listing 9. Zapytanie tworzące bazę danych z hasłem dostępu i szyfrowaniem pliku


Create Database "secure.sdf" databasepassword '<password>' encryption on

Listing 10. Connection string dla zaszyfrowanej bazy z hasłem dostępu


data source=\secure.sdf;password=<myPassword>;encrypt database=TRUE

Listing 11. Connection string dla bazy z hasłem dostępu


data source=\NorthWind.sdf; password=<myPassword>

Listing 12. Metody XMLInsert oraz XMLSelect


private void XMLInsert()
{
if (File.Exists("data.xml"))
File.Delete("data.xml");
XDocument xdoc = new XDocument(new XElement("Orders"));
foreach(Order order in Orders)
{

xdoc.Element("Orders").Add(new XElement("Order",
new XAttribute("OrderID",order.OrderID.ToString()),
new XAttribute("CustomerID", order.CustomerID == null ? "NULL" : order.CustomerID.ToString()),
new XAttribute("EmployeeID", order.EmployeeID.ToString()),
new XAttribute("ShipName", order.ShipName == null ? "NULL" : order.ShipName.ToString()),
new XAttribute("ShipAddress", order.ShipAddress == null ? "NULL" : order.ShipAddress.ToString()),
new XAttribute("ShipCity", order.ShipCity == null ? "NULL" : order.ShipCity.ToString()),
new XAttribute("ShipRegion", order.ShipRegion == null ? "NULL" : order.ShipRegion.ToString()),
new XAttribute("ShipPostalCode", order.ShipPostalCode == null ? "NULL" : order.ShipPostalCode.ToString()),
new XAttribute("ShipCountry", order.ShipCountry == null ? "NULL" : order.ShipCountry.ToString()),
new XAttribute("shipVia", order.ShipVia.ToString()),
new XAttribute("OrderDate", order.OrderDate.ToString()),
new XAttribute("RequireDate", order.RequiredDate.ToString()),
new XAttribute("ShippedDate", order.ShippedDate.ToString()),
new XAttribute("Freight", order.Freight == null ? "NULL" : order.Freight.ToString())));
xdoc.Save("data.xml");
}
}
private void XMLSelect()
{
if (File.Exists("data.xml"))
{
XDocument doc = XDocument.Load("data.xml");
var query = from c in doc.Elements("Orders") select c;
foreach (var item in query)
{
item.Elements("Order");
}
}
}

www.sdjournal.org 53
Programowanie Windows Mobile

przypadku nie ma podziału na role, dlatego


Listing 13. Metody do tworzenia, wstawiania, wyświetlania i usuwania danych w bazie Access 2007 jeśli ktoś zna connection stringa, ma dostęp
private void CreateAccessDatabase() do całej bazy. Jednak dla większości scena-
{ riuszy jest to rozwiązanie wystarczające.
OleDbConnection conn = new OleDbConnection(AccessConn);
OleDbCommand cmd = new OleDbCommand(cmdCreateTabStr, conn); Wydajność
conn.Open(); Testując wydajność, zestawiłem trzy podob-
cmd.ExecuteNonQuery(); ne do siebie rozwiązania. Wszystkie zostały
conn.Close(); stworzone do tego samego celu – przecho-
} wywania informacji. Pierwszym z nich jest
private void InsertToAccess() Linq to XML, który dostępny jest w .NET
{ 3.5 oraz .NET CF 3.5. Najwięcej proble-
OleDbConnection conn = new OleDbConnection(AccessConn); mów sprawiła mi właśnie technologia LINQ
OleDbCommand cmd = new OleDbCommand(cmdInsertTo, conn); to XML. Nie z powodu tego, że technologia
conn.Open(); ta jest trudna w implementacji, lecz z powo-
foreach(Order order in Orders) du problemów z odpowiednim odniesie-
{ niem jej do pozostałych technologii. Gene-
cmd.Parameters.Clear(); ralnie cały problem pojawił się podczas za-
cmd.Parameters.AddWithValue("@OrderID", order.OrderID); pisywania danych. Otóż klasa XDocument
cmd.Parameters.AddWithValue("@CustomerID",order.CustomerID); służąca do manipulowania danymi tworzy
cmd.Parameters.AddWithValue("@EmployeeID",order.EmployeeID);
cmd.Parameters.AddWithValue("@ShipName",order.ShipName);
cmd.Parameters.AddWithValue("@ShipAddress",order.ShipAddress);
cmd.Parameters.AddWithValue("@ShipCity",order.ShipCity);
cmd.Parameters.AddWithValue("@ShipRegion",order.ShipRegion);
cmd.Parameters.AddWithValue("@ShipPostalCode",order.ShipPostalCode);
cmd.Parameters.AddWithValue("@ShipCountry",order.ShipCountry);
cmd.Parameters.AddWithValue("@ShipVia",order.ShipVia);
cmd.Parameters.AddWithValue("@OrderDate",order.OrderDate);
cmd.Parameters.AddWithValue("@RequiredDate",order.RequiredDate);
cmd.Parameters.AddWithValue("@ShippedDate",order.ShippedDate);
cmd.Parameters.AddWithValue("@Freight",order.Freight);
foreach (OleDbParameter parameter in cmd.Parameters)
{
if (parameter.Value == null)
{
parameter.Value = DBNull.Value;
}
} Rysunek 4. Schemat bazy danych dla
cmd.ExecuteNonQuery(); przykładowej firmy
}
conn.Close(); Tabela 11. Zestawienie plusów i minusów
rozwiązania
}
private void SelectAccess() Plusy Minusy
{ Obsługa transakcji Brak podziału na
OleDbConnection conn = new OleDbConnection(AccessConn); użytkowników
OleDbCommand cmd = new OleDbCommand(cmdSelectFrom, conn); Obsługa replikacji Brak perspektyw,
conn.Open(); procedur składowa-
OleDbDataReader rdr = cmd.ExecuteReader(); nych oraz Triggerów
while (rdr.Read()) Możliwość wizualne-
{ go projektowania ba-
; zy danych
} Zgodność z ADO.NET
conn.Close();
128 bitowe zabezpie-
} czenia
private void DeleteFromAccess()
Procesor zapytań
{
Łatwe bindowanie
OleDbConnection conn = new OleDbConnection(AccessConn);
danych
OleDbCommand cmd = new OleDbCommand(cmdDelete, conn);
conn.Open(); Możliwość zdefinio-
wania funkcji w Da-
cmd.ExecuteNonQuery();
ta Set
conn.Close();
Łatwa do przeniesie-
}
nia baza danych

54 SDJ Extra 34 Biblia


MS SQL Server CE 3.5

całą strukturę w pamięci, a następnie ca-


Listing 14. Metody analogiczne do bazy Access zaimplementowane dla SQL Server CE łość jest zapisywana na dysk. W przypadku
private void CreateSQLDatabase() pozostałych technologii każda operacja jest
{ niezależna. Oznacza to, że błąd w wykona-
SqlCeConnection conn = new SqlCeConnection(SqlConn); niu danej operacji nie spowoduje utraty da-
SqlCeCommand cmd = new SqlCeCommand(cmdCreateTabStr, conn); nych zmodyfikowanych we wcześniejszych
conn.Open(); operacjach. W przypadku LINQ to XML po
cmd.ExecuteNonQuery(); wystąpieniu błędu utracone zostaną wszyst-
conn.Close(); kie dane. Dlatego zdecydowałem się na dra-
} styczny krok i po każdym dodanym wierszu
private void InsertToSQL() odbędzie się aktualizacja pliku. Spowodowa-
{ ło to bardzo duże opóźnienia, jednak uwa-
SqlCeConnection conn = new SqlCeConnection(SqlConn); żam, że takie rozwiązanie jest najbardziej
SqlCeCommand cmd = new SqlCeCommand(cmdInsertTo, conn); sprawiedliwe.
conn.Open(); Kolejną technologią, jaką wykorzystałem
foreach (Order order in Orders) w artykule, jest połączenie z bazą danych
{ silnika Microsoft Access 2007. Wszystko
cmd.Parameters.Clear(); działa również w postaci jednego pliku ac-
cmd.Parameters.AddWithValue("@OrderID", order.OrderID); cdb. Do połączenia wykorzystuję technolo-
cmd.Parameters.AddWithValue("@CustomerID", order.CustomerID); gię OleDB.
cmd.Parameters.AddWithValue("@EmployeeID", order.EmployeeID); W końcu ostatnie rozwiązanie – SQL Se-
cmd.Parameters.AddWithValue("@ShipName", order.ShipName); rver CE. Do połączenia wykorzystuje prze-
cmd.Parameters.AddWithValue("@ShipAddress", order.ShipAddress); strzeń nazw System.Data.SqlServerCe.
cmd.Parameters.AddWithValue("@ShipCity", order.ShipCity); W przypadku PocketPC mamy do wybo-
cmd.Parameters.AddWithValue("@ShipRegion", order.ShipRegion); ru tylko dwie technologie: Linq To XML
cmd.Parameters.AddWithValue("@ShipPostalCode", order.ShipPostalCode); oraz SQL Server CE. Aplikacja została prze-
cmd.Parameters.AddWithValue("@ShipCountry", order.ShipCountry); testowana na emulatorze Windows Mobile
cmd.Parameters.AddWithValue("@ShipVia", order.ShipVia); 6.0 Professional.
cmd.Parameters.AddWithValue("@OrderDate", order.OrderDate); Aplikacja, którą testowałem rozwiązania,
cmd.Parameters.AddWithValue("@RequiredDate", order.RequiredDate); dostępna jest na załączonej płycie CD. Jak
cmd.Parameters.AddWithValue("@ShippedDate", order.ShippedDate); widać, najszybszym rozwiązaniem jest SQL
cmd.Parameters.AddWithValue("@Freight", order.Freight); Server CE 3.5.
foreach (SqlCeParameter parameter in cmd.Parameters)
{ Podsumowanie
if (parameter.Value == null) Podsumowując, SQL Server CE 3.5 ma bar-
{ dzo szerokie zastosowanie, zarówno dla ma-
parameter.Value = DBNull.Value; łych, jak i średnich aplikacji pisanych na
} platformę Windows Mobile. Obsługa T-
} SQL powinna szczególnie spodobać się oso-
cmd.ExecuteNonQuery(); bom, które swoje aplikacje pisały już w opar-
} ciu o pełną wersję SQL Server. Dodatkowo
conn.Close(); możliwość wizualnego projektowania bazy
} danych bardzo upraszcza i przyspiesza two-
private void SelectSQL() rzenie aplikacji. Dzięki wprowadzeniu za-
{ bezpieczeń bazy danych oraz jej szyfrowania
SqlCeConnection conn = new SqlCeConnection(SqlConn); aplikacja jest dobrze zabezpieczona przed
SqlCeCommand cmd = new SqlCeCommand(cmdSelectFrom, conn); atakami. Na pewno dużym minusem jest
conn.Open(); brak wsparcia dla procedur składowanych,
SqlCeDataReader rdr = cmd.ExecuteReader(); perspektyw oraz triggerów. Zestawienie plu-
while (rdr.Read()) sów i minusów prezentuje Tabela 11.
{
;
}
conn.Close();
} ŁUKASZ STRĄK
private void DeleteFromSQL() Łukasz Strąk jest Microsoft Student Partnerem na
{ Uniwersytecie Śląskim w Katowicach. Prowadzi
SqlCeConnection conn = new SqlCeConnection(SqlConn); tam lokalną grupę .NET, która zrzesza pasjona-
SqlCeCommand cmd = new SqlCeCommand(cmdDelete, conn); tów technologii Microsoft. Jest również studen-
conn.Open(); tem III roku Informatyki na tamtejszym Uniwersy-
cmd.ExecuteNonQuery(); tecie. Szczególnie interesuje się technologiami in-
conn.Close(); ternetowymi, programowaniem urządzeń mobil-
} nych, VSTO oraz WPF.
Kontakt z autorem: lukasz.strak@studentpartner.pl

www.sdjournal.org 55
Programowanie Windows Mobile

Moc pod kontrolą


Efektywne zarządzanie zasilaniem w aplikacjach dla systemu
Windows Mobile
Odwiecznym problemem i wyzwaniem dla konstruktorów urządzeń
mobilnych była możliwość ich długiej pracy z wykorzystaniem
zasilania bateryjnego. Cel ten został w dużej mierze osiągnięty, ale
mimo wszystko źle napisana aplikacja potrafi zmusić użytkownika do
częstszego używania ładowarki.
• Resuming – tryb „budzenia się”. Pod-
Dowiesz się: Powinieneś wiedzieć: świetlenie i ekran są wyłączone, aplika-
• O zarządzaniu energią w urządzeniach Win- • Jak tworzyć aplikacje mobilne dla Windows cje mają 15 sekund na przełączenie urzą-
dows Mobile; Mobile z wykorzystaniem Visual Studio dzenia w inny stan zasilania, w przeciw-
• Jak tworzyć aplikacje i nie powodować nad- • Znać język C# nym przypadku urządzenie przejdzie w
miernego zużycia; • Nie zaszkodzi znajomość wywołania metod stan wstrzymania;
• Jak kontrolować tryby zasilania urządzeń Win API za pomocą mechanizmu • Suspended – tryb zatrzymania urządze-
Windows Mobile. nia (w tym pracy procesora). Wyjście z
tego stanu może zostać dokonane przez
zdarzenie pochodzące od układu peryfe-
Model zarządzania energią w Windows ryjnego (np. moduł GSM);
Mobile Standard jest prosty. Urządzenie
Poziom jest cały czas w trybie „ON”. Użycie przy- Listing 1. Nieskończona pętla
trudności cisku ON/OFF urządzenia spowoduje je-
go całkowite wyłączenie i niemożność np. while(true)
prowadzenia rozmowy telefonicznej. Je- {
Odrobina teorii dynym sposobem na oszczędzenie baterii // Działanie wątku
Urządzenia z Windows Mobile 6 z punktu jest zmniejszenie jasności podświetlenia if (bBatteryDeadYet) {
widzenia SDK można podzielić na: ekranu, a następnie jego wyłączenie oraz break;
zmniejszenie prędkości działania proceso- }
• Windows Mobile Professional (odpo- ra. Wszystkie uruchomione wcześniej apli- if (EndOfThread) {
wiada to w WM5 urządzeniom typu kacje i wątki działają w tym stanie. break;
PocketPC – wyposażonym w ekran do- W przypadku urządzeń Windows Mobi- }
tykowy); le Professional system zarządzania energią }
• Windows Mobile Standard (odpowiada działa w dwóch płaszczyznach:
to w WM5 urządzeniom typu Smart- Listing 2. Nieskończona pętla z
phone – bez ekranu dotykowego). • systemu operacyjnego (System Power opóźnieniem czasowym
States);
Każde z urządzeń z Windows Mobile na po- • peryferiów urządzenia (Device Driver while(true)
kładzie oprócz wyświetlacza i klawiatury mo- Power States). {
że zawierać szereg różnych peryferiów rozsze- //odczekaj 100ms i daj odpocząć
rzających możliwości urządzenia: moduł GPS, Dla Windows Mobile Professional dostępne procesorowi
moduł WLAN, Bluetooth, których działanie są następujące wartości System Power States: System.Threading.Thread.Sleep(100);
również może powodować szybsze zużycie ba-
terii. I o ile standardowo w urządzeniach moż- • On – tryb pełnego zasilania; // Działanie wątku
na kontrolować jasność i czas działania pod- • BacklightOff – tryb wyłączenia pod- if (bBatteryDeadYet) {
świetlenia ekranu (co jest dla baterii chyba naj- świetlenia ekranu; break;
większym zadaniem), o tyle do kontrolowania • ScreenOff – tryb wyłączenia ekranu; }
stanu działania dodatkowych modułów ko- • Unattended – tryb wyłączenia ekranu, if (EndOfThread) {
nieczne są zewnętrzne aplikacje. podświetlenia ekranu oraz urządzeń au- break;
Z uwagi na znaczne różnice w zarządzaniu dio. Uruchomione aplikacje nadal działa- }
energią między Windows Mobile Standard a ją w urządzeniu, jednak z punktu widze- }
Professional, poszczególne modele zostaną nia użytkownika urządzenie jest nieak-
opisane osobno: tywne;

56 SDJ Extra 34 Biblia


Moc pod kontrolą

• Off – urządzenie jest wyłączone – działa je- ko parametry przyjmuje ścieżkę do pliku wy- • NOTIFICATION _ EVENT _ TIME _ CHANGE
dynie podtrzymanie pracy układu zegara. konywalnego, oraz identyfikator zdarzenia – zmiana czasu;
uruchamiającego aplikację. Oto lista dostęp- • NOTIFICATION _ EVENT _ SYNC _ END –
Każde z peryferiów urządzenia może przyj- nych zdarzeń: zakończenie synchronizacji z PC;
mować stany zasilania:
Listing 3. Efektywne wykorzystanie obiektu klasy Timer
• DO – pełne zasilanie urządzenia;
• D1 – urządzenie działa w trybie oszczę- using System;
dzania energii; using System.Collections.Generic;
• D2 – urządzenie w stanie czuwania; using System.ComponentModel;
• D3 – urządzenie w stanie wstrzymania; using System.Data;
• D4 – urządzenie całkowicie odłączone using System.Drawing;
od zasilania. using System.Text;
using System.Windows.Forms;
Dobre praktyki programowania
Jako programista masz wielki wpływ na efek- namespace TimerAppState
tywność wykorzystania baterii w swoich apli- {
kacjach. Oto kilka rad, na co należy zwrócić public partial class Form1 : Form
uwagę. {
Timer t;
Nieskończone pętle
Najczęstszą konstrukcją odpowiedzialną za public Form1()
wykonywanie wątku jest działanie aplika- {
cji w pętli. InitializeComponent();
Taka konstrukcja obciąża niezwykle moc- }
no procesor, a co za tym idzie rośnie zuży-
cie energii. Warto w takim przypadku w cia- private void Form1_Load(object sender, EventArgs e)
ło pętli wstawić niewielkie opóźnienie w celu {
zmniejszenia aktywności procesora. t = new Timer();
t.Interval = 1000;
t.Tick += new EventHandler(t_Tick);
Aktywność timer’ów t.Enabled = true;
w zależności od stanu aplikacji }
Zdarza się, że w aplikacji używane są timer’y,
których zadaniem jest periodyczne wykonywa- void t_Tick(object sender, EventArgs e)
nie czynności w aplikacji podczas jej działania., {
np. odświeżanie danych w polu tekstowym. Je- lblTime.Text = DateTime.Now.ToShortDateString() + "" + DateTime.Now.To
śli nasza aplikacja jest w danym momencie nie- LongTimeString();
widoczna – zasłonięta przez inną aplikację – ta- }
kie odświeżanie nie jest do końca uzasadnione.
Należy więc zadbać o to, aby w takim przypad- private void Form1_Closing(object sender, CancelEventArgs e)
ku wstrzymać działanie timer’a i przywrócić je, {
gdy aplikacja przejdzie na „pierwszy plan”. Z t.Tick -= t_Tick;
pomocą przychodzą tu zdarzenia Activate i De- t.Enabled = false;
activate klasy System.Windows.Forms.Form. Na- t.Dispose();
leży też oczywiście pamiętać o prawidłowym }
zwolnieniu obiektu podczas zamykania aplika-
cji (w zdarzeniu Closing). private void Form1_Deactivate(object sender, EventArgs e)
{
Cykliczne wywoływanie aplikacji t.Enabled = false;
Jeśli celem działania Twojej aplikacji jest }
okresowe sprawdzanie pewnych warun-
ków, np. terminarza, lokalnej bazy danych, private void Form1_Activated(object sender, EventArgs e)
a przez pozostałe 99% czasu się „nudzi”, {
to zastanów się nad wykorzystaniem funk- t.Enabled = true;
cji CeRunAppAtTime(string application, }
SystemTime startTime) z CoreDll.dll. Funk-
cję tę należy wywołać z poziomu kodu zarzą- private void menuExit_Click(object sender, EventArgs e)
dzanego za pomocą mechanizmu P/Invoke. {
Możliwe jest również automatyczne Close();
uruchomienie aplikacji w momencie zaj- }
ścia jednego z poniższych zdarzeń. Służy }
do tego metoda CeRunAppAtEvent(string }
pwszAppName, int lWhichEvent), która ja-

www.sdjournal.org 57
Programowanie Windows Mobile

• NOTIFICATION _ EVENT _ ON _ AC _ POWER


Listing 4. Cykliczne uruchamianie aplikacji – podłączenie zasilania zewnętrznego;
using System; • N O T IF IC A T IO N _ E V E N T _ O F F _ A C _
using System.Collections.Generic; POWER – odłączenie zasilania zewnętrz-
using System.Text; nego;
using System.Runtime.InteropServices; • NOTIFICATION _ EVENT _ NET _ CONNECT
using System.Reflection; – połączenie urządzenia z siecią;
• N O TIFIC ATIO N _ E V E N T _ N ET _
namespace RunAgainProgram DISCONNECT – odłączenie urządzenia od
{ sieci;
class Program • N O T I F IC A T IO N _ E V E N T _ D E V IC E _
{ CHANGE – włożenie/wyciągnięcie karty
[StructLayout(LayoutKind.Sequential)] pamięci;
public class SystemTime • NOTIFICATION _ EV ENT _ IR _
{ DISCOVERED – wykrycie urządzenia z
public ushort wYear; modułem IrDA;
public ushort wMonth; • N O TIF IC A TIO N _ E V E N T _ R S232 _
public ushort wDayOfWeek; DETECTED – podłączenie do urządzenia
public ushort wDay; poprzez port szeregowy;
public ushort wHour; • N O TIF IC A TIO N _ E V E N T _ R E ST O R E _
public ushort wMinute; END– odtworzenie systemu po operacji
public ushort wSecond; hard-reset;
public ushort wMilliseconds; • NOTIFICATION _ EVENT _ WAKEUP – wyj-
} ście urządzenia ze stanu uśpienia;
• NOTIFICATION _ EVENT _ TZ _ CHANGE –
[DllImport("CoreDLL")] zmiana strefy czasowej;
public static extern int CeRunAppAtTime(string application, SystemTime • N O TIF IC A TIO N _ E V E N T _ M AC H I N E _
startTime); NAME _ CHANGE – zmiana nazwy urzą-
dzenia;
[DllImport("CoreDLL")] • NOTIFICATION _ EVENT _ NONE – usuwa
public static extern int FileTimeToSystemTime(ref long lpFileTime, skojarzenie wywołania aplikacji.
SystemTime lpSystemTime);
Don’t stop the application
[DllImport("CoreDLL")] Gdy nasza aplikacja jest uruchomiona i nie
public static extern int FileTimeToLocalFileTime(ref long lpFileTime, ref wchodzi w interakcje z użytkownikiem, to po
long lpLocalFileTime); pewnym czasie (w przypadku urządzeń Po-
cketPC) system zarządzania pamięcią najpierw
wyłączy podświetlenie, potem wyłączy ekran,
public static void RunAppAtTime(string applicationEvent, DateTime aby na koniec wprowadzić urządzenie w tryb
startTime) uśpienia. Aby przeciwdziałać temu stanowi,
{ można skorzystać z jednej z funkcji Power API,
long fileTimeUTC = startTime.ToFileTime(); tj. SystemIdleTimeReset(), i kasować licznik od-
long fileTimeLocal = 0; powiedzialny za odmierzanie czasu do uśpienia
SystemTime systemStartTime = new SystemTime(); systemu operacyjnego tuż przed próbą przejścia
FileTimeToLocalFileTime(ref fileTimeUTC, ref fileTimeLocal); w ten stan. Ale jak określić ten interwał ? Z po-
FileTimeToSystemTime(ref fileTimeLocal, systemStartTime); mocą przychodzi nam rejestr systemowy, gdzie
CeRunAppAtTime(applicationEvent, systemStartTime); w kluczu HKLM\SYSTEM\CurrentControlSet\
} Control\Power przechowywane są informacje
o zwłoce w wyłączeniu ekranu i urządzenia w
static void Main(string[] args) przypadku zasilania bateryjnego i zewnętrzne-
{ go. Należy znaleźć najmniejszą z tych warto-
//wykonaj kod aplikacji ści i ustawić timer w aplikacji na nieco mniej-
System.Console.WriteLine(DateTime.Now.ToLongTimeString()); szą wartość w celu wywołania wspomnianej już
funkcji SystemIdleTimeReset().
//pobierz pełną nazwę pliku EXE ze ścieżką Wyżej wymieniony sposób niestety nie za-
string exeFile = typeof(Program).Assembly.GetModules()[0].FullyQualifi działa, jeśli użytkownik Pocket PC wciśnie przy-
edName; cisk „On/Off”, dając sygnał urządzeniu do wej-
//ustaw następne uruchomienie za godzinę ścia w stan uśpienia. Jednak i tu z pomocą przy-
RunAppAtTime(exeFile, DateTime.Now.AddHours(1.0)); chodzi jedna z metod Power API – PowerPolicy-
} Notify(PPNMessage dwMessage, int option). Na
} czas działania aplikacji dodatkowo należy wy-
} wołać dla niej tryb pracy UNATTENDED.
Zarządzanie mocą może być również do-
konywane na poziomie poszczególnych

58 SDJ Extra 34 Biblia


Moc pod kontrolą

układów wchodzących w skład urządzenia. podświetlenie to bkl1:, głośniki wav1:, a feriów. Służy do tego funkcja ReleasePowerRe
Standardowo w rejestrze systemowym w wbudowany moduł GPS – gpd0: quirement(IntPtr hPowerReq).
gałęzi HKEY_LOCAL_MACHINE\System\ • DeviceState jest stanem zasilania i
CurrentControlSet\Control\Power\State znaj- przyjmuje wartości od D0 do D4 Słowem zakończenia
duje się lista stanów urządzenia, a dla każde- Mam nadzieję, że zaprezentowany artykuł
go z nich zdefiniowana jest lista peryferiów Pozostałe parametry są zwyczajowo stałe dla wprowadził Czytelników gładko w temat efek-
wraz z odpowiadającymi im stanami zasilania. wszystkich standardowych wywołań tej funk- tywnego programowania urządzeń Windows
Wartość 0 – odpowiada stanowi D0, 1 – D1 cji a rezultatem jej działania w przypadku po- Mobile z uwzględnieniem mechanizmów za-
itd. Programista, który chce np. żeby wbudo- wodzenia niezerowa wartość. Dobrze jest ją za- rządzania energią i pozwoli odbiorcom Wa-
wany odbiornik GPS był aktywny przez ca- pamiętać, tak aby na zakończenie programu szych aplikacji na rzadsze używanie ładowarek
ły czas działania aplikacji nie musi modyfi- przywrócić oryginalny stan zasilania dla pery- sieciowych.
kować jednak zawartości rejestru; wystarczy
posłużyć się funkcji Win API – SetPowerRequ Listing 5. Uruchomienie aplikacji w momencie wystąpienia określonego zdarzenia
irement(string pDevice, CEDEVICE_POWER_
STATE DeviceState, DevicePowerFlags //mechanizm P/Invoke w celu uzyskania dostępu do CeRunAppAtEvent
DeviceFlags, IntPtr pSystemState, uint [DllImport("coredll.dll", EntryPoint="CeRunAppAtEvent", SetLastError=true)]
StateFlagsZero). Kilka słów na temat para- private static extern bool CeRunAppAtEvent(string pwszAppName, int lWhichEvent);
metrów – oto najważniejsze z nich:
//uruchom kalkulator w momencie wyjścia z trybu uśpienia
• pDevice jest nazwą modułu sprzętowe- CeRunAppAtEvent(@"\Windows\Calc.exe", (int)WhichEvent.NOTIFICATION_EVENT_WAKEUP);
go wchodzącego w skład urządzenia np.

Listing 6. Niedopuszczenie urządzenia do przejścia w stan wstrzymania

using System; if (oACTimeOut is int)


using System.Collections.Generic; {
using System.ComponentModel; int v = (int)oACTimeOut;
using System.Data; if(v>0) retVal = Math.Min(retVal, v);
using System.Drawing; }
using System.Text; if (oScreenPowerOff is int)
using System.Windows.Forms; {
using System.Runtime.InteropServices; int v = (int)oScreenPowerOff;
using Microsoft.Win32; if(v>0) retVal = Math.Min(retVal, v);
}
namespace DontStopTheApp //ustaw czas na 90% z minimalnego czasu
{ return retVal*(0.9*1000);
public partial class Form1 : Form }
{
public Form1() private void Form1_Load(object sender, EventArgs e)
{ {
InitializeComponent(); //pobierz najkrótszy czas
} int interval = DelayTime();
resetTimer.Interval = interval;
//pobierz ustawienia z rejestru resetTimer.Enabled = true;
int DelayTime() }
{
int retVal = 1000; private void miQuit_Click(object sender, EventArgs
RegistryKey key = Registry.LocalMachine.OpenS e)
ubKey( {
@"\SYSTEM\CurrentControlSet\ this.Close();
Control\Power"); }
object oBatteryTimeout = key.GetValue("BattPow
erOff"); [DllImport("CoreDLL")]
object oACTimeOut = key.GetValue("ExtPowerOf public static extern int SystemIdleTimerReset();
f");
object oScreenPowerOff = key.GetValue("ScreenP // resetuj licznik
owerOff"); private void resetTimer_Tick(object sender,
EventArgs e)
if (oBatteryTimeout is int) {
{ SystemIdleTimerReset();
int v = (int)oBatteryTimeout; }
if(v>0) retVal = Math.Min(retVal,v); }
} }

www.sdjournal.org 59
Programowanie Windows Mobile

Listing 7. Wprowadzenie aplikacji w tryb UNATTENDED Listing 8. Blokada modułu GPS przed wejściem w tryb uśpienia
using System;
using System.Collections.Generic; [Flags()]
using System.ComponentModel; public enum DevicePowerFlags
using System.Data; {
using System.Drawing; None = 0,
using System.Text; POWER_NAME = 0x00000001,
using System.Windows.Forms; POWER_FORCE = 0x00001000,
using System.Runtime.InteropServices; POWER_DUMPDW = 0x00002000
}
namespace UnAttendedMode public enum CEDEVICE_POWER_STATE : int
{ {
public partial class Form1 : Form PwrDeviceUnspecified = -1,
{ D0 = 0,
public Form1() D1 = 1,
{ D2 = 2,
InitializeComponent(); D3 = 3,
} D4 = 4,
PwrDeviceMaximum
[DllImport("CoreDLL")] }
public static extern int PowerPolicyNotify(int
dwMessage, int option); [DllImport("CoreDLL")]
const int PPN_UNATTENDEDMODE = 3; public static extern int ReleasePowerRequirement(IntPtr
hPowerReq);
private void Form1_Load(object sender, EventArgs e)
{ [DllImport("CoreDLL", SetLastError = true)]
public static extern IntPtr SetPowerRequirement
//powiadom system o wprowadzeniu dla aplikacji trybu UNATTENDED (
string pDevice,
PowerPolicyNotify(PPN_UNATTENDEDMODE, -1); CEDEVICE_POWER_STATE DeviceState,
DevicePowerFlags DeviceFlags,
} IntPtr pSystemState,
uint StateFlagsZero
private void Form1_Closing(object sender, );
CancelEventArgs e)
{ IntPtr _gpsPowerRequirements = IntPtr.Zero;
//zwolnij aplikację z trybu UNATTENDED
PowerPolicyNotify(PPN_UNATTENDEDMODE, 0); private void Form1_Load(object sender, EventArgs e)
} {
} _gpsPowerRequirements = SetPowerRequirement("gpd0:",
} CEDEVICE_POWER_STATE.D0, DevicePowerFlags.POWER_NAME,
IntPtr.Zero, 0);
}
private void Form1_Closing(object sender, CancelEventArgs e)
MARIAN WITKOWSKI {
Jest miłośnikiem oraz entuzjastą wykorzystywania technologii mobilnych w co- if (_gpsPowerRequirements != IntPtr.Zero)
dziennym zastosowaniu. Cenne doświadczenie w zakresie usług mobilnych na- {
był podczas współpracy w latach 2001-2006 z firmą One-2-One SA zajmującą ReleasePowerRequirement(_gpsPowerRequirements);
się integracją usług dodanych w telefonii komórkowej. Prowadzi własną firmę }
świadczącą usługi informatyczne w zakresie tworzenia mikroprocesorowych sys- }
temów czasu rzeczywistego, technologii mobilnych oraz układów automatyki.
Kontakt z autorem: mw@it-mobile.net

W sieci
• WM Power Management – http://windowsmobilepro.blogspot.com/2005/08/windows-mobile-pocket-pc-smartphone.html;
• How Do I: Preserve Battery Power When my Application is in the Background? – http://msdn.microsoft.com/pl-pl/netframework/dd135211(en-us).aspx;
• How Do I: Assure that my Application Code Continues Running when the Device is in Suspended Mode? – http://msdn.microsoft.com/pl-pl/
netframework/cc949112(en-us).aspx;
• Power Management Function – http://msdn.microsoft.com/en-us/library/aa909892.aspx.
• Power to the PocketPC – http://blogs.msdn.com/windowsmobile/archive/2006/08/16/702833.aspx

60 SDJ Extra 34 Biblia


Programowanie Symbian OS

Obsługa akcelerometru
na platformie Series60
Wykorzystaj w pełni możliwości telefonu komórkowego!

Akcelerometr, bądź inaczej – sensor ruchu, od stosunkowo niedawna


jest wykorzystywany jako rozszerzenie technologicznych możliwości
nowoczesnych telefonów komórkowych. Niniejszy artykuł pokazuje, jak
oprogramować akcelerometr w aplikacjach Symbian OS, działających na
platformie S60, oraz jak w praktyce wykorzystać potencjał tego urządzenia.
ści z nich dostępna jest mniejsza ilość
Dowiesz się: Powinieneś wiedzieć: funkcji. Ponadto to API nie jest częścią
• W jaki sposób oprogramować akcelerometr • Podstawy programowania aplikacji Symbian platformy S60 i nie jest dostępne na
w aplikacjach Symbian OS na platformę S60; OS w języku C++; urządzeniach innego producenta niż
• Jak wykorzystać akcelerometr w praktyce • Podstawy teorii wzorców projektowych, a w Nokia.
przy tworzeniu własnych aplikacji. szczególności wzorca Obserwator. • S60 Sensor Framework: dostępny na urzą-
dzeniach S60 5th ed, S60 3rd ed FP2 i
Nokia E66
znaczonych dla platformy S60, opiszemy
również, w jaki sposób przy pomocy akcele- W dalszej części artykułu przedstawimy
Poziom trudności rometru można rozszerzyć funkcjonalność sposób odczytu danych z akcelerometru
programów działających na pokładzie tele- przy użyciu pierwszego i drugiego API z
fonów komórkowych. przedstawionej powyżej listy. Testowa apli-
kacja stworzona na potrzeby niniejszego ar-

P
roducenci urządzeń mobilnych raz S60: przegląd dostępnych tykułu działa na urządzeniach z serii S60
po raz zadziwiają nas techniczny- API do obsługi akcelerometru 3rd Edition. Została ona skonstruowana
mi nowinkami stosowanymi w no- Platformy bazujące na systemie operacyj- przy pomocy SDK S60 3rd edition FP 2 roz-
woczesnych telefonach komórkowych. Jed- nym Symbian cieszą się opinią rozwiązań szerzonego o następujące wtyczki:
ną z takich nowinek był niewątpliwie sen- trudnych do oprogramowania. Niestety, w
sor ruchu. Urządzenie to, zwane również odniesieniu do obsługi akcelerometru wię- • Sensor plug-in (http://www.forum.nokia.com/
akcelerometrem bądź przyspieszeniomie- cej w tym stwierdzeniu prawdy niż przesa- info/sw.nokia.com/id/4284ae69-d37a-4319-
rzem, potrafi mierzyć własny ruch. Mecha- dy. Otóż już na samym początku programi- bdf0-d4acdab39700/Sensor_plugin_S60_
nizm ten, znany i stosowany w praktyce już sta pretendujący do napisania aplikacji obsłu- 3rd_ed.exe.html);
od dawna (np. do badania ruchu części ma- gującej sensor ruchu staje przed trudnym wy- • Sensor API Plug-in
szyn, przeciążeń samolotów czy chociażby w borem: które z dostępnych API zastosować? (ht tp: / /w w w.fo r um .noki a .c om /info/
laptopach w celu wykrywania zmian położe- Kłopot w tym, że dostęp do danych z akce- sw.nokia.com/id/8059e8ae-8c22-4684-be-
nia w celu zabezpieczenia dysku twardego), lerometru można na chwilę obecną uzyskać 6b-d40d443d7efc/Sensor_API_Plug_in_
został odkryty na nowo w świecie technolo- przez trzy różne API języka C++ oraz jedno S60_3rd_FP2.html).
gii mobilnych. O zastosowaniach tego urzą- API języka Python. Poniżej kilka słów na te-
dzenia słyszy się najczęściej w odniesieniu mat każdego z nich: Instalacja wtyczek
do platformy Apple iPhone i Apple iPod To- do obsługi akcelerometru
uch. Nie każdy jednak wie, że zanim platfor- • Sensor Plugin: to API zostało udostępnio- Skoro już zdecydowaliśmy, które z dostęp-
my te ujrzały światło dzienne, akcelerometr ne jako pierwsze; współpracuje ono z na- nych wtyczek użyjemy do eksperymentów
był dostępny na urządzeniach z platformy stępującymi urządzeniami: Nokia 5500, z akcelerometrem, warto wspomnieć kil-
S60, działających pod kontrolą systemu ope- Nokia N82, Nokia N93i, Nokia N95 ka słów na temat ich instalacji. Pisząc ni-
racyjnego Symbian. W niniejszym artykule oraz Nokia N95 8GB. niejszy artykuł, założyliśmy, iż czytające
przedstawimy, w jaki sposób można opro- • Sensor API: ma to samo pokrycie urzą- go osoby posiadają przynajmniej podsta-
gramować sensor ruchu w aplikacjach prze- dzeń co Sensor Plugin, jednak na czę- wowe doświadczenia z programowaniem

62 SDJ Extra 34 Biblia


Obsługa akcelerometru

aplikacji dla platformy Symbian S60. Z te- tej pory dla osi X będą wartościami odpo- RArray<TRRSensorInfo>, która – jak ławo
go też względu postanowiliśmy nie opisy- wiadającymi osi Y i na odwrót. Dodatko- się domyśleć – po wywołaniu będzie wy-
wać procesu instalacji samego SDK, a jedy- wo należy pamiętać o tym, iż otrzymywane pełniona obiektami typu TRRSensorInfo,
nie dodatkowych wtyczek. Na samym po- wartości są ze znakiem. zaś te zawierać będą informację o dostęp-
czątku warto zauważyć, iż warto zainstalo- AbyprzeciążonametodaHandleDataEventL() nych sensorach. W naszym przypadku oka-
wać obydwa plug-iny do tego samego SDK; była wywołana, należy dodać obiekt imple- zało się, iż telefon Nokia N95 posiada wię-
dzięki temu budowanie przykładowej apli- mentującej ją klasy jako obserwatora do obiek- cej niż jeden sensor. W aparacie tym są do-
kacji obędzie się bez przełączania aktywne- tu typu CRRSensorApi przy pomocy metody stępne dwa sensory: RotSensor (ang. rotate
go SDK. AddDataListener. sensor) oraz AccSensor (ang. acceleration sen-
Rozszerzenie Sensor Plug-in dostarczane W celu utworzenia instancji klasy sor). W przypadku sensora rotacji dane są
jest w postaci pojedynczej paczki instalacyj- CRRSensorApi należy najpierw pobrać li- przekazywane tylko w polu iSensorData1
nej (Sensor_plugin_S60_3rd_ed.exe). Po jej stę istniejących sensorów za pomocą sta- i oznaczają orientację telefonu (0o, 90o,
uruchomieniu uruchamia się instalator, któ- tycznej metody FindSensorsL z tej sa- 180o i 270o). Nas interesuje oczywiście
ry krok po kroku przeprowadza nas przez ca- mej klasy. Jako parametr podajemy tablicę drugi z dostępnych sensorów, który zapi-
ły proces (patrz: Rysunki 1-5).
W przypadku Sensor API Plug-in instalacja
wygląda niemalże identycznie. Po pomyśl-
nym zainstalowaniu obydwu pakietów mo-
żemy przyjrzeć się dokładniej, co oferują nam
obydwa API.

Sensor Plug-in
W przypadku korzystania z rozszerzenia
Sensor Plug-in, wszystkie klasy niezbęd-
ne do odczytania danych z akcelerometru
znajdują się w pliku nagłówkowym rrsenso-
rapi.h. Pierwszym krokiem do pozyskania
interesujących nas informacji jest zaimple-
mentowanie klasy dziedziczącej z interfej-
su MRRSensorDataListener (patrz: Listing
1) i przeciążeniu czysto wirtualnej meto-
dy HandleDataEventL, definiowanej przez
ten interfejs.
Jako parametry wspomnianej metody
otrzymujemy obiekty typu TRRSensorInfo
oraz TRRSensorEvent. Definicje klas opisu-
jących wspomniane typy przedstawione są
na Listingu 2.
Pierwsza klasa zawiera 3 pola: kategorie Rysunek 1. Instalacja rozszerzenia Sensor Plug-in: krok pierwszy
sensora i ID sensora (oba typu TInt) oraz
nazwę sensora (TBuf<KMaxSensorName>).
W drugiej klasie zdefiniowane są trzy pola
typu TInt (iSensorData1, iSensorData2,
iSensorData3), w których przechowywane
będą odczyty z trzech osi akcelerometru,
odpowiadające odpowiednio osiom Y, X i
Z. Na Rysunku 6 przedstawiono wizuali-
zację wspomnianych osi. Należy tutaj pod-
kreślić, że gdy zmieni się orientacja ekranu
(np. przesuniemy ekran w dół w telefonie
Nokia N95), to wartości przychodzące do

Listing 1. Definicja interfejsu


MRRSensorDataListener

class MRRSensorDataListener
{
public:
virtual void HandleDataEventL(
TRRSensorInfo aSensor,
TRRSensorEvent aEvent )
= 0;
};
Rysunek 2. Instalacja rozszerzenia Sensor Plug-in: krok drugi

www.sdjournal.org 63
Programowanie Symbian OS

suje we wszystkich trzech polach dane na • Sensor Plug-in API; ży o tym pamiętać, gdy chcemy wykorzy-
temat ruchu (w postaci przyśpieszenia) na • Channel Finder API; stać te dane np. do sterowania grą.
osiach X, Y oraz Z. • Sensor Channel API. Rozważmy teraz bardziej szczegóło-
Korzystając z rozszerzenia Sensor Plug-in, wo, w jaki sposób działają wymienione
należy pamiętać o tym, aby do listy dołą- Pierwszy z wymienionych elementów nie API. Zaczniemy od pobrania listy czujni-
czanych bibliotek w pliku definicji projektu jest załączony w ogólnodostępnym SDK, ja- ków. W tym celu należy utworzyć tablicę
(mmp) dodać wpis rrsensorapi.lib. Warto też ko że jest on przeznaczony tylko dla licen- RArray dla przechowywania obiektów typu
zauważyć, że biblioteka ta jest niedostępna cjobiorców platformy S60. Drugie API słu- TSensrvChannelInfo; definicja typu takiej ta-
dla emulatora. ży do pobierania listy dostępnych czujni- blicy zdefiniowana jest w pliku sensrvtypes.h ja-
ków. Przy pomocy ostatniego API możemy ko RSensrvChannelInfoList. Następnie two-
Sensor API Plug-in odczytywać dane z czujnika, opakowane rzymy instancję klasy CSensrvChannelFinder
Zrąb (ang. framework) oferowany w ramach w deskryptor. Warto zauważyć, że Sensor (zdefiniowanej w pliku sensrvchannelfin-
rozszerzenia Sensor API Plug-in składa się z Channel API nie zamienia danych osi X i der.h). Poza tablicą, którą wypełni metoda
trzech elementów: osi Y w przypadku obrócenia ekranu; nale- CSensrvChannelFinder::FindChannelsL ,
należy utworzyć jeszcze dodatkowy obiekt
TSensrvChannelInfo, który posłuży jako
filtr – wszystkie niezerowe (w przypadku
typu TInt) oraz niepuste (w przypadku de-
skryptorów) pola tej klasy posłużą do odfil-
trowania znalezionych czujników. W naszym
przypadku używamy wartości KSensrvChann
elTypeIdAccelerometerXYZAxisData dla po-
la TSensrvChannelInfo::iChannelType. W
ten sposób uzyskujemy gwarancję, iż wszyst-
kie sensory umieszczone na wynikowej liście
będą akcelerometrami oferującymi odczyty
przyspieszeń z trzech osi.
Kiedy wybierzemy właściwy czujnik z
otrzymanej listy, to w celu odczytania przy-
chodzących z niego danych należy utwo-
rzyć obiekt klasy CsensrvChannel (plik
nagłówkowy: sensrvchannel.h). Do meto-
dy CSensrvChannel::NewL przekazuje-
my jeden z wpisów z wcześniej uzyskanej
tablicy. Następnie wywołujemy metodę
CSensrvChannel::OpenChannelL() i przeka-
zujemy obiekt nasłuchujący (tj. dziedziczący
z interfejsu MSensrvDataListener) do meto-
Rysunek 3. Instalacja rozszerzenia Sensor Plug-in: krok trzeci dy CSensrvChannel::StartDataListeningL
(patrz: Listing 3).
Metoda ta przyjmuje 4 parametry: obiekt
MSensrvDataListener, wielkość pożądaną
bufora liczoną w ilościach obiektu danych
sensora, maksymalną wielkość bufora oraz

Listing 2. Definicje klas TRRSensorInfo oraz


TRRSensorEvent

class TRRSensorInfo
{
public:
TInt iSensorCategory;
TInt iSensorId;
TBuf<KMaxSensorName> iSensorName;
};

class TRRSensorEvent
{
public:
TInt iSensorData1;
TInt iSensorData2;
TInt iSensorData3;
};
Rysunek 4. Instalacja rozszerzenia Sensor Plug-in: krok czwarty

64 SDJ Extra 34 Biblia


Obsługa akcelerometru

opóźnienie maksymalne dla sensora po- katnie – nieco przekombinowana. Krót- korzystywanego rozszerzenia (Sensor Plug-
między kolejnymi uaktualnieniami obiek- kie podsumowanie mówi samo za siebie: in bądź Sensor API Plug-in).
tu MSensrvDataListener. W moim przykła- dwa równoległe API dość mocno różnią- Koncepcyjnie schemat korzystania z
dzie wartości, jakich użyłem, to kolejno 1, 1, ce się od siebie, przy czym drugie z nich naszego wrappera jest podobny do opisa-
0; nie jest to dobre rozwiązanie ze względów (Sensor API Plug-in) stanowi mocno roz- nych wyżej rozwiązań: najpierw należy po-
wydajnościowych, ale na demonstrowany budowany zrąb do odczytywania danych brać listę dostępnych czujników, następ-
przykład wystarczające. z sensorów. Z punktu widzenia programi- nie stworzyć czujnik i przekazać do nie-
Po wykonaniu opisanych wyżej czynności, sty, który chciałby po prostu pobierać war- go obiekt nasłuchujący. W kolejnych pod-
dane z akcelerometru będą spływać do dziedzi- tości przyśpieszenia na osiach X, Y i Z, nie punktach opiszemy implementację nasze-
czącego po interfejsie MSensrvDataListener martwiąc się na poziomie kodu aplikacji o go opakowania oraz przykład jego wyko-
obiektu obserwatora poprzez wołanie meto- to, z jakim ma do czynienia urządzeniem, rzystania przedstawiony w kontekście pro-
dy zwrotnej ChannelChangeDetected(). Na- istniejący stan rzeczy wydaje się być moc- stej aplikacji testowej.
główek tej metody przedstawiony jest na Li- no zniechęcający do dalszych eksperymen-
stingu 4. tów. Mając takie same odczucia, postano- Implementacja wrappera
Opis wyłuskania danych o przyśpieszeniu wiliśmy zaproponować Czytelnikom ni- W niniejszym punkcie opiszemy implemen-
z przekazywanego do tej metody obiektu ty- niejszego artykułu rozwiązanie tej mało tację opakowania przykrywającego dwa API
pu TSensrvChannelInfo przedstawiony jest dogodnej sytuacji w postaci wygodnego do obsługi akcelerometru: Sensor Plug-in oraz
w dalszej części artykułu. opakowania (ang. wrapper) na obydwa API. Sensor API Plug-in. Nasz wrapper zaimplemen-
Nasz wrapper skupia się tylko i wyłącznie towany jest w postaci klasy CAccelerometer.
Wrapper na obydwa API na sensorze odczytującym wartości przy- Na początek rozważmy interfejs tejże klasy
Po przeczytaniu ostatnich dwóch punk- śpieszenia na osiach X, Y, Z, oferuje prosty przedstawiony na Listingu 5.
tów niniejszego artykułu można odnieść interfejs, ukrywając wszelkie szczegóły im- Listing ten przedstawia zawartość pli-
wrażenie, iż obsługa koncepcyjnie proste- plementacyjne przed korzystającym z nie- ku nagłówkowego Accelerometer.hpp, któ-
go urządzenia, jakim jest akcelerometr pod go programistą, i, co istotne – na poziomie rego sporą część zajmuje definicja interfej-
system Symbian OS, jest – mówiąc deli- interfejsu jest całkowicie niezależny od wy- su interesującej nas klasy. Patrząc na pierw-
sze linijki deklaracji klasy, można zauwa-
Listing 3. Nagłówek metody CSensrvChannel::StartDataListeningL żyć, że dziedziczy ona z CBase, czyli jej in-
stancje będą tworzone dynamicznie (wy-
virtual void StartDataListeningL( nika to z programistycznych konwencji
MSensrvDataListener* aDataListener, określonych przez Symbiana; zakładamy,
const TInt aDesiredCount, iż Czytelnik niniejszego tekstu zna wspo-
const TInt aMaximumCount, mniane konwencje). W dalszej kolejności
const TInt aBufferingPeriod ) = 0; klasa dziedziczy po interfejsie jednego z
obserwatorów: MRRSensorDataListener
Listing 4. Nagłówek metody MSensrvChannelListener::ChannelChangeDetected bądź MSensrvDataListener. Konkret-
virtual void ChannelChangeDetected( ny interfejs dobierany jest w zależno-
const TSensrvChannelInfo& aDetectedChannel, ści od dostępności definicji symboli
TSensrvChannelChangeType aChangeType ) = 0; preprocesora: ACC_SENSOR_API lub ACC_
S60_SENSOR_FRAMEWORK. Jak się łatwo do-
myśleć, każdy z tych symboli związany
jest z opisanymi wcześniej rozszerzenia-
mi obsługi sensora ruchu. W tym momen-
cie warto zaznajomić się z zawartością pli-
ku AccConfig.hpp (patrz: Listing 6), w któ-
rym – również w zależności od dostępno-
ści wspomnianych symboli – dołączane są

��

��
��

��
��
��

Rysunek 5. Instalacja rozszerzenia Sensor Plug-in: krok piąty Rysunek 6. Wizualizacja osi akcelerometru

www.sdjournal.org 65
Programowanie Symbian OS

specyficzne dla poszczególnych API syste- Listing 5. Interfejs klasy CAccelerometer


mowe pliki nagłówkowe.
W tym momencie jasne staje się to, w ja- #if !defined __INCLUDED_ACCELEROMETER_WRAPPER_HPP__
ki sposób aplikacje mogą wykorzystywać nasz #define __INCLUDED_ACCELEROMETER_WRAPPER_HPP__
wrapper. W tym celu trzeba będzie skorzystać z #include <badesca.h>
publicznego API klasy CAccelerometer i zdefi- #include "AccConfig.hpp"
niować globalnie jeden z opisanych wyżej sym- namespace Acc
boli preprocesora (można to zrobić z poziomu {
pliku mmp). Zmiana tej definicji będzie powo- class MAccelerometerObserver
dować automatyczne przełączanie się pomię- {
dzy obydwoma API do obsługi akcelerome- public:
tru. Po jej wprowadzeniu wystarczy ponownie virtual void OnAccEventL( TInt aAxisX, TInt aAxisY, TInt aAxisZ ) = 0;
skompilować projekt w celu uzyskania paczki };
instalacyjnej wspierającej zadane API. class CAccelerometer :
Wróćmy do interfejsu klasy CAccelerometer. public CBase
Na początku mamy tam dwie publiczne meto- #if defined (ACC_SENSOR_API)
dy statyczne. Pierwsza z nich (CAccelero- , public MRRSensorDataListener
meter::NewL()) jest częścią symbianowe- #elif defined (ACC_S60_SENSOR_FRAMEWORK)
go idiomu dwufazowej konstrukcji obiektów , public MSensrvDataListener
i nie będziemy jej w tym miejscu szczegóło- #endif
wo opisywać. Druga metoda (CAccelerome- {
ter::GetAvailableSensorsL()) jest, z nasze- public:
go punktu widzenia, bardziej interesująca: bę- static CAccelerometer* NewL( const TDesC& aSensor );
dziemy wykorzystywać ją w celu pobierania in- static void GetAvailableSensorsL( CDesCArray* aArray );
formacji o dostępnych sensorach ruchu. Meto- void AddObserverL( MAccelerometerObserver* aObserver );
da ta jako parametr przyjmuje wskaźnik na ta- void RemoveObserver( MAccelerometerObserver* aObserver );
blicę deskryptorów (czyli symbianowych na- ~CAccelerometer();
pisów), która zostanie wypełniona odpowied- private:
nimi informacjami. Warto w tym miejscu za- CAccelerometer();
uważyć, iż w publicznym interfejsie klasy void ConstructL( const TDesC& aSensor );
CAccelerometer nie stosujemy żadnych dedy- RPointerArray<MAccelerometerObserver> iObservers;
kowanych struktur do opisu sensora (np. takich // API specific
jak opisywana wcześniej klasa TRRSensorInfo). #if defined (ACC_SENSOR_API)
Chcąc jak najbardziej uprościć ten interfejs, static void GetSensorsInfoL( RArray<TRRSensorInfo>& aInfoArray );
przechowujemy informację na temat sensora static inline TInt FindSensorIndex( const RArray<TRRSensorInfo>& aInfoArray,
ruchu w postaci zwykłego ciągu znaków. const TDesC& aSensorName );
Kolejne dwie, niestatyczne metody: void HandleDataEventL( TRRSensorInfo aSensor, TRRSensorEvent aEvent );
AddObserverL oraz RemoveObserver, słu- CRRSensorApi* iSensor;
żą do zarządzania obiektami obserwatorów #elif defined(ACC_S60_SENSOR_FRAMEWORK)
stanu akcelerometru. W naszym wrapperze static inline TInt FindSensorIndex( const RSensrvChannelInfoList& aInfoArray,
zastosowaliśmy bardzo minimalistyczny in- const TDesC& aSensorName );
terfejs obserwatora; interfejs ten dostępny w void DataError( CSensrvChannel& aChannel, TSensrvErrorSeverity aError );
postaci klasy MAccelerometerObserver jest void DataReceived( CSensrvChannel& aChannel, TInt aCount, TInt aDataLost );
zdefiniowany na Listingu 5. Kluczowa jest void GetDataListenerInterfaceL( TUid aInterfaceUid, TAny*& aInterface );
przede wszystkim jego metoda zwrotna: CSensrvChannel* iSensor;
#endif
virtual void OnAccEventL( TInt aAxisX, };
TInt aAxisY, TInt };
aAxisZ ) = 0; #include "Accelerometer.inl"
#endif // __INCLUDED_ACCELEROMETER_WRAPPER_HPP__
Przyjmuje ona po prostu trzy wartości typu
TInt reprezentujące przyśpieszenie na po- Listing 6. Zawartość pliku AccConfig.hpp
szczególnych osiach. #if !defined __INCLUDED_ACC_CONFIG_HPP__
Tak wygląda publiczna część interfejsu #define __INCLUDED_ACC_CONFIG_HPP__
klasy CAccelerometer. W celu pełnego zro- #if defined (ACC_SENSOR_API)
zumienia mechanizmów działania nasze- # include <rrsensorapi.h>
go opakowania przyjrzyjmy się teraz prywat- #elif defined (ACC_S60_SENSOR_FRAMEWORK)
nym składowym oraz metodom tej klasy. Pry- # include <sensrvchannelfinder.h>
watny interfejs klasy dzieli się na dwie czę- # include <sensrvchannel.h>
ści: pierwsza z nich jest niezależna od wyko- # include <sensrvaccelerometersensor.h>
rzystywanego API, druga – zmienia się w za- # include <sensrvdatalistener.h>
leżności od tego, które rozszerzenie stosuje- #endif
my. Rozważmy na początek część niezależną. #endif // __INCLUDED_ACC_CONFIG_HPP__
Mamy tutaj prywatny konstruktor oraz me-

66 SDJ Extra 34 Biblia


Obsługa akcelerometru

todę ConstructL(), które stanowią szczegół muje ona jako pierwszy parametr obiekt ty- CAccelerometer::GetAvailableSensorsL()
implementacyjny (są częścią wspomniane- pu const RArray<TRRSensorInfo>& bądź (patrz: Listing 8).
go wcześniej mechanizmu dwufazowej kon- const RSensrvChannelInfoList&. Metoda ta odpowiada za wypełnienie prze-
strukcji obiektów stosowanej przy progra- Główna część implementacji wrappera kazanej tablicy napisów (aArray) nazwami
mowaniu aplikacji C++ pod Symbain OS), i ukryta jest w pliku źródłowym Accelerome- dostępnych sensorów. W przypadku korzy-
nie będziemy ich szczegółowo opisywać. Ko- ter.cpp. Na początek przyjrzymy się definicjom stania z Sensor API (ACC_SENSOR_API), imple-
lejnym i zarazem ostatnim elementem w tej publicznych metod klasy CAccelerometer. mentacja tej metody jest trywialna i polega na
części API jest prywatna składowa: Na pierwszy ogień pójdzie statyczna metoda wywołaniu CRRSensorApi::FindSensorsL()

RPointerArray<MAccelerometerObserver> Listing 7. Zawartość pliku Accelerometer.inl


iObservers;
namespace Acc
Składowa ta reprezentuje dynamiczną listę {
przechowującą wskaźniki do obserwatorów
akcelerometru. namespace
Druga część prywatnego interfejsu kla- {
sy jest zależna od wykorzystywanego API; const TInt KObserversArrayGranularity = 2;
z tego też względu jest ona umieszczona }
w dwóch sekcjach kontrolowanych przez
instrukcje sterujące preprocesora: #if inline CAccelerometer::CAccelerometer()
defined / #elif defined / #endif. W każ- : iObservers( KObserversArrayGranularity )
dej z tych sekcji umieszczone są specyficz- {
ne dla danego API deklaracje metod oraz }
pól potrzebnych do inicjacji akcelerometru
oraz przechwytywania generowanych przez #if defined (ACC_SENSOR_API)
niego zdarzeń.
Podsumowując opis interfejsu klasy re- inline TInt Caccelerometer::FindSensorIndex(
prezentującej sensor ruchu, warto nadmie- const RArray<TRRSensorInfo>& aInfoArray,
nić, iż została ona umieszczona w przestrze- const TDesC& aSensorName )
ni nazw Acc. {
Przejdźmy teraz do opisu implementacji for ( TInt i = 0; i < aInfoArray.Count(); ++i )
klasy CAccelerometer. W pliku Accelerome- {
ter.inl (patrz: Listing 7) umieściliśmy stałe if ( aInfoArray[i].iSensorName == aSensorName )
oraz metody typu inline. {
Warto zwrócić uwagę, że stała KObserve return i;
rsArrayGranularity została ukryta w nie- }
nazwanej przestrzeni nazw, co gwarantu- }
je ograniczenie jej widoczności wyłącznie return KErrNotFound;
w zakresie pliku, w którym ją zdefiniowa- }
no. Oprócz definicji konstruktora plik Ac-
celerometer.inl zawiera jeszcze dwie im- #elif defined (ACC_S60_SENSOR_FRAMEWORK)
plementacje metod CAccelerometer::
FindSensorIndex. Metoda ta odpowiada za inline TInt Caccelerometer::FindSensorIndex(
wyszukanie indeksu sensora w zadanej li- const RSensrvChannelInfoList& aInfoArray,
ście na podstawie jego nazwy. W zależności const TDesC& aSensorName )
od wykorzystywanego rozszerzenia przyj- {
for ( TInt i = 0; i < aInfoArray.Count(); ++i )
{
HBufC* str = HBufC::NewLC( aInfoArray[i].iLocation.Length() );
str->Des().Copy( aInfoArray[i].iLocation );
if ( *str == aSensorName )
{
CleanupStack::PopAndDestroy( str );
return i;
}
CleanupStack::PopAndDestroy( str );
}
return KErrNotFound;
}

#endif

} // namespace Acc
Rysunek 7. Zrzut ekranu z testowej aplikacji Graph

www.sdjournal.org 67
Programowanie Symbian OS

oraz skopiowaniu uzyskanych za jej pośred- stingu 9. Metoda ta jako parametr otrzymuje W ten sposób, zaraz po pomyślnej konstruk-
nictwem danych do obiektu aArray. W przy- nazwę sensora (const TDesC& aSensor), któ- cji, nasz wrapper jest nieustannie notyfiko-
padku korzystania z alternatywnego API ry ma być obsługiwany. Jej zwartość jest w du- wany o zdarzeniach generowanych przez
(ACC_S60_SENSOR_FRAMEWORK), sprawa jest żej mierze tożsama z implementacją opisanej sensor ruchu. W przypadku niepowodzenia,
nieco bardziej skomplikowana. Musimy po wcześniej metody GetAvailableSensorsL, ty- tj. wtedy, gdy żądane urządzenie nie jest do-
kolei: stworzyć obiekt poszukiwacza (ang. le że zamiast zapisywania nazw dostępnych sen- stępne, funkcja konstruująca rzuca symbia-
finder), stworzyć i odpowiednio zainicjować sorów na listę, wyszukiwany jest konkretny ak- nowy wyjątek (User::Leave) z kodem błędu
obiekt filtra (TsensrvChannelInfo), i korzy- celerometr (określony przez nazwę zapisaną KErrNotFound.
stając z tych dwóch, wykonać zapytanie: w aSensor), tworzony jest obiekt go reprezen- W odniesieniu do implementacji publicz-
tujący (CRRSensorApi bądź CSensrvChannel, nego interfejsu klasy CAccelerometer, pozo-
finder->FindChannelsL( channelInfoList, w zależności od stosowanego rozszerzenia), stały nam do rozważania tylko metody obsłu-
searchConditions ); po czym instancja nowo tworzonej klasy gujące obserwatorów oraz destruktor. Imple-
CAccelerometer podłącza się do tego obiektu mentacje tych metod przedstawione są na Li-
Po wykonaniu powyższych kroków możemy jako obserwator. Odpowiadają za to wywołania: stingu 10.
skopiować z channelInfoList nazwy do- Zarządzanie obserwatorami sprowadza
stępnych sensorów. Odpowiada za to dedy- iSensor->AddDataListener( this ); się do dwóch operacji: dodanie (CAcce-
kowana pętla for. lerometer::AddObserverL()) i usunięcie
Kolejny istotny fragment implementacji na- oraz: ( Caccelerometer::RemoveObserver() ).
szego wrappera to kod inicjujący umieszczony Implementacje tych metod są trywial-
w metodzie konstruującej ConstructL(). Za- iSensor->StartDataListeningL( this, 1, 1, ne i polegają przede wszystkim na funk-
wartość tej metody przedstawiona jest na Li- 0 ); cjonalności kontenera RPointerArray<M
AccelerometerObserver>. Kontener ten

Listing 8. Definicja metody CAccelerometer::GetAvailableSensorsL() (zaimplementowany jako szablon kla-


sy) przechowuje w przypadku nasze-
namespace Acc go wrappera wskaźniki do obiektów ty-
{ pu MAccelerometerObserver. W przypad-
void CAccelerometer::GetAvailableSensorsL( CDesCArray* aArray ) ku operacji dodawania, sprawdzamy naj-
{ pierw, czy obiekt obserwatora nie był już
#if defined (ACC_SENSOR_API) wcześniej dodany (warto tu zauważyć, iż
to przeszukiwanie odbywa się po adresie
const TInt KSensorGranularity = 2; wskaźnika obserwatora, a nie na bazie po-
RArray<TRRSensorInfo> array( KSensorArrayGranularity ); równywania zawartości pól obiektu), a je-
CRRSensorApi::FindSensorsL( array ); śli ten warunek nie jest spełniony, to obser-
for ( TInt i = 0; i < array.Count(); ++i ) wator jest dodany do tablicy. W przypadku
{ usuwania, sprawa jest prosta: korzystając z
aArray->AppendL( array[ i ].iSensorName ); metody RPointerArray<MAccelerometerO
} bserver>::Remove(), usuwamy zadanego
array.Close(); obserwatora. W destruktorze klasy nisz-
czymy tablicę obserwatorów (w tym miej-
#elif defined (ACC_S60_SENSOR_FRAMEWORK) scu trzeba podkreślić, iż niszczona jest tyl-
ko tablica, obiekty obserwatorów pozosta-
CSensrvChannelFinder* finder = CSensrvChannelFinder::NewLC();
RSensrvChannelInfoList channelInfoList;
CleanupClosePushL( channelInfoList );

TSensrvChannelInfo searchConditions;
searchConditions.iChannelType = KSensrvChannelTypeIdAccelerometerXYZAxisData;
finder->FindChannelsL( channelInfoList, searchConditions );
for( TInt i = 0; i < channelInfoList.Count(); ++i )
{
HBufC* str = HBufC::NewLC( channelInfoList[i].iLocation.Length() );
str->Des().Copy( channelInfoList[i].iLocation );
aArray->AppendL( *str );
CleanupStack::PopAndDestroy( str );
}
channelInfoList.Close();
CleanupStack::Pop( &channelInfoList );
CleanupStack::PopAndDestroy( finder );

#endif

}
}
Rysunek 8. Zrzut ekranu z gry Labyrinth

68 SDJ Extra 34 Biblia


Obsługa akcelerometru

ją nienaruszone) oraz obiekt reprezentują- wane przez sensor ruchu znajduje się w ciele ło to zadanie trywialne, o tyle w przypadku
cy sensor. metody CAccelerometer::DataReceived(). CAccelerometer::DataReceived() sprawa
Ostatnią część naszego opakowania stano- Idea działania tej metody jest bardzo po- jest nieco bardziej zagmatwana. Trick polega
wi implementacja prywatnych metod, zależ- dobna do jej odpowiednika z Sensor API: na tym, iż interesujące nas informacje są za-
nych od rozszerzenia. Są to funkcje zwrotne wyłuskać dane i przekazać je do obserwato- pakowane w deskryptor, czyli innymi słowy
wymagane przez poszczególne API; za pomo- rów. Jednakże, o ile w przypadku metody w bufor, i musimy je sobie sami wypakować.
cą tychże funkcji wrapper odbiera informacje CAccelerometer::HandleDataEventL() by- Służy do tego struktura typu TPckgBuf<TSe
o zdarzeniach generowanych przez sensor ru-
chu. Implementacja tych metod przedstawio- Listing 9. Definicja metody CAccelerometer::ConstructL()
na jest na Listingu 11.
W przypadku korzystania z rozszerzenia namespace Acc
Sensor Plug-in, sprawa jest stosunkowo prosta, {
jako że mamy do obsłużenia tylko jedną me- void CAccelerometer::ConstructL( const TDesC& aSensor )
todę: CAccelerometer::HandleDataEventL. {
Jednym z parametrów przekazywanych do #if defined (ACC_SENSOR_API)
tej metody jest obiekt opisywanej wcze- RArray<TRRSensorInfo> array( KSensorArrayGranularity );
śniej klasy TRRSensorEvent. W tej sytu- CleanupClosePushL( array );
acji, w ciele metody CAccelerometer:: CRRSensorApi::FindSensorsL( array );
HandleDataEventL wystarczy wyłuskać ze TInt sensorIndex = FindSensorIndex( array, aSensor );
wspomnianego obiektu odpowiednie skła- if ( sensorIndex < 0 )
dowe (iSensorData1, iSensorData2 oraz {
iSensorData3) i przekazać je, gdzie trzeba, CleanupStack::PopAndDestroy();
iterując po kolejnych elementach tablicy z User::Leave( KErrNotFound );
obserwatorami. }
Z kolei kiedy wykorzystujemy alterna-
tywne rozszerzenie, musimy zaimplemen- iSensor = CRRSensorApi::NewL( array[ sensorIndex ] );
tować aż trzy metody zwrotne: iSensor->AddDataListener( this );
CleanupStack::PopAndDestroy();
• CAccelerometer::DataError(),
• CAccelerometer::DataReceived() return;
• oraz CAccelerometer::GetDataListene #elif defined (ACC_S60_SENSOR_FRAMEWORK)
rInterfaceL().

Na szczęście, biorąc pod uwagę założenia CSensrvChannelFinder* finder = CSensrvChannelFinder::NewLC();


co do funkcjonalności naszego opakowania, RSensrvChannelInfoList channelInfoList;
pierwsza i trzecia z wymienionych metod CleanupClosePushL( channelInfoList );
nie wymagają dostarczenia implementacji.
Z tego też względu ich ciała pozostają puste. TSensrvChannelInfo searchConditions;
Główny kod obsługujący zdarzenia genero- searchConditions.iChannelType = KSensrvChannelTypeIdAccelerometerXYZAxisD
ata;
finder->FindChannelsL( channelInfoList, searchConditions );

TInt sensorIndex = FindSensorIndex( channelInfoList, aSensor );


if ( sensorIndex < 0 )
{
channelInfoList.Close();
CleanupStack::Pop( &channelInfoList );
CleanupStack::PopAndDestroy( finder );
User::Leave( KErrNotFound );
}

iSensor = CSensrvChannel::NewL( channelInfoList[ sensorIndex ] );


iSensor->OpenChannelL();
iSensor->StartDataListeningL( this, 1, 1, 0 );

channelInfoList.Close();
CleanupStack::Pop( &channelInfoList );
CleanupStack::PopAndDestroy( finder );

return;
#endif
}
Rysunek 9. Zrzut ekranu z aplikacji iMilk: }
szklanka trzymana prosto

www.sdjournal.org 69
Programowanie Symbian OS

nsrvAccelerometerAxisData>. Po przeka- W dalszej części artykułu pokażemy, jak Na początek zbadajmy jej interfejs
zaniu tej struktury do metody GetData() zastosować zaprezentowane opakowania (patrz: Listing 12).
w obiekcie reprezentującym kanał (CSen- przy tworzeniu aplikacji korzystającej z sen- Pierwsze miejsce, na które należy zwrócić
srvChannel& aChannel) możemy wycią- sora ruchu. uwagę, to początek definicji klasy. Widać tu
gnąć z niej interesujące nas dane: pierwszy ślad zastosowania naszego wrappe-
Wrapper w praktyce ra. Mowa tu oczywiście o publicznym dzie-
TSensrvAccelerometerAxisData data = pckg(); W poprzednim punkcie przedstawiliśmy dziczeniu klasy CGraphView po interfejsie
szczegółowy opis implementacji wrappera Acc::MAccelerometerObserver. Następ-
Warto też zauważyć, że zanim zaczniemy opakowującego dwa API do obsługi sen- stwem tego faktu jest pojawienie się nastę-
przetwarzać zdarzenie, warto sprawdzić, sora ruchu. Teraz, na przykładzie testowej pującej deklaracji w publicznym interfej-
czy mamy do czynienia z interesującym nas aplikacji, pokażemy, jak można wykorzy- sie klasy:
typem kanału. W tym celu ciało metody stać go w praktyce.
opakowane jest następującą instrukcją wa- Kod źródłowy wspomnianej aplikacji void OnAccEventL( TInt aAxisX, TInt
runkową: znajduje się w podkatalogu src/graph, w ar- aAxisY, TInt aAxisZ
chiwum z materiałami dołączonymi do ni- );
if ( aChannel.GetChannelInfo().iChannelT niejszego artykułu. Zakładamy, iż Czytelnik
ype == niniejszego tekstu zna podstawy programo- Za pomocą tej metody zwrotnej, widok
KSensrvChannelTypeIdAccelero wania aplikacji pod Symbian OS przy użyciu aplikacji będzie odbierał informacje od
meterXYZAxisData ) języka C++. Z tego też względu nie będzie- sensora. Informacje te będą opakowywane
{ /* ... */ } my w tym miejscu opisywać całego szkiele- w strukturę TReading, a następnie prze-
tu aplikacji, skupimy się za to na elementach chowywane w tablicy, a na ich podsta-
Na tym kończymy opis implementacji kla- bezpośrednio wykorzystujących nasz wrap- wie rysowany będzie wykres (ang. graph):
sy CAccelerometer. Zainteresowanych per. Aplikacja testowa posiada jeden widok, stąd właśnie się wzięła taka, a nie inna na-
Czytelników zapraszamy do analizy ko- w którym rysowane są odczyty każdej z osi. zwa naszej testowej aplikacji. W prywat-
du źródłowego naszego wrappera, któ- Widok jest odświeżany po każdym odczycie. nej części interfejsu da się zauważyć skła-
ry jest umieszczony na płycie DVD do- Odczyty z każdej osi są skalowane do wyso- dową: Acc::CAccelerometer* iSensor,
łączonej do niniejszego czasopisma. Opi- kości ekranu, w zależności od dotychcza- która reprezentuje akcelerometr. Warto
sane wyżej pliki (AccConfig.hpp, Accelero- sowych ekstremalnych wartości odczytów. też zwrócić uwagę na prywatną metodę
meter.cpp, Accelerometer.hpp oraz Accelero- Za inicjację oraz obsługę sensora ruchu od- CreateSensorL() oraz składowe iArray,
meter.inl) znajdują się w podkatalogu src/ powiada klasa CGraphView, reprezentująca iMax oraz iMin. Przeznaczenie tych skła-
accelerometer. wspomniany widok. dowych opisane będzie w dalszej części
artykułu.
Listing 10. Implementacja metod do obsługi obserwatorów oraz destruktora w klasie CAccelerometer Na Listingu 13 przedstawiona jest zawar-
tość pliku GraphAppView.cpp, z pominię-
namespace Acc ciem definicji metod CGraphView::Draw()
{ oraz CGraphView::DisplayPopupListLC(),
void CAccelerometer::AddObserverL( MAccelerometerObserver* aObserver ) które opisane będą nieco później.
{ W pierwszej kolejności przyjrzymy
if ( KErrNotFound == iObservers.Find( aObserver ) ) się implementacji metody CGraphView:
{
iObservers.AppendL( aObserver );
}
}

void CAccelerometer::RemoveObserver( MAccelerometerObserver* aObserver )


{
TInt pos = iObservers.Find( aObserver );
if ( pos >= 0 )
{
iObservers.Remove( pos );
}
}

CAccelerometer::~CAccelerometer()
{
iObservers.Close();
#if defined (ACC_SENSOR_API)
delete iSensor;
#elif defined (ACC_S60_SENSOR_FRAMEWORK)
delete iSensor;
#endif
}
} Rysunek 10. Zrzut ekranu z aplikacji iMilk:
szklanka po przechyleniu

70 SDJ Extra 34 Biblia


Obsługa akcelerometru

:CreateSensorL(). Metoda ta wywo- na docelowym urządzeniu, potrzeba jesz- zakładającą stosowanie rozszerzenia Sensor
ływana jest z poziomu CGraphView:: cze szczypty konfiguracji. Na szczęście nie Plug-in. Patrząc na ten Listing, należy zwró-
ConstructL(), czyli w drugiej fazie kon- ma tego zbyt dużo. Wszystko, co trzeba zro- cić uwagę na definicję makra ACC_SENSOR_
struowania obiektu reprezentującego wi- bić, to zdefiniować jedno z makr wspiera- API (ostatnia linia pliku) oraz fragment:
dok. Jej zadaniem jest stworzenie i kon- nych przez nasz wrapper, oraz dopisać w pli-
figuracja obiektu reprezentującego sensor ku definicji projektu (mmp) odpowiednie bi- #if defined GCCE
ruchu. W tym celu tworzona jest na po- blioteki. Na Listingu 16 przedstawiony jest LIBRARY rrsensorapi.lib
czątku dynamiczna tablica do przecho- taki plik zawierający konfigurację projektu #endif
wywania nazw dostępnych sensorów. Ta-
blica ta wypełniana jest danymi w trakcie Listing 11. Implementacja prywatnych metod klasy CAccelerometer, zależnych od rozszerzenia
wywołania:
namespace App
Acc::CAccelerometer::GetAvailableSensorsL( {
array ); #if defined(ACC_SENSOR_API)

W kolejnym kroku użytkownik jest proszo- void CAccelerometer::HandleDataEventL(


ny o wybór sensora z dostępnej listy (służy TRRSensorInfo aSensor,
po temu funkcja DisplayPopupListLC(), TRRSensorEvent aEvent )
która opisana będzie kilka akapitów da- {
lej). Po wybraniu docelowego sensora, wi- TInt xAxis = aEvent.iSensorData2;
dok tworzy obiekt wrappera i podłącza się TInt yAxis = aEvent.iSensorData1;
do niego jako obserwator. Od tego momen- TInt zAxis = aEvent.iSensorData3;
tu metoda CGraphView::OnAccEventL()
zaczyna odbierać zdarzenia generowane for ( TInt i = 0; i < iObservers.Count(); ++i )
przez wybrany akcelerometr. W przypad- {
ku naszego widoku, informacje napływają- iObservers[i]->OnAccEventL( xAxis, yAxis, zAxis );
ce z sensora są pakowane w strukturę i do- }
łączane do listy iArray. }
Oprócz tego przy okazji każdego wystą-
pienia takiego zdarzenia zapamiętywane są #elif defined (ACC_S60_SENSOR_FRAMEWORK)
maksymalne wielkości wychyleń na wszyst-
kich osiach; będą one wykorzystywane do void CAccelerometer::DataError(
skalowania wykresu rysowanego w funk- CSensrvChannel& aChannel,
cji CGraphView::Draw(). Ostatnia linia w TSensrvErrorSeverity aError )
tej funkcji zawiera wywołanie DrawNow();, {
które wymusza odrysowanie powierzchni }
widoku.
Wróćmy jeszcze na moment do funkcji void CAccelerometer::DataReceived(
CGraphView::DisplayPopupListLC(). De- CSensrvChannel& aChannel,
finicja tej funkcji przedstawiona jest na Li- TInt aCount, TInt aDataLost )
stingu 14. {
Funkcja ta buduje proste menu kontek- if ( aChannel.GetChannelInfo().iChannelType ==
stowe zawierające nazwy poszczególnych KSensrvChannelTypeIdAccelerometerXYZAxisData )
sensorów. Dzięki temu użytkownik może {
wybrać interesujące go urządzenie. Funk- TPckgBuf<TSensrvAccelerometerAxisData> pckg;
cja zwraca indeks tablicy wskazujący wy- aChannel.GetData( pckg );
brany sensor. TSensrvAccelerometerAxisData data = pckg();
Sercem klasy reprezentującej nasz widok for ( TInt i = 0; i < iObservers.Count(); ++i )
jest metoda CGraphView::Draw(). To wła- {
śnie w tym miejscu wizualizowane są dane iObservers[i]->OnAccEventL( data.iAxisX, data.iAxisY, data.iAxisZ
odczytywane z akcelerometru. Kod źródło- );
wy przedstawiający definicję tej metody za- }
prezentowany jest na Listingu 15. }
Kod ten odpowiada za rysowanie trzech }
wykresów obrazujących zmiany wychyleń
sensora ruchu na trzech osiach: X, Y i Z. void CAccelerometer::GetDataListenerInterfaceL( TUid aInterfaceUid, TAny*&
Przykładowy zrzut ekranu przedstawiający aInterface )
działanie naszej testowej aplikacji można za- {
obserwować na Rysunku 7. }

Konfiguracja projektu #endif


Implementacja aplikacji to jeszcze nie
wszystko. Aby poprawnie zbudować i uru- }
chomić zaprezentowany powyżej program

www.sdjournal.org 71
Programowanie Symbian OS

Występowanie instrukcji warunkowej #if dwie konfiguracje projektów dostępne są Wąż wkracza do akcji!
defined w powyższym fragmencie wiąże w paczce z kodem źródłowym umieszczo- Faktem niezbitym jest, iż do programowa-
się z faktem, iż biblioteki do obsługi sen- nej na płycie DVD dołączonej do niniejsze- nia poważnych aplikacji pod Symbian OS
sora ruchu są dostępne jedynie w wersji na go pisma. stosuje się język C++. Jednakże pisanie ta-
urządzenie (stąd symbol GCCE w ciele wa- Tych Czytelników, którzy mają dostęp do kowych aplikacji, szczególnie dla osób po-
runku). W przypadku korzystania z dru- telefonu działającego pod kontrolą systemu stronnych, nie jest ani łatwym, ani przy-
giego rozszerzenia, plik mmp wygląda nie- Symbian S60 3rd Edition (lub nowszego), jemnym zadaniem. Na szczęście dla tych,
malże identycznie, z tą różnicą, że w ostat- serdecznie zapraszamy do zbudowania i wy- którzy chcieliby po prostu trochę poeks-
niej linii definiujemy makro ACC _ S60 _ próbowania naszej testowej aplikacji oraz do perymentować ze swoim telefonem, ist-
SENSOR _ FRAMEWORK oraz dołączamy biblio- dalszych eksperymentów z akcelerometrem, nieją mniej potężne, ale za to o wiele prost-
teki SensrvClient.lib i sensrvutil.lib zamiast bazujących na wykorzystaniu przedstawione- sze alternatywy. Jedną z takich alternatyw
rrsensorapi.lib. Dla zainteresowanych oby- go wyżej opakowania. jest możliwość uruchamiania na platfor-
mie S60 skryptów pisanych w języku Py-
Listing 12. Zawartość pliku nagłówkowego GraphAppView.hpp, zawierającego interfejs klasy thon. Wprowadzenie do tego ciekawego te-
GraphView matu było niedawno prezentowane na ła-
mach SDJ (numer 2/2009) w artykule pt.
#ifndef __INCLUDED_GRAPH_VIEW_HPP__ Wąż w komórce autorstwa Mariana Witkow-
#define __INCLUDED_GRAPH_VIEW_HPP__ skiego. Dla nas temat ten wydał się intere-
sujący z tego względu, iż z poziomu skryp-
#include <coecntrl.h> tu Pythona możemy uzyskać dostęp do da-
#include <e32base.h> nych generowanych przez sensor ruchu.
#include "Accelerometer.hpp" Co więcej, da się to zrobić o wiele prościej
i szybciej niż w przypadku programu pisa-
class CGraphView : nego w C++. Innymi słowy, jeśli chciałbyś
public CCoeControl, drogi Czytelniku poeksperymentować z ak-
public Acc::MAccelerometerObserver celerometrem bądź stworzyć na szybko ja-
{ kiś mały prototyp aplikacji, a niekoniecz-
public: nie masz ochotę zagłębiać się w szczegóły
static CGraphView* NewL( const TRect& aRect ); programowania aplikacji symbianowych w
static CGraphView* NewLC( const TRect& aRect ); C++, to zapraszamy do dalszej lektury te-
go podpunktu.
virtual ~CGraphView(); Jeśli chodzi o proces instalacji i konfigu-
void Draw( const TRect& aRect ) const; racji środowiska deweloperskiego Python for
virtual void SizeChanged(); S60, to w Internecie można znaleźć cały sze-
reg materiałów na ten temat (patrz: ramka
void OnAccEventL( TInt aAxisX, TInt aAxisY, TInt aAxisZ ); W sieci). My polecamy przeczytanie wspo-
mnianego wyżej artykułu autorstwa pana
private: Witkowskiego; artykuł ten można pobrać
struct TReading w postaci elektronicznej za darmo ze stro-
{ ny SDJ (wymagane jest jedynie podanie ad-
inline TReading( TInt aX, TInt aY, TInt aZ ) resu e-mail).
{ Na Listingu 17 przedstawiona jest imple-
iX = aX; mentacja prostej klasy napisanej w języku Py-
iY = aY; thon gromadzącej dane odczytywane z senso-
iZ = aZ; ra ruchu.
} Schemat działania tej klasy jest bardzo
TInt iX; podobny do idei zastosowanej w opisanym
TInt iY; wcześniej wrapperze. Przy tworzeniu obiek-
TInt iZ; tu klasy Accelerometer, w konstruktorze (_
}; _init__) pobierany jest obiekt reprezentują-
cy sensor ruchu, po czym tworzony obiekt
void ConstructL(const TRect& aRect); (self) rejestruje się jako obserwator tego
void CreateSensorL(); sensora. Metoda zwrotna (on_acc_event)
TInt DisplayPopupListLC( MDesCArray* aArray ); odbiera stan sensora i wyłuskuje z niego
CGraphView(); wartości składowych data_1, data_2 oraz
data_3, reprezentujące wychylenia urządze-
CArrayFix<TReading>* iArray; nia na poszczególnych osiach. Aby uzyskać
TReading iMax; dostęp do sensora ruchu w skrypcie języka
TReading iMin; Python, należy zaimportować moduł sensor
Acc::CAccelerometer* iSensor; (import sensor). Instancję przedstawionej
}; wyżej klasy możemy stworzyć przy pomo-
cy instrukcji:
#endif // !defined __INCLUDED_GRAPH_VIEW_HPP__
acc = Accelerometer()

72 SDJ Extra 34 Biblia


Obsługa akcelerometru

,zaś prosty mechanizm odczytywania da- przedstawiony jest na wspomnianym Li- data, w której komórkach przechowywa-
nych za jej pomocą przedstawiony jest na Li- stingu. Przed odczytem danych wewnątrz ne są wartości przyśpieszeń na poszczegól-
stingu 18. pętli musimy sprawdzić, czy są one do- nych osiach.
Patrząc na zawartość tego Listingu, trze- stępne (stąd biorą się właśnie dwie in- Przykłady zaprezentowane w tej części
ba nadmienić, iż programy pisane na plat- strukcje warunkowe poprzedzające od- artykułu opracowane zostały na podsta-
formę S60 w Pythonie bazują na tzw. pę- czyt danych), po czym możemy odwo- wie skryptu accelball.py autorstwa Chri-
tli aplikacji. Fragment takiej właśnie pętli łać się bezpośrednio do składowej tablicy stophera Schmidta. Skrypt ten jest ogólno-

Listing 13. Zawartość pliku GraphAppView.cpp

#include <coemain.h> CDesCArrayFlat* array = new (ELeave) CDesCArrayFlat(


#include <eikenv.h> KSensorGranularity );
#include <aknpopup.h> CleanupStack::PushL( array );
#include <aknlists.h> Acc::CAccelerometer::GetAvailableSensorsL( array );
if ( array->Count() == 0 )
#include "GraphAppView.hpp" {
User::Leave( KErrNotFound );
namespace }
{ TInt sensorIndex( KErrNotFound );
const TInt KMargin = 6; TInt tries = 0;
} while ( sensorIndex == KErrNotFound && tries++ < 3 )
{
CGraphView* CGraphView::NewL( const TRect& aRect ) sensorIndex = DisplayPopupListLC( array );
{ }
CGraphView* self = CGraphView::NewLC( aRect ); iSensor = Acc::CAccelerometer::NewL( (*array)[
CleanupStack::Pop( self ); sensorIndex ] );
return self; iSensor->AddObserverL( this );
} CleanupStack::PopAndDestroy( array );
}
CGraphView* CGraphView::NewLC( const TRect& aRect )
{ CGraphView::~CGraphView()
CGraphView* self = new ( ELeave ) CGraphView; {
CleanupStack::PushL( self ); delete iSensor;
self->ConstructL( aRect ); iSensor = NULL;
return self; delete iArray;
} iArray = NULL;
}
void CGraphView::ConstructL( const TRect& aRect )
{ void CGraphView::SizeChanged()
iArray = new (ELeave) CArrayFixFlat<TReading>( {
aRect.Width() + 2 ); DrawNow();
}
CreateSensorL();
void CGraphView::OnAccEventL( TInt aAxisX, TInt aAxisY, TInt
// Create a window for this application view aAxisZ )
CreateWindowL(); {
while( iArray->Count() > Rect().Width() )
// Set the windows size {
SetRect( aRect ); iArray->Delete( 0 );
}
// Activate the window, which makes it ready to be drawn iArray->AppendL( TReading( aAxisX, aAxisY, aAxisZ ) );
ActivateL();
} iMax.iX = (iMax.iX>aAxisX)?iMax.iX:aAxisX;
iMax.iY = (iMax.iY>aAxisY)?iMax.iY:aAxisY;
CGraphView::CGraphView() iMax.iZ = (iMax.iZ>aAxisZ)?iMax.iZ:aAxisZ;
: iMax( KMinTInt, KMinTInt, KMinTInt ), iMin.iX = (iMin.iX<aAxisX)?iMin.iX:aAxisX;
iMin( KMaxTInt, KMaxTInt, KMaxTInt ) iMin.iY = (iMin.iY<aAxisY)?iMin.iY:aAxisY;
{ iMin.iZ = (iMin.iZ<aAxisZ)?iMin.iZ:aAxisZ;
}
DrawNow();
void CGraphView::CreateSensorL() }
{
const TInt KSensorGranularity = 2;

www.sdjournal.org 73
Programowanie Symbian OS

dostępny w Internecie (patrz: ramka W sie- W tym punkcie postaramy się, bazując na wuje się tutaj idealnie jako nowy rodzaj kon-
ci) i stanowi pełny przykład aplikacji S60 analizie istniejących przypadków, zaprezen- trolera. Rozważmy konkretny przypadek
wykorzystującej sensor ruchu za pośred- tować Czytelnikom garść pomysłów odno- użycia w postaci gry Labyrinth (patrz: ram-
nictwem języka Python. Zainteresowa- śnie wykorzystania tego urządzenia w prak- ka W sieci). Któż z nas w młodości nie spę-
nych Czytelników odsyłamy na stronę au- tyce. Liczymy, iż pomysły te będą dla Czy- dził kilku godzin nad małym pudełeczkiem
tora skryptu w celu jego pobrania i szcze- telników źródłem natchnienia przy projek- z przezroczystą ścianką, metalowymi kulka-
gółowej analizy. towaniu własnych aplikacji wykorzystują- mi oraz dziurkami, do których owe kulki na-
cych sensor ruchu. Aby nieco rozluźnić at- leżało doprowadzić? Labyrinth pozwala cie-
Programowanie mosferę, zacznijmy od omówienia, jakie są szyć się podobną zabawą przy wykorzysta-
to nie wszystko,... potencjalne: niu telefonu komórkowego (gra jest aktual-
...można by rzec. Liczy się również, a może nie dostępna na platformy iPhone/iPod To-
przede wszystkim, koncepcja! Zakładamy, Zastosowania akcelerometru uch oraz Android). Clue rozgrywki polega
iż Czytelnicy niniejszego tekstu, po prze- w aplikacjach rozrywkowych na tym, iż telefon traktujemy dokładnie jak
czytaniu powyższych akapitów, są solid- Gry komputerowe lubi (prawie) każdy. O ile opisane wyżej pudełeczko. Na ekranie wy-
nie przygotowani do realizacji zadań pro- dorosłym osobnikom grać w domu czasami świetlacza przedstawiona jest kulka poru-
gramistycznych z zakresu obsługi akcele- nie wypada (lub zwyczajnie brakuje im na to szająca się po tytułowym labiryncie, zaś za-
rometru w aplikacjach Symbian OS. Acz- czasu), o tyle – grając w autobusie czy tram- daniem gracza jest przeprowadzanie jej do
kolwiek sama umiejętność oprogramo- waju, w poczekalni u lekarza czy w prze- określonej dziurki. Sterowanie w grze odby-
wania danej technologii czasami nie wy- rwie długiego i nudnego zebrania – jak naj- wa się właśnie za pomocą akcelerometru: de-
starcza. Akcelerometr to stosunkowo no- bardziej. A jeżeli przy okazji można się po- likatne wychylenia urządzenia na boki wpra-
we rozwiązanie – przynajmniej w kontek- chwalić kolegom z pracy swoim nowym, ga- wiają kulkę w ruch. Zastosowanie prostych
ście rynku aplikacji mobilnych, dlatego też dżeciarskim telefonem komórkowym, to tym zasad fizyki ciała stałego pozwala uzyskać
bardzo ważne jest koncepcyjne zrozumie- bardziej. Dlatego właśnie tzw. gry okazjo- wrażenie, iż obcujemy z prawdziwą kulką
nie tego, w jaki sposób można wykorzystać nalne (ang. casual games) przeznaczone do zamkniętą w pudełku... Na Rysunku 8 moż-
jego potencjał w celu uatrakcyjnienia pro- uruchamiania na urządzeniach mobilnych na zobaczyć zrzut ekranu z gry.
gramów uruchamianych na aparacie ko- są tak bardzo popularne we współczesnym, Co ciekawe, ta stosunkowo prosta apli-
mórkowym. zagonionym świecie. Akcelerometr wpaso- kacja została pobrana ze sklepu Apple już
prawie 10 milionów razy! Podobne przy-
Listing 14. Definicja metody CGraphView::DisplayPopupListLC() kłady wykorzystania akcelerometru moż-
na mnożyć. Istnieje również cały szereg
TInt CGraphView::DisplayPopupListLC( MDesCArray* aArray ) klasycznych gier (np. wyścigów czy sho-
{ ot'em-up'ów), w których akcelerometr jest
CAknSinglePopupMenuStyleListBox* list = new( ELeave ) wykorzystywany jako alternatywny kon-
CAknSinglePopupMenuStyleListBox; troler (dla przykładu: wychylenie urzą-
CleanupStack::PushL( list ); dzenia w lewo bądź w prawo powoduje
skręcanie/przemieszczanie obiektu, któ-
CAknPopupList* popup = CaknPopupList::NewL( rym steruje gracz). Mówiąc krótko: liczba
list, R_AVKON_SOFTKEYS_OK_BACK, zastosowań akcelerometru w dziedzinie
AknPopupLayouts::EMenuWindow); rozrywki jest w zasadzie nieskończona (je-
CleanupStack::PushL( popup ); dynym ograniczeniem jest wyobraźnia au-
popup->SetTitleL( _L("Select accelerometer") ); tora aplikacji).
W tym miejscu należy jednak zdać so-
list->ConstructL( popup, CEikListBox::ELeftDownInViewRect ); bie sprawę z pewnej kwestii. Otóż gdyby
list->CreateScrollBarFrameL( ETrue ); przyszła Ci Czytelniku ochota, aby w ra-
list->ScrollBarFrame()->SetScrollBarVisibilityL( mach eksperymentu popróbować imple-
CEikScrollBarFrame::EOff, mentacji swojej własnej wersji labiryntu,
CEikScrollBarFrame::EAuto); to pamiętaj o tym, że większość tego ty-
pu gier bazuje na symulacjach fizycznych.
CTextListBoxModel* model=list->Model(); Gdybyś spróbował zmodyfikować naszą
model->SetItemTextArray( aArray ); testową aplikację (do czego gorąco nama-
model->SetOwnershipType( ELbmDoesNotOwnItemArray ); wiamy) w taki sposób, aby zamiast wy-
kresu na ekranie rysowała się kulka prze-
TInt ret = -1; mieszczająca się bezpośrednio na podsta-
wie przyrostów pobieranych bezpośrednio
if ( popup->ExecuteLD() ) z urządzania, to wynikowy efekt wyglądał-
{ by nieco kulawo. Kulka ruszałaby się dość
ret = list->CurrentItemIndex(); niezdarnie, skokowo.
} Aby zaimplementować funkcjonal-
ność podobną do tej, którą oferuje gra La-
CleanupStack::Pop( popup ); birynth, należy zbudować silnik fizycz-
CleanupStack::PopAndDestroy( list ); ny, który bierze pod uwagę takie aspek-
return ret; ty jak przyśpieszenie, tarcie itd. Przyro-
} sty odczytywane z akcelerometru (zazwy-
czaj dodatkowo filtrowane bądź uśrednia-

74 SDJ Extra 34 Biblia


Obsługa akcelerometru

Listing 15. Definicja metody CGraphView::DisplayPopupListLC()

void CGraphView::Draw( const TRect& /*aRect*/ ) const st = TPoint( i, pos );


{ }
// Get the standard graphics context gc.SetBrushColor( KRgbGreen );
CWindowGc& gc = SystemGc(); gc.SetPenColor( KRgbGreen );
// Gets the control's extent
TRect drawRect( Rect()); st = TPoint( 0, h / 2 );
// Clears the screen
gc.Clear( drawRect ); for ( TInt i = 0; i < iArray->Count(); ++i )
const TInt h = drawRect.Height(); {
const TInt rangeX = TInt pos = ( ( iArray->At( i ).iY - iMin.iY )
((iMax.iX - iMin.iX)==0)?1:(iMax.iX - iMin.iX); * ( Rect().Height() - KMargin ) ) / rangeY;
const TInt rangeY = pos = Rect().Height() - pos - ( KMargin / 2 );
((iMax.iY - iMin.iY)==0)?1:(iMax.iY - iMin.iY);
const TInt rangeZ = gc.DrawLine( st, TPoint( i, pos ) );
((iMax.iZ - iMin.iZ)==0)?1:(iMax.iZ - iMin.iZ); st = TPoint( i, pos );
gc.SetBrushColor( KRgbBlack ); }
gc.SetPenColor( KRgbBlack ); gc.SetBrushColor( KRgbBlue );
gc.DrawLine( TPoint( 0, h/2 ), TPoint( drawRect.Width(), gc.SetPenColor( KRgbBlue );
h/2 ) ); st = TPoint( 0, h / 2 );
gc.SetBrushColor( KRgbRed ); for ( TInt i = 0; i < iArray->Count(); ++i )
gc.SetPenColor( KRgbRed ); {
TPoint st( 0, h / 2 ); TInt pos = ( ( iArray->At( i ).iZ - iMin.iZ )
for ( TInt i = 0; i < iArray->Count(); ++i ) * ( Rect().Height() - KMargin ) ) / rangeZ;
{ pos = Rect().Height() - pos - ( KMargin / 2 );
TInt pos = ( ( iArray->At( i ).iX - iMin.iX ) gc.DrawLine( st, TPoint( i, pos ) );
* ( Rect().Height() - KMargin ) ) / rangeX; st = TPoint( i, pos );
pos = Rect().Height() - pos - ( KMargin / 2 ); }
gc.DrawLine( st, TPoint( i, pos ) ); }

Listing 16. Zawartość pliku build\graph_sensor_api\Graph.mmp

TARGET Graph.exe END //RESOURCE


TARGETTYPE exe
UID 0x100039CE 0xA0005A7D USERINCLUDE ..\..\src\graph
USERINCLUDE ..\..\src\accelerometer
SECUREID 0xA0005A7D
EPOCSTACKSIZE 0x5000 SYSTEMINCLUDE \epoc32\include

SOURCEPATH ..\..\src\graph LIBRARY euser.lib


SOURCE main.cpp LIBRARY apparc.lib
SOURCE GraphApplication.cpp LIBRARY cone.lib
SOURCE GraphAppView.cpp LIBRARY eikcore.lib
SOURCE GraphAppUi.cpp LIBRARY avkon.lib
SOURCE GraphDocument.cpp LIBRARY commonengine.lib
LIBRARY efsrv.lib
SOURCEPATH ..\..\src\accelerometer LIBRARY estor.lib
SOURCE Accelerometer.cpp LIBRARY eikcoctl.lib
LIBRARY eikctl.lib
SOURCEPATH . LIBRARY bafl.lib

START RESOURCE Graph.rss #if defined GCCE


HEADER LIBRARY rrsensorapi.lib
TARGETPATH resource\apps #endif
END //RESOURCE
LANG SC
START RESOURCE Graph_reg.rss
#ifdef WINSCW VENDORID 0
TARGETPATH \private\10003a3f\apps CAPABILITY NONE
#else
TARGETPATH \private\10003a3f\import\apps MACRO ACC_SENSOR_API
#endif

www.sdjournal.org 75
Programowanie Symbian OS

ne) traktowane są z punktu widzenia ta- tzw. programy-gadżety. Są to zazwyczaj drob- sługi tego urządzenia dostępnych z pozio-
kiego silnika jako wektory siły przykłada- ne aplikacje, które nie wnoszą specjalnej war- mu języka C++. W ramach rozwinięcia te-
ne do kulki. tości użytkowej; służą raczej jako zabawki, matu przedstawiliśmy implementację wrap-
którymi można pochwalić się przed znajo- pera opakowującego wspomniane interfejsy
Zastosowania mymi. Znakomitym przykładem takiego pro- programistyczne. Pokazaliśmy również przy-
w aplikacjach użytkowych gramu jest iMilk, czyli aplikacja symulująca... kład użycia wspomnianego wrappera bazują-
W przypadku aplikacji użytkowych akcelero- szklankę mleka. Dwa przykładowe zrzuty cy na prostej, testowej aplikacji. Jako interesu-
metr również znalazł cały szereg zastosowań. ekranu z tej aplikacji znajdują się odpowied- jącą alternatywę dla dość skomplikowanych
Jako jeden z najprostszych przypadków uży- nio na Rysunkach 9 i 10. API dostępnych z poziomu C++, przedstawi-
cia można podać aplikację ShutUp, co na ję- Poruszanie urządzeniem (jak szklanką) liśmy możliwość oprogramowania akcelero-
zyk polski można przetłumaczyć jako Za- powoduje wylewanie się wirtualnego napoju. metru w telefonach działających pod kontro-
mknijSię. Ta działająca w tle aplikacja pozwa- Aplikacje-gadżety są nie lada gratką dla agen- lą Symbian OS z poziomu skryptu języka Py-
la uciszyć telefon (np. alarm ustawiony w bu- cji reklamowych, stosuje się je często jako ma- thon. W ramach podsumowania tematu opi-
dziku) poprzez... fizyczne przewrócenie te- teriały marketingowe (przykładem może być saliśmy szereg istniejących aplikacji wykorzy-
lefonu do góry nogami. Co ciekawe, razem aplikacja podobna do iMilk reklamująca mar- stujących sensor ruchu.
z wprowadzeniem do telefonów mobilnych kę popularnego piwa). Mamy głęboką nadzieję, iż niniejszy arty-
akcelerometru pojawiły się całkiem nowe ka- kuł zainteresuje Czytelników tematem akce-
tegorie aplikacji użytkowych. Jedną z takich Podsumowanie lerometru i pomoże im wykorzystać to in-
kategorii są programy wspierające utrzymy- W powyższym artykule przedstawiliśmy te- nowacyjne rozwiązanie we własnych apli-
wanie dobrej kondycji, np. liczniki kroków mat obsługi akcelerometru przy tworzeniu kacjach.
bądź kalorii spalanych podczas spaceru. Inna, aplikacji Symbian OS dla platformy S60.
nowa rodzina aplikacji, korzystająca dość in- Główny nacisk został położony na wykorzy- WOJCIECH GASEK
tensywnie z dobrodziejstw akcelerometru, to stanie dwóch najważniejszych API do ob- Pracuje na stanowisku Starszego Programisty
Gier Natywnych w firmie Gamelion, wchodzą-
Listing 17. Wykorzystanie Python for S60: definicja klasy Accelerometer cej w skład Grupy BLStream. Wojciech specjali-
zuje się w technologiach związanych z produk-
class Accelerometer(object): cją oprogramowania na platformy mobilne, ze
data = [] szczególnym naciskiem na tworzenie gier. Gru-
def __init__(self): pa BLStream powstała, by efektywniej wykorzy-
acc = sensor.sensors()['AccSensor'] stywać potencjał dwóch szybko rozwijających się
self.s = sensor.Sensor(acc['id'], acc['category']) producentów oprogramowania – BLStream i Ga-
self.s.connect(self.on_acc_event) melion. Firmy wchodzące w skład grupy specja-
lizują się w wytwarzaniu oprogramowania dla
def on_acc_event(self, state): klientów korporacyjnych, w rozwiązaniach mo-
self.data = [] bilnych oraz produkcji i testowaniu gier.
for key in ['data_1', 'data_2', 'data_3']: Kontakt z autorem:
val = state[key] wojciech.gasek@game-lion.com
self.data.append(val)

def cleanup(self): RAFAŁ KOCISZ


self.s.disconnect() Pracuje na stanowisku Dyrektora Techniczne-
go w firmie Gamelion, wchodzącej w skład Gru-
Listing 18. Wykorzystanie Python for S60: odczytywanie danych z obiektu reprezentującego py BLStream. Rafał specjalizuje się w technolo-
akcelerometer
giach związanych z produkcją oprogramowa-
while running: nia na platformy mobilne, ze szczególnym na-
# ... ciskiem na tworzenie gier. Grupa BLStream po-
if not acc: continue wstała, by efektywniej wykorzystywać potencjał
if not len(sense_conn.delta): continue dwóch szybko rozwijających się producentów
x = acc.data[0] oprogramowania – BLStream i Gamelion. Firmy
y = acc.data[1] wchodzące w skład grupy specjalizują się w wy-
z = acc.data[2] twarzaniu oprogramowania dla klientów korpo-
# ... racyjnych, w rozwiązaniach mobilnych oraz pro-
acc.cleanup() dukcji i testowaniu gier.
Kontakt z autorem: rafal.kocisz@game-lion.com

W Sieci
• http://www.forum.nokia.com/ – strona domowa portalu Forum Nokia; tutaj można pobrać Symbian S60 SDK;
• http://wiki.forum.nokia.com/index.php/S60_Sensor_Framework – krótki opis oraz przykład użycia S60 Sensor Framework;
• http://wiki.forum.nokia.com/index.php/Sensor_API – opis Sensor API;
• http://www.forum.nokia.com/Tools_Docs_and_Code/Tools/Runtimes/Python_for_S60/ – tutaj można pobrać Python for S60 SDK;
• http://crschmidt.net/ – strona domowa Christophera Schmidta; stąd można pobrać skrypt Python wykorzystujący akcelerometr;
• http://crschmidt.net/symbian/accelball.py – bezpośredni odnośnik do skryptu accelball.py;
• http://labyrinth.codify.se/ – strona domowa gry Labyrinth.

76 SDJ Extra 34 Biblia


Programowanie Symbian OS

SDL na Symbianie
Cztery porty

W jaki sposób można wykorzystać jedną z najlepszych i najszerzej


wykorzystywanych bibliotek ułatwiających tworzenie gier? Czy jest
dostępna jej implementacja na Symbiana? Na co należy zwrócić
szczególną uwagę podczas jej użytkowania? Odpowiedzi na wszystkie
te pytania (i nie tylko) znajdują się poniżej.
em biblioteka Allegro dopiero raczkowała
Dowiesz się: Powinieneś wiedzieć: z obsługą innych systemów operacyjnych,
• Niezbędnego minimum na temat programo- • Co nieco na temat telefonów z Symbianem a gdy dorobiła się już jako-tako działającej
wania z wykorzystaniem biblioteki SDL; na pokładzie; wersji, nie zdobyła zbyt wielkiej popularno-
• Jak przedstawia się sytuacja z używaniem • Jak się programuje w C/C++. ści. Co-to-nie-potrafiąca biblioteka PLIB zo-
SDL-a na Symbianie; stała wykorzystana w kilku ambitnie zapo-
• Jakie są plusy i minusy istniejących rozwiązań. wiadających się projektach, później słuch o
niej (i o tamtych projektach) zaginął.
Sytuacja nie wygląda zbyt ciekawie. Mro-
Tymczasem pod Linuksem, pod Unik- wie bibliotek i brak jednoznacznego lide-
sami w ogólności, tryumfy święci konso- ra nie zachęca do tworzenia oprogramowa-
Poziom trudności la tekstowa. I nic dziwnego, bo z rozwią- nia, szczególnie w kontraście do będącego
zań okienkowych mamy takie perełki jak standardem na Windowsach DirectX.
TWM, FVWM czy też Window Maker (no, Pracując nad windowsowym portem ską-
ten jakoś ujdzie). Na pierwsze wersje śro- dinąd znanego emulatora Macintosha Exe-

B
iblioteka SDL (Simple DirectMedia dowiska KDE czy GNOME przyjdzie nam cutor, Sam Lantinga zauważył prostą zasa-
Layer) jest wieloplatformową war- jeszcze kilka miesięcy poczekać, dużo one dę: pewna część funkcjonalności była im-
stwą abstrakcji sprzętowej zapewnia- zresztą nie zmienią; na Rysunku 1 można plementowana w ten sam sposób na każ-
jącą niskopoziomowy dostęp do urządzeń sobie obejrzeć, jak to to wyglądało. Nawia- dej platformie sprzętowej. Zawsze należa-
dźwiękowych, wideo, klawiatury, myszy itp. sem mówiąc, ówczesnym szczytem mody ło zapewnić pewien sposób na dobranie
Na liście oficjalnie wspieranych systemów było używanie schematu graficznego na- się do ekranu, dźwięku, urządzeń wejścio-
operacyjnych znajdują się między innymi: śladującego Windows 95. wych. Stąd bardzo blisko było do idei stwo-
Windows, Linux, Mac OS, BeOS, różne od- Linuksowe gry ograniczają się do bezpo- rzenia wieloplatformowej biblioteki mają-
miany BSD, Solaris. Biblioteka nieoficjalnie średniego wykorzystywania bibliotek X11, cej za zadanie umożliwienie dostępu do
wspiera również takie systemy jak Amiga OS, na dodatek w wariancie, w którym wszyst- tych zasobów. Dzięki takiemu podejściu
OS/2, czy też interesujący nas w kontekście kie lokalne operacje odbywają się przez programista zamiast zawracać sobie głowę
tego artykułu – Symbian OS. sieć. Podczas gdy cały świat gra w Diablo szczegółami implementacyjnymi DirectX,
czy Starcrafta, fanatycy „lepszego” syste- libX11, SVGAlib mógłby wykorzystać jed-
Rys historyczny mu mogą co najwyżej popykać w XBilla, nolite API, a za niskopoziomowe operacje
Cofnijmy się do początków roku 1998, tak Xchompa czy XEvil (Rysunek 2). Istniały, właściwe dla konkretnej platformy odpo-
gdzieś w okolice lutego albo marca. Szczy- co prawda, jakieś tam próby stworzenia bi- wiadałaby biblioteka.
towym osiągnięciem w dziedzinie GUI jest bliotek, które ułatwiałyby tworzenie gier, Tak narodził się Simple DirectMedia
Windows 95. Windows 98 znane jest głów- ale bardziej przypominało to ostatnie po- Layer. Początkowo obsługiwanych plat-
nie w kontekście szybko rozpowszechniają- drygi konającej ostrygi, niż skoordynowane form sprzętowych nie było zbyt wiele; tyl-
cego się filmu (o YouTube nikt nawet wte- działania. Z ciekawszych opcji można wy- ko Windows i Linux. Szybko dołączył do
dy nie myślał), na którym Bill Gates robi mienić bibliotekę SVGALib, która umożli- nich BeOS, wspaniale zapowiadający się
dobrą minę do blue screena po próbie pod- wiała bezpośredni dostęp do karty graficz- system operacyjny, o którym dzisiaj nikt
łączenia skanera USB. DirectX znajduje się nej, jednak nic poza tym. Uzyskanie wspar- nie pamięta. Na nadmiar aplikacji wyko-
gdzieś pomiędzy wersją 3.0 a 5.0 – bez re- cia dla nowszych kart graficznych było pro- rzystujących SDL-a również nie można by-
welacji, ale wiadomo już, że Windows do blematyczne, nie istniała również możli- ło narzekać. Wspomniany wcześniej emu-
prawdziwych gier jednak się nadaje. Ma- wość jej wykorzystania pod iksami. Biblio- lator Executor, mało znany shareware (Ma-
my nawet akcelerację grafiki trójwymiaro- teka GGI, swego czasu zapowiadająca się elstrom), jak również Doom, który w wer-
wej – dopiero co wypuszczony został układ dosyć interesująco, nigdy nie wyszła poza sji wykorzystującej inne biblioteki dostęp-
Voodoo2. stadium ciekawostki. Popularna pod DOS- ny był od 4 lat. Czemu więc SDL nie został

78 SDJ Extra 34 Biblia


Biblioteka SDL na Symbianie

tylko jedną z wielu dostępnych bibliotek Za pomocą pierwszej linii inicjalizujemy bi- mknięcia okna), lub naciśnięcie klawisza
Loki Software? bliotekę i deklarujemy, że będziemy wyko- klawiatury – wyjdź z funkcji (a zatem też z
Firma Loki Software, założona w sierpniu rzystywali podsystem graficzny. Nie przej- programu).
1998 roku, zatrudnia Sama Lantingę na sta- mujemy się obsługą błędów (zostawmy to ja- Łopatologiczne wyłożenie w zasadzie ba-
nowisku lead developera. Pomysłem na biz- ko zadanie domowe dla czytelnika). Druga nalnego kodu było w tym momencie nie-
nes, jak się później okazało dosyć kiepskim, linia zapewnia poprawne uprzątnięcie zaso- zmiernie ważne. Zrozumienie działania te-
było portowanie windowsowych gier na Li- bów podczas wychodzenia z programu. go programu jest równoznaczne ze zrozu-
nuksa, idea swoją drogą zapożyczona z prze- mieniem działania całego SDL-a. Program
mysłu filmowego, gdzie analogią gry na Win- SDL_Surface* screen = SDL_SetVideoMode( jest cały czas aktywny – nie ma tu typowej
dowsie miał być film w kinie i odpowiednio 256, 256, 32, 0 ); dla Symbiana reakcji na timery, nie ma też
gry na Linuksie – film na kasecie lub płycie charakterystycznego dla środowisk graficz-
DVD. Dzięki obsadzeniu kluczowego stano- Tworzymy specjalną powierzchnię gra- nych typu GTK czy QT podłączania sygna-
wiska przez autora SDL-a gry zyskiwały so- ficzną – jej zawartość będzie wyświetlana łów, które później powodują podjęcie odpo-
lidną podstawę systemową, a rozwój biblio- w oknie aplikacji. Chcemy uzyskać okno wiednich akcji.
teki miał mocne plecy zapewnione przez o rozmiarach 256×256 pikseli i o 32-bi-
wsparcie finansowe ze strony firmy. Należy towej głębi kolorów. Ostatni parametr to Przykład bardziej kompletny
tu wspomnieć, że mimo zaangażowania Lo- flagi, które nas w tym momencie nie in- Na Listingu 2 przedstawiony został poprzed-
ki kod źródłowy Simple DirectMedia Layer teresują. nio omawiany program rozszerzony o funkcję
przez cały czas pozostawał otwarty. rysującą na ekranie. Do głównej pętli progra-
Reszta jest historią. Loki przeportowało SDL_Event event; mu dodany został następujący fragment kodu:
wiele głośnych tytułów, jak chociażby Heroes while( SDL_PollEvent( &event ) ) { ... }
of Might & Magic III, Railroad Tycoon II, De- if( SDL_LockSurface( screen ) == 0 )
scent 3, Unreal Tournament, Rune czy Postal, W nieskończonej pętli odpytujemy kolej- {
a biblioteka SDL udowodniła, że jak najbar- kę zdarzeń SDL-a. Należy upewnić się, że Draw( screen );
dziej nadaje się do poważnych zastosowań. W przy każdym odpytywaniu kolejka zosta-
chwili upadku Loki Games w styczniu 2002 nie całkowicie opróżniona (warunek pę- SDL_UnlockSurface( screen );
roku Simple DirectMedia Layer był już de tli while). Jednorazowe wywołanie SDL _ SDL_Flip( screen );
facto standardem, a znaczenie pozostałych PollEvent() jest niepoprawne! }
bibliotek zostało zmarginalizowane.
switch( event.type ) Funkcja SDL _ LockSurface udostępnia
Z czym to się je? { nam bezpośredni wskaźnik do „pamię-
Jeżeli SDL jest taką rewelacją, to z pewno- case SDL_QUIT: ci ekranu” (składowa pixels). W zależno-
ścią chcielibyśmy zobaczyć jakiś kawałek ści od implementacji i konfiguracji środo-
kodu źródłowego, żeby się przekonać, jak case SDL_KEYDOWN: wiska może się za tym kryć udostępnienie
to naprawdę wygląda. Listing 1 przedsta- return 0; tymczasowego bufora, konwersja systemo-
wia prosty program, który otwiera okienko ... wego formatu piksela na żądany podczas
i czeka na naciśnięcie dowolnego klawisza. tworzenia powierzchni graficznej, lub na-
Polecenie kompilacji jest proste: Jeżeli typem zdarzenia jest żądanie wyjścia wet brak jakiejkolwiek operacji. Każda za-
(na przykład po naciśnięciu przycisku za- blokowana powierzchnia musi być później
g++ sdl.cpp `sdl-config --libs --cflags`

Na platformach innych niż Linux jest ana-


logicznie – należy tylko podać ścieżki do
nagłówków i zlinkować program z biblio-
teką. Po uruchomieniu powinno się otwo-
rzyć okienko tak jak na Rysunku 3. Krót-
kie wyjaśnienie, co do czego służy:

#include <SDL.h>

Jedyny nagłówek biblioteki, jaki trzeba


wskazać preprocesorowi.

int main( int argc, char** argv )

Uwzględnienie parametrów argc i argv jest


istotne, bez tego program może mieć proble-
my z linkowaniem. Wynika to z podmiany
przez bibliotekę funkcji main na SDL _ main
i uruchamiania programu za pomocą funk-
cji main dostarczanej przez SDL.

SDL_Init( SDL_INIT_VIDEO ); Rysunek 1. Blast from the past, zaawansowane środowisko okienkowe w 1998 roku (http://
atexit( SDL_Quit ); xwinman.org/screenshots/fvwm2-franz.gif)

www.sdjournal.org 79
Programowanie Symbian OS

odblokowana za pomocą polecenia SDL _ Dodatkowe biblioteki czaj w wyskalowanych do 0 – 255 osobnych
UnlockSurface. Funkcja SDL _ Flip odpo- Ktoś może zapytać, czemu rysowanie po składowych R, G, B do formatu właściwego
wiedzialna jest za zamianę buforów, ie. za ekranie odbywa się w dosyć zagmatwany dla powierzchni graficznej. Zwrócenie uwa-
wyświetlenie zmian wykonanych na po- sposób. Manipulowanie wskaźnikami, ope- gi na wydajność kodu jest szczególnie waż-
wierzchni na ekranie. racje bitowe, zbyt czytelne to nie jest. Powo- ne na platformach mobilnych, gdzie proce-
W procedurze Draw pobierany jest wskaź- dów jest kilka: sory są z reguły dosyć wolne, w celu ograni-
nik do pamięci ekranu (surface->pixels), czenia zużycia baterii. Przykładową proce-
który rzutujemy na 32-bity, które odpowia- • szybkość; durę implementującą funkcjonalność Put-
dają jednemu pikselowi (podczas tworzenia • mały rozmiar kodu; Pixel (SDL jej nie posiada!) można obejrzeć
powierzchni ekranowej zażądaliśmy 32-bi- • szybkość; pod adresem http://www.libsdl.org/intro.en/
towej głębi kolorów). W pętli wypełniamy • brak zależności od innych bibliotek; usingvideo.html.
całą powierzchnię okna wzorem graficz- • szybkość. Jak można wywnioskować z powyższego
nym przedstawionym na Rysunku 4. Nale- przykładu, funkcjonalność biblioteki SDL jest
ży tu zwrócić uwagę, że omawiany kod bę- Funkcje typu PutPixel, które przy każdym stosunkowo prosta, jednak nie można powie-
dzie działał poprawnie tylko na architektu- wywołaniu muszą obliczać offset do pikse- dzieć, że niewystarczająca. Jeżeli komuś bra-
rach little endian, gdzie formatem koloru la, nigdy nie będą tak szybkie jak sekwen- kuje pewnych możliwości, być może nie zro-
32-bitowego jest 0xAARRGGBB. Funkcja cyjny zapis do pamięci. Kod pisany dla jed- zumiał jej założeń projektowych. Na szczęście
SDL_GetTicks zwraca liczbę milisekund od nego specyficznego zastosowania nie musi dla osób pragnących bardziej wysokopoziomo-
czasu inicjalizacji programu, co wykorzysta- obsługiwać wielu różnych formatów pikseli, wego podejścia istnieje kilka bibliotek uzupeł-
ne jest w celu umożliwienia animacji koloru co się z tym również wiąże, nie musi wyko- niających funkcjonalność SDL-a.
niebieskiego. nywać konwersji koloru podawanego zazwy-
SDL_gfx
Biblioteka udostępnia możliwość rysowa-
nia prymitywów graficznych takich jak
piksel, linia, prostokąt, okrąg, elipsa, łuk,
trójkąt, wielokąt, krzywa Beziera. Część z
nich dostępna jest również w wersji anty-
aliasowanej. Poza tym zaimplementowane
są bardziej zaawansowane funkcje, takie
jak rotozooming, filtrowanie obrazów czy
też kontrolowanie ilości ramek wyświetla-
nych na sekundę.
Przykładowe projekty wykorzystujące tę
bibliotekę: E-UAE, wormux, lincity-ng.
Rysunek 2. Typowa linuksowa gra z 1998 roku (http://www.games.ru/games/linux/screenshots/xevil.gif)
SDL_image
Simple DirectMedia Layer potrafi wczyty-
Listing 1. Prościutki program wać z dysku tylko obrazki zapisane w for-
macie BMP. Biblioteka SDL_image znacz-
#include <SDL.h> nie poszerza tę funkcjonalność, dodając
int main( int argc, char** argv ) obsługę między innymi formatów PNG,
{ TGA, TIFF, JPEG, GIF. Ładowanie zostało
SDL_Init( SDL_INIT_VIDEO ); ujednolicone do funkcji IMG_Load.
atexit( SDL_Quit ); Niektóre z projektów używających
SDL_Surface* screen = SDL_SetVideoMode( 256, 256, 32, 0 ); SDL_image: enigma, Battle for Wesnoth,
Rocks'n'Diamonds, Mirror Magic, Liqu-
for(;;) id War 6.
{
SDL_Event event; SDL_mixer
while( SDL_PollEvent( &event ) ) SDL implementuje bardzo podstawowe
{ urządzenie audio. Programy chcące od-
switch( event.type ) twarzać dźwięki dostają kilkadziesiąt ra-
{ zy na sekundę pewien mały bufor do wy-
case SDL_QUIT: pełnienia danymi audio w zadanym forma-
case SDL_KEYDOWN: cie. Granie dwóch dźwięków na raz lub po-
return 0; trzeba dostosowania formatu dźwięku wią-
że się z koniecznością samodzielnego napi-
default: sania miksera lub wykorzystania biblioteki
break; SDL_mixer, która ma już zaimplemento-
} wane miksowanie dowolnej liczby kanałów.
} Biblioteka umożliwia wczytywanie plików
} WAVE, VOC, OGG. Dodatkowo można
} użyć jeden kanał muzyczny, który wspiera
pliki WAVE, MOD, MIDI, OGG, MP3. Po-

80 SDJ Extra 34 Biblia


Biblioteka SDL na Symbianie

za tym mikser posiada pewne wsparcie dla tece SDL_sound. Lista obsługiwanych forma- z Simple DirectMedia Layer. Sama rasteryzacja
podstawowych efektów dźwiękowych. tów jest znacznie bardziej obszerna, doszły tu znaków odbywa się za pomocą biblioteki Fre-
Wybrane programy korzystające z tej bi- na przykład Speex, FLAC, AU, AIFF, IT, XM, etype. Pośród udostępnianych funkcji znajdu-
blioteki: Rise of the Triad, Pushover, Hed- S3M i wiele innych. je się pobieranie metadanych takich jak metry-
gewars, Jump & Bump, Trackballs. Biblioteka SDL_sound wykorzystana zo- ka czcionki, rozmiar zadanego tekstu, jak rów-
stała w projektach: DOSBox, Advanced Stra- nież rysowanie na ekranie z antyaliasingiem za-
SDL_net tegic Command. równo włączonym, jak i wyłączonym. Bibliote-
Biblioteka rozszerzająca funkcjonalność SDL ka potrafi składać tekst z kerningiem.
o obsługę sieci. Obsługuje konwersję danych SDL_ttf SDL_ttf użyte zostało między innymi w
z/na format sieciowy, rozwiązywanie nazw, Biblioteka SDL_ttf umożliwia wykorzystanie programach: X-Moto, GNU Robbo, UFO:
gniazda TCP/UDP itp., udostępniając wspól- fontów TrueType w programach korzystających Alien Invasion, Tux Paint.
ny dla wszystkich platform interfejs.
Funkcjonalność udostępnianą przez Listing 2. Program rozszerzony
SDL_net jest wymagana przez: Widelands,
Warzone 2100, Maelstrom. #include <SDL.h>

SDL_sound void Draw( SDL_Surface* surface )


Jeżeli funkcjonalność udostępniana przez {
bibliotekę SDL_mixer nam nie odpowiada, Uint32* dst = (Uint32*)surface->pixels;
możemy sami zaimplementować miksowa- Uint32 ticks = ( SDL_GetTicks() / 4 ) & 0x1FF;
nie, a ładowanie dźwięków powierzyć biblio- if( ticks & 0x100 )
{
ticks = 511 - ticks;
}
for( int y = 0; y < 256; y++ )
{
for( int x = 0; x < 256; x++ )
{
*dst++ = ( x << 16 ) | ( y << 8 ) | ticks;
}
}
}

int main( int argc, char** argv )


{
SDL_Init( SDL_INIT_VIDEO );
atexit( SDL_Quit );

Rysunek 3. Wynik działania programu z Listingu 1


SDL_Surface* screen = SDL_SetVideoMode( 256, 256, 32, 0 );

for(;;)
{
SDL_Event event;
while( SDL_PollEvent( &event ) )
{
switch( event.type )
{
case SDL_QUIT:
case SDL_KEYDOWN:
return 0;
default:
break;
}
}
if( SDL_LockSurface( screen ) == 0 )
Rysunek 4. Wzór graficzny rysowany przez {
program z Listingu 2
Draw( screen );

SDL_UnlockSurface( screen );
SDL_Flip( screen );
}
}
Rysunek 5. SDL działający na Nokii 9210 (http:// }
koti.mbnet.fi/haviital/SDL/warp.jpg)

www.sdjournal.org 81
Programowanie Symbian OS

Oczywiście nie są to wszystkie bibliote- • joystick – obsługa joysticka; Podsystemy, które przed użyciem należy
ki możliwe do wykorzystania razem z SDL- • loadso – dynamiczne wczytywanie bi- zainicjalizować za pomocą SDL_Init to: ti-
em, dużo bardziej kompletną listę można bliotek podczas działania programu; mer, audio, video, cdrom, joystick.
znaleźć pod adresem http://www.libsdl.org/ • main – wrapper implementujący pod-
libraries.php. stawową aplikację właściwą dla danej A co z Symbianem?
platformy; Symbianowy port Simple DirectMedia
Wewnętrzna budowa • stdlib – implementacja pewnej części Layer jest sprawą dosyć skomplikowaną.
Simple DirectMedia Layer składa się z kilku funkcjonalności standardowych bi- Ale zacznijmy od początku.
w miarę niezależnych od siebie modułów. bliotek, które nie wszędzie mogą być W roku 2001 Hannu Viitala udostępnia
Podział na niezależne części jest w pewnym dostępne; port biblioteki SDL na platformę EPOC i
stopniu widoczny w wywołaniu funkcji SDL_ • thread – obsługa wątków; przy jego pomocy tworzy port Frodo, emu-
Init, która przyjmuje listę podsystemów do • timer – funkcjonalność związana z latora C64 na pierwszy symbianowy tele-
zainicjalizowania. Źródła pogrupowane są w obsługą czasu; fon – Nokię 9210 (Rysunek 5). Wersja ta
następujące katalogi: • video – dosyć złożony podsystem od- miała spore braki – jak na przykład brak
powiedzialny nie tylko za grafikę, ale jakiejkolwiek obsługi dźwięku. Proble-
• audio – wszystko, co związane z również za obsługę urządzeń wejścio- mem było z pewnością również wsparcie
dźwiękiem; wych (mysz, klawiatura) i zdarzeń. różnych urządzeń, zaglądając w kod moż-
• cdrom – obsługa CD-ROM-u: odtwa- na zauważyć brak rozdzielczości typo-
rzanie audio, wysuwanie tacki; W większości katalogów znajduje się kil- wych dla symbianowych urządzeń pierw-
• cpuinfo – rozpoznawanie typu procesora; ka niewielkich plików źródłowych defi- szej i drugiej edycji, przykładowo 176x208
• events – obsługa zdarzeń; niujących wspólny interfejs danego mo- – 9210 to komunikator z niestandardo-
• file – wewnętrzna obsługa plików; dułu, a implementacje właściwe dla kon- wą rozdzielczością. Niemniej był to kawa-
• hermes – biblioteka konwersji formatów kretnej platformy znajdują się w podka- łek kodu otwierający przed programista-
pikseli; talogach. mi i użytkownikami zupełnie nowe moż-
liwości. Był – bo od dłuższego czasu jest
nierozwijany. Jeden z ważniejszych projek-
tów wykorzystujących ten port to CDoom,
przy którym obok Viitali pracował Markus
Mertama (Rysunek 6).
Nazwisko Mertama warto zapamiętać,
ponieważ to właśnie on zajął się dalszym
rozwojem symbianowego Dooma (C2Do-
om), a później również SDL-a. Odtworze-
nie chronologii jest co najmniej ciężkie,
jednak udało się dojść do tego, że pierwsza
wersja Simple DirectMedia Layer na urzą-
dzenia S60 trzeciej edycji została udostęp-
niona w sierpniu 2006 roku. Analiza roz-
woju tej wersji jest chyba niemożliwa, au-
tor zdaje się o żadnym systemie kontro-
li wersji nie słyszał, a z jego strony moż-
na ściągnąć tylko najnowszą wersję. Nie
znajduje się tam również żaden dziennik
zmian, dlatego też – tutaj tylko opis stanu
Rysunek 6. CDoom na Nokii 9210. Bramy piekieł otworzyły się 11 września 2001 roku (http:// w dniu dzisiejszym (wersja biblioteki ze
doom.wikia.com/wiki/File:Nokia9210cdoom.jpg) stycznia 2009). Biblioteka została znacz-
nie rozbudowana, co z jednej strony jest
dobre, a z drugiej złe – ale o tym później.
Dodane zostały moduły: obsługi dźwię-
ku, podobnie jak wersja pecetowa działa-
jący z wykorzystaniem wątków; obsługi
CD-ROM, służący właściwie nie wiadomo
do czego. Ważnym rozwinięciem funkcjo-
nalności jest dodanie wsparcia dla trzeciej
edycji S60, z zachowaniem kompatybilno-
ści z edycjami pierwszą i drugą. Port wspie-
ra również OpenGL-a, wirtualną mysz, ob-
sługę własnych blitterów itp.
Trzecim portem SDL-a na urządzenia z
Symbianem jest wersja Larsa Perssona zna-
nego szerzej jako Anotherguest. Różnice
między tą wersją a wersją Mertamy wydają
się nieznaczne, wynika to najpewniej z wy-
Rysunek 7. Rick Dangerous w rozdzielczościach 240x320 i 320x240 miany kodu między wersjami, przykłado-

82 SDJ Extra 34 Biblia


Biblioteka SDL na Symbianie

wo wersja Mertamy zawiera moduł obsługi ło pobrać ze strony http://www.bigorno.net/ kim – xrick pracował w rozdzielczości
joysticka napisany przez Perssona. xrick/, samo portowanie poza drobny- 320x200, a wyświetlacze telefonów mają
Czwarty port został stworzony przez au- mi problemami z ilością dostępnej pamię- rozdzielczość 320x240. W takim wypad-
tora tego artykułu z powodu rażących bra- ci przebiegło stosunkowo bezproblemowo. ku poprawnym i jedynym akceptowalnym
ków funkcjonalności, niejasnej dokumen- Niemniej, na tak wczesnym etapie pojawi- dla mnie rozwiązaniem było wyświetlanie
tacji, niesatysfakcjonującej wydajności i ły się już pewne zgrzyty związane z wyko- gry z zachowaniem odwzorowania pikse-
wielu drobnych błędów w istniejących roz- rzystaniem portu Mertamy. Przede wszyst- li 1:1, oraz wypełnienie pustej przestrzeni
wiązaniach.

Po co aż cztery porty?
W idealnej rzeczywistości nie istniałby ża-
den port SDL-a na Symbiana, byłaby to po
prostu wbudowana w bibliotekę funkcjo-
nalność, tak jak zrealizowane są wersje na
Windowsy, Linuksa, itd., których nie okre-
śla się w końcu portami. Niestety, mało ma
to wspólnego z rzeczywistością, gdzie każ-
dy z autorów ma swój pogląd na szczegóły
implementacyjne i dba tylko o własne inte-
resy. Na pewno też nikt nie utrzymuje swo-
jego forka z nudów. Persson stara się dbać
o jak największe pokrycie wszelakich wer- Rysunek 9. ScummVM odpalony w orientacji landscape
sji Symbiana, wliczając w to takie, których
nikt już dzisiaj na swoim telefonie nie ma.
Mertama ma swój własny, dosyć dziwny
Śmiechu warte
Zobaczmy, co takiego o historii powstania SDL-a piszą na Wikipedii (http://en.wikipedia.org/
światopogląd na niektóre rzeczy, poza tym wiki/Simple_DirectMedia_Layer):
pracuje w Nokii. Ja – no cóż – mam najlep- Sam Lantinga created the library, first releasing it in early 1998, while working for Loki Software.
szą wersję SDL-a na Symbiana. Przynajm- He got the idea while porting a Windows application to Macintosh. He then used SDL to port Do-
niej dla mnie. om to BeOS (see Doom source ports).
Przygoda z moją wersją kodu zaczęła się
No... Nie w Moskwie, tylko w Leningradzie, nie samochody, tylko rowery, i nie rozdają, tylko
od próby odpalenia na telefonie gry Rick kradną, a poza tym wszystko się zgadza.
Dangerous, klasyka z ośmio- i szesnastobi-
towców. Kod źródłowy klona gry można by-

Pierwszy symbianowy telefon?


Pierwszym telefonem wykorzystującym system Symbian (który wtedy tak się jeszcze nie na-
zywał – funkcjonowała nazwa EPOC Release 5) był Ericsson R380. Użytkownik nie mógł jed-
nak instalować na nim swoich programów. Ta możliwość pojawiła się dopiero w bazującej na
Symbian OS 6.0 Nokii 9210 Communicator. Stąd też w tekście znalazło się uproszczenie.

SDL 1.3
Jest to mityczna gałąź rozwojowa biblioteki mająca w założeniach przewrócić do góry
nogami całą wiedzę o SDL-u. Autorzy zapowiadają zerwanie kompatybilności binarnej z
wersją 1.2, możliwość otworzenia więcej niż jednego okna, znaczne przebudowanie ko-
du odpowiedzialnego za obsługę urządzeń wyświetlających, dodanie akceleracji grafiki
dwuwymiarowej przy pomocy sprzętu akcelerującego grafikę trójwymiarową, zmiany w
obsłudze urządzeń wejściowych, jak na przykład obsługę wielu myszy jednocześnie, im-
plementację kodu umożliwiającego nagrywanie dźwięku, wspieranie systemów posiada-
jących wiele kart dźwiękowych, możliwość odłączania i podłączania joysticków bez re-
startowania aplikacji, obsługę interfejsów haptycznych i wiele innych zmian. Pierwsze
przebąknięcia o tej wersji pojawiły się w okolicach 2000 roku, ale do tej pory nie docze-
kaliśmy się wersji stabilnej.

W Nokii znają się na rzeczy!


No cóż. Tak to się może wydawać, jak się jest użytkownikiem telefonów z Symbianem. Po
dłuższym obcowaniu z tym systemem operacyjnym od strony programistycznej, włączając
w to pracę nad grami na nową, niesamowitą platformę, przebijanie się przez żenującą doku-
mentację czy też użeranie się z niemającym o niczym pojęcia wsparciem technicznym, moż-
na stwierdzić tylko jedno: Symbian został zaprojektowany przez oderwany od rzeczywisto-
ści komitet teoretyków, zaimplementowany został przez ludzi charakteryzujących się rażą-
cym brakiem kompetencji, a potem rzesza programistów dała sobie wmówić bzdurę, jako-
by to na Symbiana pisało się w języku „Symbian C++”, a nie C++. Więcej na ten temat można
Rysunek 8. ScummVM odpalony w orientacji znaleźć w SDJ 06/2008.
portrait

www.sdjournal.org 83
Programowanie Symbian OS

kolorem czarnym. Niestety, SDL rozciągał 1.2.13 (najnowsza w chwili pisania tego ar- Czyszczenie kodu
obraz wyświetlany przez grę do pełnej roz- tykułu) i porcie Mertamy w wersji 2.2.0. Stan projektu można było opisać jednym
dzielczości ekranu, co po pierwsze wyglą- słowem: bagno. Dwa nigdzie nie wykorzy-
dało niezbyt ładnie (niektóre linie były po- EKA1? stywane śmiecio-pliki źródłowe mające w
wielone, inne nie), po drugie zaburzało pro- Pierwszą, bardzo poważną zmianą by- sumie 55 KB i wprowadzające zamiesza-
porcje, a po trzecie było okropnie wolne. Pro- ło usunięcie wsparcia dla S60 pierwszej i nie. Pięć różnych styli formatowania kodu
blem został ominięty przez rozszerzenie po- drugiej edycji (kernel EKA1), które z mo- w obrębie 20 linii w jednym pliku. Mnó-
wierzchni ekranowej, na której gra się ry- jego punktu widzenia było niepotrzebnym stwo zakomentowanego kodu nieznanego
suje do rozdzielczości wyświetlacza telefo- balastem. Platforma jest praktycznie rzecz przeznaczenia. #ifdefy bez żadnego opisu
nu. Nie było to jednak zupełne rozwiąza- biorąc martwa, co więcej, dostępny sprzęt i sensu. Sześciokilobajtowa reimplemen-
nie problemu – SDL nie potrafił przełączyć znacznie ogranicza możliwe zastosowania tacja std::queue (na dodatek zrealizowa-
orientacji telefonu z domyślnej 240x320 na – w rozdzielczości 176x208, z kilkudzie- na Symbian-way). Komentarze stwierdza-
320x240, co wymagało uruchomienia pro- sięcio megahertzowym procesorem i kilko- jące rzeczy oczywiste (includes, forward
gramu typu RotateMe przed włączeniem ma megabajtami pamięci się nie zaszaleje. declarations). Przekombinowana hierar-
samej gry. Jeżeli ten krok pominąć, mogli- Poza tym – jak testować, czy zmiany w bi- chia klas, która sensu żadnego co prawda
śmy się delektować cudem widocznym na bliotece będą się chociażby kompilowały ze nie ma, za to jest bardzo obiektowa. Więk-
Rysunku 7 (następnego dnia cud posiniał i starym kodem? EKA1 musiało odejść. szość kodu została oczyszczona.
obrzękł). Problemy zostały zgłoszone auto- Wartym wspomnienia udogodnieniem
rowi portu SDL-a, niestety odpowiedź by- wyłącznym dla EKA2 (S60 trzeciej i piątej Wirtualny wskaźnik myszy
ła dalece niesatysfakcjonująca, chociaż kil- edycji) jest dostępność bibliotek OpenC i Kuriozum pozbawione jakiegokolwiek
ka pomniejszych błędów – jak na przykład OpenC++. Dzięki nim można w miarę bez- użytku: po naciśnięciu zielonej słuchawki
niepoprawna implementacja funkcji SDL_ problemowo wykorzystywać standardowe na ekranie telefonu pokazywał się wskaź-
ListModes zostało naprawionych. funkcje POSIX, STL i podzbiór Boosta. nik myszy przydatny zupełnie do nicze-
Dalszy rozwój xricka został przeze mnie go. Trafienie nim w konkretny punkt by-
porzucony, pojawiła się zresztą konkuren- Biblioteka statyczna czy dynamiczna? ło trudne bądź niemożliwe (duży skok),
cyjna implementacja wyposażona w me- Mertama dostarcza swoją bibliotekę w for- a przesuwanie na drugi koniec ekranu
nu, z poziomu którego można było zmie- mie dynamicznie linkowanej. Ma to mo- uciążliwe (brak akceleracji ruchu). Klika-
nić orientację telefonu, zmienić opcje ska- że jakieś tam zalety, ale jest również po- nie również zostało zrealizowane bez po-
lowania itp. niepotrzebne w/g mnie prze- ważna wada: GCC 3.4 (jedyne wspierane myślunku: pierwsze kliknięcie przesuwa-
szkadzajki. Projektem, którym bardziej się przez Nokię) ma błąd, który uniemożliwia ło prawdziwy kursor myszy w miejsce wir-
zainteresowałem od strony programistycz- poprawne zbudowanie dll-ki, w której jest tualnego, a dopiero drugie było rzeczywi-
nej, został OpenTTD – klon (czy też może jakieś tam statyczne mambo-dżambo (nie- stym kliknięciem, ale przez program wi-
implementacja na podstawie deasemblowa- zbyt pamiętam, o co tam chodziło). Jedy- dziane było dopiero po wyłączeniu trybu
nego kodu wzbogacona o wiele poprawek i ną alternatywą jest wykorzystanie płatne- wirtualnego kursora. Mertama w C2Do-
udoskonaleń) gry Transport Tycoon Delu- go kompilatora RVCT. Dostarczanie bi- omie z tego nie korzystał, bo i do czego, a w
xe. Pierwsza wersja działająca na S60 zo- blioteki dynamicznej wymaga też zacho- OpenTTD (gra całkowicie kontrolowana
stała opublikowana po niecałym miesiącu wania kompatybilności binarnej, co może myszą!) okazało się, ile jest to warte, wobec
prac, pod koniec stycznia 2008 roku. Pod- być czasem zbyt mocno wiążące ręce. Po- czego SDL-owy wskaźnik został usunięty.
czas dalszego rozwoju projektu niedosko- za tym, mimo zapewnień Mertamy, na fo-
nałości SDL-a Mertamy tak dały mi się we rach można znaleźć raporty o problemach SDL_ListModes
znaki, jak również wymiana maili była na ze współpracą symbianowego OpenTTD Wspomniana wcześniej problematyczna
tyle nieowocna, że w lipcu 2008 zacząłem z C2Doomem. Biorąc wszystko powyższe funkcja zachowywała się już co prawda
rozwijać swoje własne odgałęzienie biblio- pod uwagę, decyzja mogła być tylko jedna zgodnie ze specyfikacją, jednak implemen-
teki. Kod bazuje na bibliotece SDL w wersji – mój SDL będzie statycznie linkowany. tacja była brzydkim hackiem. Kod odpytu-

Rysunek 10. Wyraźnie widoczne przerwy w


strumieniu audio Rysunek 11. Zużycie energii przez aplikację aktywną, w tle i gdy nie jest włączona

A co to jest dźwięk?
Obrazki są łatwe. Każdy wie, co to jest rozdzielczość, każdy ma mniejsze lub większe pojęcie o podziale każdego koloru na składowe: czerwoną,
zieloną i niebieską. Pojęcie o tym, czym tak naprawdę są dane audio, jest zazwyczaj dużo mniejsze. W dużym skrócie: dźwięk jest zapisywany
za pomocą próbek, których liczba na sekundę jest określana przez częstotliwość. Próbki mogą być podawane dla jednego (mono), dwóch (ste-
reo), a czasami nawet większej liczby kanałów. Próbki mogą mieć też różny, ale stały w strumieniu format: 8 bit, 16 bit, ze znakiem lub bez. Licz-
ba bitów określa dokładność, z jaką mierzymy poziom natężenia dźwięku, a obecność znaku to czysta kosmetyka. Następne pytanie – jak dzia-
ła głośnik? W dużym uproszczeniu jest to papierowa membrana z przyklejonym magnesem, który może nią poruszać w jedną lub drugą stronę.
No i tak naprawdę wiemy już wszystko: próbki opisują relatywne napięcia podawane na magnes, czyli właściwie położenia membrany głośnika.
Membrana, odkształcając się, wytwarza fale dźwiękowe. Proste, prawda?

84 SDJ Extra 34 Biblia


Biblioteka SDL na Symbianie

jący bibliotekę o listę rozdzielczości wy- Listing 3. Plik bld.inf


chodził poza przekazaną tablicę i odczyty-
wał specjalnie ustawionego na 0 prywatne- prj_mmpfiles
go inta. Funkcja została zmieniona na po- sdl.mmp
prawną.
Listing 4. Plik sdl.mmp.
Wychodzenie z aplikacji target sdl.exe
Mertama błędnie założył, że naciśnięcie targettype exe
czerwonej słuchawki musi być równoważ- uid 0x100039ce 0xa0112233
ne zabiciu aplikacji. SDL w takiej sytuacji vendorid 0
wysyłał do aplikacji zdarzenie SDL_Quit
i wyłączał dostęp do ekranu. Tymczasem epocstacksize 0xA000
OpenTTD po otrzymaniu tego zdarzenia epocheapsize 0x100000 0x1000000
wyświetla zapytanie czy na pewno chcesz
wyjść z gry. Wyłączenie wyświetlania grafi- start resource sdl.rss
ki oczywiście uniemożliwiało użytkowni- targetpath \private\10003a3f\import\apps
kowi reakcję, a jedynym sposobem na wyłą- end
czenie aplikacji był restart telefonu. W mo-
jej wersji jedyną reakcją biblioteki na naci- start resource sdl_loc.rss
śnięcie klawisza czerwonej słuchawki jest targetpath \resource\apps
wysłanie odpowiedniego zdarzenia. lang SC
Kolejne zadanie domowe dla czytelni- end
ków: proszę zakomentować obsługę SDL_
Quit w programie z Listingu 1 i spróbować option gcce -funit-at-a-time
zamknąć program za pomocą przycisku za-
mknięcia okna. source sdl.cpp
Interesującą ciekawostką jest fakt obsłu-
gi czerwonej słuchawki w dwóch różnych systeminclude \epoc32\include\stdapis
miejscach. Okazuje się, że odpowiedni kod systeminclude \epoc32\include
w metodzie AppUi::HandleCommandL jest systeminclude \epoc32\include\SDL
wywoływany tylko za pierwszym naciśnię-
ciem klawisza. Aby obsłużyć następne na- library apgrfx.lib
ciśnięcia, należy dodać obsługę do metody library apparc.lib
AppUi::HandleWsEventL.
library avkon.lib
Okno konsoli (?) library bafl.lib
Nikt nie wie, co to jest – przypuszczalnie
również autor. W dokumentacji znajdu- library bitgdi.lib
je się o tym mętna wzmianka, jakichkol- library cone.lib
wiek przykładów użycia brak. Wyleciało
z hukiem. library efsrv.lib
library eikcore.lib
Drawing Overlays
Wśród dodanej funkcjonalności w porcie library eiksrv.lib
SDL-a znajdowała się możliwość naryso- library euser.lib
wania czegoś warstwę wyżej niż rysowa-
ła aplikacja; wirtualny kursor myszy był library fbscli.lib
jednym z przypadków użycia (prawdopo- library gdi.lib
dobnie jedynym). Sam pomysł, żeby udo-
stępniać funkcje, które są dostępne tylko library hal.lib
na Symbianie, a do tego mogą być w pro- library libc.lib
sty sposób zrealizowane w samej aplika- library libm.lib
cji, jest trochę nie na miejscu, wobec cze- library libpthread.lib
go w moim porcie Drawing Overlays zna- library libstdcpp.lib
leźć nie można. library libz.lib
library mediaclientaudiostream.lib
Skalowanie ekranu library remconcoreapi.lib
Problem został omówiony wcześniej, dla library remconinterfacebase.lib
przypomnienia sobie, o co chodzi, wystar- library scdv.lib
czy spojrzeć na Rysunek 7. Argument pro- library ws32.lib
gramistów pracujących nad pozostałymi library centralrepository.lib
portami, jakoby użytkownicy woleli mieć
rozciągnięty, zniekształcony obraz gry za- staticlibrary sdl.lib
miast odwzorowania pikseli 1:1 z czarny-

www.sdjournal.org 85
Programowanie Symbian OS

mi paskami na górze i dole, po prostu do trzymany, przykładowo N95 8GB. Na ta- reagować na systemową zmianę orientacji
mnie nie trafia. Co za tym idzie, skalować kim telefonie uruchamiamy w portretowej przez wysłanie do aplikacji komunikatu o
ekranu u mnie się nie da (a przynajmniej orientacji ekranu ScummVM, wykorzystu- zmianie rozmiaru okna.
nie tak jak w reszcie portów, ale o tym tro- jący port SDL-a autorstwa Perssona. Obraz Należy tu wspomnieć, że w forku Mer-
chę później). na ekranie będzie prawdopodobnie znie- tamy pojawiło się jakiś czas temu wsparcie
kształcony; aby rozwiązać ten problem, dla sprzętowych orientacji. Widocznie wy-
Dostęp do pamięci ekranu musimy sięgnąć do dokumentacji, z któ- starczająco długo narzekałem.
Pod Symbianem na ekranie można rysować rej można dowiedzieć się, jaka kombina-
na kilka różnych sposobów. Mertama wy- cja klawiszy wyłącza skalowanie ekranu, ja- Wsparcie dla własnych blitterów
myślił sobie, że będzie wspierał wszystkie ka kombinacja obraca ekran itd. W efekcie Kolejna rzecz, która została usunięta z powo-
możliwe tryby dostępu, mimo że niektóre powinniśmy uzyskać wynik zbliżony do te- du przerzucania odpowiedzialności za funk-
są niewydajne albo nawet określane przez go z Rysunku 8, gdzie możemy zaobserwo- cjonalność, którą powinna implementować
dokumentację jako przestarzałe. Podejście wać obrócone menu. Naturalną reakcją jest biblioteka na programistę aplikacji. Ktoś, kto
takie argumentuje różnicami wydajności obrócenie telefonu tak, by obraz był w pra- wykorzystuje SDL nie powinien się intereso-
działania na różnych urządzeniach, co za- widłowym położeniu. Tutaj zaczynają się wać szczegółami dostępu do ekranu.
sadniczo żadnego programisty używającego dziać śmieszne rzeczy: telefon zmienia
SDL-a nie powinno interesować, od wybo- orientację na krajobrazową (landscape), ale Trzy biblioteki?
ru poprawnego trybu jest w końcu biblio- biblioteka, a co za tym idzie – aplikacja, te- SDL w wersji Mertamy dostarczany był
teka. Mertama bardzo również zachwala go nie obsługuje. Opłakany efekt końcowy w postaci trzech bibliotek: zasadniczego
wykorzystanie BitGdi, które niby to jest możemy zobaczyć na Rysunku 9. Popraw- SDL-a, przykładowej implementacji pod-
najlepiej zaimplementowane, najbardziej nym zachowaniem byłoby oczywiście wy- stawowej aplikacji symbianowej i małego
pewne co do wsparcia w przyszłości, po- muszenie przez aplikację stosowania orien- kawałka kodu odpowiedzialnego za prze-
za tym wspierające systemowe efekty gra- tacji landscape, niestety, zarówno Persson, kazanie opcji do biblioteki. Zgodnie z za-
ficzne i umożliwiające działanie w okienku. jak i Mertama są dosyć uparci w tej spra- sadą mówiącą, że programista aplikacji nie
Wszystko byłoby dobrze, gdyby nie doku- wie, twierdząc, że sposób obsługi orienta- powinien być odpowiedzialny za systemo-
mentacja jasno stwierdzająca, że to właśnie cji zależy od telefonu, w większości przy- we sprawy, implementacja aplikacji została
podejście jest przestarzałe i wolne, a zale- padków jest ona na poziomie systemu ope- włączona do głównej biblioteki, przecho-
ca się używanie Direct Screen Access, czyli racyjnego czysto programowa i dlatego le- dząc przy okazji małe odchudzenie. Na-
bezpośredniego dostępu do ekranu. Moja piej stosować hacki w bibliotece polegające tomiast kod przekazujący opcje został usu-
wersja biblioteki korzysta wyłącznie z tego na trzymaniu się jednej sprzętowej orien- nięty, razem z funkcjonalnością włączaną
trybu rysowania, a co za tym idzie jest tak tacji i zmieniania sposobu rysowania na przez opcje (wirtualny kursor myszy, tryb
szybka jak to tylko możliwe. ekranie w zależności od ustawionej w bi- dostępu do pamięci ekranu itd.).
bliotece programowej orientacji. Podejście
Orientacja ekranu takie – jak widać na załączonym obrazku Klawisze zmiany głośności
Poprawna obsługa zmian orientacji ekra- – zupełnie się nie sprawdza. Udostępniana Kwestia obsługi tych klawiszy w ogóle nie
nu zawsze była dla mnie jednym z klu- przeze mnie wersja kodu nie tylko imple- została poruszona w portach SDL-a. Jest to
czowych elementów, które Simple Direct- mentuje obsługę orientacji sprzętowo, ale uciążliwe do tego stopnia, że w ScummVM
Media Layer musi poprawnie obsługiwać. również zupełnie pozbawia programistę głośność zmienia się, naciskając zieloną
Problem wyjaśnić będzie najlepiej na przy- konieczności wiedzy o tym, że jakieś orien- słuchawkę, co powoduje wejście do specjal-
kładzie. Załóżmy, że posiadamy telefon z tacje w ogóle istnieją. Biblioteka sama do- nego trybu, w którym klawisze góra/dół
wbudowanym akcelerometrem, taki, któ- pasowuje orientację do zażądanej rozdziel- zmieniają głośność, po czym znowu naci-
ry potrafi automatycznie zmienić orien- czości ekranu, a w przypadku stosowania ska się zieloną słuchawkę, aby wrócić do
tację ekranu w zależności od tego, jak jest wyświetlania pełnoekranowego potrafi za- normalnego stanu obsługi klawiszy. Do
tego dochodzi jeszcze konieczność wcze-
śniejszego ustawienia odpowiedniego try-
Wsparcie dla emulatora bu skalowania ekranu, gdyż w przypadku
Przede wszystkim: to, co przychodzi razem z SDK Symbiana i nazywa się „emulator”, to nie
stosowania nierozciągniętego obrazu kla-
jest żaden emulator, tylko implementacja wybranej funkcjonalności systemu bazująca na
WinApi. Co za tym idzie, wszystko, co się dzieje pod spodem, nie ma wiele wspólnego z do- wisze góra/dół odpowiedzialne są za prze-
celowym sprzętem, na którym potrafią się dziać dosyć dziwne rzeczy. Od debugowania na suwanie ekranu.
urządzeniu się nie ucieknie. A po co zawracać sobie głowę „emulatorem”, kiedy aplikację ko- Używanie tych klawiszy w kodzie Mer-
rzystającą z SDL-a można uruchomić natywnie na pececie? tamy zmusza programistę do dosyć poważ-
nego zgłębienia się w symbianowy kod, bo-
wiem Nokia owszem, umożliwia do nich
dostęp, ale za pomocą API Bluetooth. Jak-
Ciekawsze projekty używające SDL na Symbianie by tego było mało, implementacja mecha-
OpenTTD – pomimo wielu naśladowców najlepsza do tej pory zabawa w budowanie impe-
rium transportowego – http://www.tt-forums.net/viewtopic.php?t=35942;
nizmu obserwatora, który pozwala na
C2
Doom – co tu dużo mówić, klasyka – http://koti.mbnet.fi/mertama/index_new.html; zmianę mapy klawiatury, posiada poważny
UAE4ALL – emulator Amigi – http://my-symbian.com/forum/viewtopic.php?p=360328#360328; błąd: po modyfikacji przez użytkownika
DOSBox – emulator DOS-a – http://s60dosbox.sourceforge.net/; biblioteka i tak nadpisuje ją swoją wersją.
Rick Dangerous – kultowa platformówka – http://www.atopo.net/tool_rick.php; Mój kod ma wbudowaną obsługę klawi-
ScummVM – maszyna wirtualna pozwalająca uruchamiać stare gry przygodowe –
szy zmiany głośności. Programista musi
http://scummvm.org/;
OpenTyrian – jedna z lepszych strzelanek „w górę” – http://www.embeddev.se/agroot/tyrian_ tylko obsłużyć za pomocą standardowego
s60v3.sis. sposobu obsługi klawiatury dwa dodatko-
we kody klawiszy.

86 SDJ Extra 34 Biblia


Biblioteka SDL na Symbianie

Rozmiar bufora audio audio z zaznaczonymi przerwami można zo- Wyłączanie dźwięku
Odtwarzanie dźwięku na niskim poziomie baczyć na Rysunku 10. Zastąpienie dziwacz- Prawidłowa obsługa wyłączania dźwięku
opiera się na wypełnianiu bufora danych nie obliczanych opóźnień występujących w na telefonach komórkowych jest bardzo
dźwiękowych, który jest następnie przeka- kodzie obsługującym bufory audio przez pro- ważna, nie chcemy w końcu, żeby w cza-
zywany niżej, do sprzętu. Wszystkie wyso- sty mechanizm naśladujący muteksy rozwią- sie gdy użytkownik prowadzi rozmowę te-
kopoziomowe metody odgrywania plików zało problem. lefoniczną, grała w tle muzyka. Realizacja
dźwiękowych mogą się starać ukrywać ten Nagranie dźwięku przed modyfikacjami: w bibliotece Mertamy była bardziej szyb-
bufor, ale na wystarczająco niskim pozio- http://team.pld-linux.org/%7Ewolf/symbian/ kim hackiem niż zaplanowanym działa-
mie zawsze gdzieś się on pojawi. Mamy tu- audio/old.ogg. Nagranie po modyfikacjach: niem – za sterowanie urządzeniem dźwię-
taj interesujący problem: jak prawidłowo http://team.pld-linux.org/%7Ewolf/symbian/ kowym odpowiedzialny był kod wyświetla-
wybrać rozmiar bufora? Gdy bufor będzie audio/new.ogg. jący grafikę, poza tym nie można było wy-
mały (przykładowo 512 bajtów), słyszal-
ne opóźnienie dźwięku będzie bardzo ma- Listing 5. Plik sdl.pkg
łe – przy 16 bitowych próbkach i częstotli-
wości 44 kHz w stereo system wymaga wy- &EN
pełnienia bufora co 3 ms. Niestety, jeżeli
nie zdążymy z obsługą tego żądania, urzą- ; standard SIS file header
dzenie dźwiękowe nie będzie miało co od- #{"SDL"},(0xA0112233),1,0,0
twarzać – pojawi się chwila ciszy. Aby tego
uniknąć, należy zwiększyć rozmiar bufora, ;Localised Vendor name
co da aplikacji więcej czasu na reakcję, ale %{"Vendor-EN"}
jednocześnie wiąże się ze wzrostem słyszal-
nego opóźnienia dźwięku. Urządzenie au- ;Unique Vendor name
dio ma, przykładowo, pół sekundy danych, :"Vendor"
jeżeli będziemy chcieli teraz odtworzyć ja-
kiś dźwięk, usłyszymy go dopiero gdy te da- [0x101F7961], 0, 0, 0, {"S60v3"}
ne się skończą i ponownie będziemy musie- [0x1028315F], 0, 0, 0, {"S60v5"}
li wypełnić bufor.
Rozważania te są ważne dla zrozumienia ;Files to install
problemu istniejącego w bibliotece Mertamy. "\epoc32\release\gcce\urel\sdl.exe" - "!:\sys\bin\sdl.exe"
Można tam było podać rozmiar bufora pro-
gramowego, ale bufor sprzętowy zawsze miał "\epoc32\data\z\private\10003a3f\import\apps\sdl.rsc" - "!:\private\10003a3f\
256 bajtów! Pięknie łączyło to wszystkie pro- import\apps\sdl.rsc"
blemy obu alternatyw, nie dając zalet żadnej
z nich. Po uproszczeniu nieźle przekombino- "\epoc32\data\z\resource\apps\sdl_loc.rsc" - "!:\resource\apps\sdl_loc.rsc"
wanego kawałka kodu moja wersja po pierw-
sze zachowuje się zgodnie z oczekiwaniami, Listing 6. Plik sdl.rss
po drugie działa dużo bardziej wydajnie. #include <appinfo.rh>
Persson, prawdopodobnie dochodząc
do podobnych wniosków, w swoim porcie UID2 KUidAppRegistrationResourceFile
przepisał system dźwiękowy od zera. UID3 0xA0112233

Parametry aplikacji RESOURCE APP_REGISTRATION_INFO


Mertama umożliwiał wprowadzenie parame- {
trów aplikacji przed jej uruchomieniem, wy- app_file="sdl";
świetlając symbianowy dialog. Podczas two- localisable_resource_file="\\resource\\apps\\sdl_loc";
rzenia aplikacji można to było obejść, umiesz- }
czając w konkretnym miejscu plik, jednak by-
ło to dosyć uciążliwe dla programisty. Pozo- Listing 7. Plik sdl_loc.rss
stawienie możliwości wprowadzenia dodat- #include <appinfo.rh>
kowych opcji wprowadzało natomiast niektó-
rych użytkowników w zakłopotanie. Coś mi RESOURCE LOCALISABLE_APP_INFO
wyskoczyło, co mam wpisać? – pytali. Zamie- {
szanie wprowadzane przez tę funkcjonalność short_caption = "SDL Test";
połączone ze stosunkowo dużym rozmiarem caption_and_icon =
kodu odpowiedzialnym za obsługę w końcu {
zaważyło nad jej usunięciem. CAPTION_AND_ICON_INFO
{
Trzeszczący dźwięk caption = "SDL Test";
Symbianowy SDL miał dalsze problemy z od- number_of_icons = 0;
twarzaniem dźwięków. Wyczulone ucho by- }
ło w stanie wychwycić przerwy w odtwarza- };
nym dźwięku. Analiza w edytorze potwier- }
dziła przypuszczenia, fragment strumienia

www.sdjournal.org 87
Programowanie Symbian OS

łączyć odtwarzania dźwięku z pozostawio- świetlić obraz wielkości 640x480, co więcej, Wynikowa biblioteka statyczna i wymagane
nym miksowaniem. Istnieją aplikacje, w jest to obraz poprawnie zmniejszony, bez nagłówki zostaną automatycznie umieszczo-
których synchronizacja czasowa powiąza- uciekania się do sztuczek takich jak rysowa- ne w odpowiednich katalogach SDK.
na jest z odtwarzaniem dźwięku, nie mo- nie co drugiego piksela. Zbudowanie naszej przykładowej aplikacji
żemy sobie więc pozwolić na zupełne wy- Powyższa lista problemów jest dość po- z Listingu 2 nie wymaga oczywiście żadnych
łączenie obsługi audio, nawet gdy nic nie kaźna, nie jest to bynajmniej przechwala- zmian w kodzie, ale niestety proces nie jest
gra z głośników. nie się, tylko próba uwidocznienia najważ- prosty i przyjemny z powodu konieczności
W mojej wersji biblioteki obsługa dźwię- niejszych mankamentów, na które moż- stworzenia plików odpowiedzialnych za reje-
ku jest całkowicie wyłączona, gdy aplikacja na się natknąć podczas używania bibliote- strację aplikacji w menu telefonu. W katalo-
znajduje się w tle. Gdy włączony jest pro- ki Simple DirectMedia Layer pod Symbia- gu z programem tworzymy pliki z Listingów
fil milczący (przypadek w ogóle nie ob- nem. Największym dowcipem jest promo- 3, 4, 5, 6, 7. Stosunkowo pokaźna lista biblio-
sługiwany w pozostałych wariantach bi- wane przez Mertamę hasełko S60 program- tek w pliku sdl.mmp wynika z braku możli-
blioteki), wyjście audio jest wyłączone, ale ming without Symbian!, które w zderzeniu wości zlinkowania statycznej biblioteki z pli-
dźwięk jest miksowany. z rzeczywistością okazuje się tak napraw- kami przez nią wymaganymi. Ostatnim kro-
dę stekiem bzdur. Dość wspomnieć o stric- kiem jest wydanie poleceń:
Zużycie energii te symbianowej klasie CSDL i wszystkich
Pomiary obciążenia baterii wykonane apli- usuniętych przeze mnie dodatkach. bldmake bldfiles
kacją Nokia Energy Profiler nie nastraja- abld build gcce urel
ły pozytywnie. Aplikacja podczas działania Trochę praktyki makesis -d$EPOCROOT sdl.pkg
potrzebowała 1,07W, co jest akceptowalne, Można powiedzieć: no dobra, a jak tego signsis sdl.sis sdl.sisx
w końcu interesuje nas jak największa wy- użyć? Przede wszystkim musimy mieć za-
dajność gry, gdy w nią gramy. Dużo gorzej instalowane symbianowe SDK. Szczegóło- Uwaga: na Windowsach może być wymaga-
przedstawiały się wyniki aplikacji chodzą- we instrukcje dotyczące instalacji można na drobna korekta składni polecenia signsis
cej w tle, która zużywała aż 0,51W, co silnie znaleźć w SDJ 06/2008, tutaj nie będziemy (dodanie certyfikatów, można je wcześniej
kontrastowało z przypadkiem, gdy aplikacja się wgłębiać w temat. Zakładam też, że czy- wygenerować komendą makekeys).
nie była włączona – wtedy wykorzystanie telnik ma pojęcie o budowaniu programów Wygenerowany plik sdl.sisx wrzucamy
energii plasowało się na poziomie 0,11W, co na Symbiana przynajmniej na poziomie po- na telefon, instalujemy i cieszymy się dzia-
widać na Rysunku 11. Tylko w moim porcie mocy dostarczanej w ramach SDK. łającym programem.
zużycie energii przez aplikację działającą w Bibliotekę SDL w wersji obsługującej S60
tle zostało obniżone do 0,13W. można pobrać na dwa różne sposoby. Jeże- Słowo na koniec
li ktoś używa systemu kontroli wersji Git, Zapoznaliśmy się z dostępnymi portami bi-
Format koloru piksela wystarczy, że sklonuje adres git://repo.or.cz/ blioteki SDL na Symbiana, poznaliśmy ich
Mój port SDL-a wewnętrznie obsługu- SDL.s60v3.git. Alternatywą jest pobranie wady i zalety. Nie pozostaje nic innego, jak
je tylko format 565, podczas gdy pozosta- paczki z najnowszą wersją źródeł spod adre- brać się za przenoszenie istniejących gier i
łe warianty starają się obsługiwać możliwie su http://repo.or.cz/w/SDL.s60v3.git?a=snap- aplikacji na telefony. Wyrażam tylko nadzie-
wszystkie. Różnica jest czysto architektu- shot;sf=tgz. Tutaj ważna uwaga: w obu przy- ję, że przy wyborze używanej wersji biblio-
ralna, dla programisty wykorzystującego bi- padkach nie ma żadnej gwarancji stabilności teki programiści będą się kierować wygodą
bliotekę nic się nie zmienia. W dalszym cią- kodu, zazwyczaj jednak sprawdzam zmiany użytkownika, czyli również moją.
gu może używać koloru ośmio- czy 32-bito- przed wysłaniem ich na serwer.
wego, jedynie wewnętrzny bufor jest szesna- Aby skompilować bibliotekę, wchodzi-
stobitowy, co może co prawda spowodować my do katalogu symbian: BARTOSZ TAUDUL
nieznaczne obniżenie jakości finalnego ob- Bartosz Taudul zajmuje się rozwojem jednego
razu, jednak znacznie upraszcza kod i umoż- cd symbian z forków biblioteki SDL na S60. Pracuje również ja-
liwia bardzo szybkie obliczenie średnie- ko programista w firmie Gamelion wchodzącej w
go koloru z czterech pikseli. Ta funkcjonal- Następnie wydajemy polecenia urucha- skład Grupy BLStream; częścią jego obowiązków
ność jest wykorzystywana do obsługi trybów miające proces budowania: jest optymalizacja i rozwój niskopoziomowej bi-
graficznych o rozdzielczości większej niż fi- blioteki zapewniającej jednolity dostęp do sprzętu
zyczna rozdzielczość wyświetlacza telefonu bldmake bldfiles z poziomu aplikacji na różnych platformach.
– na ekranie wielkości 320x240 można wy- abld build gcce urel Kontakt z autorem: wolf@pld-linux.org

W Sieci
• http://libsdl.org/ – strona domowa biblioteki SDL;
• http://www.ferzkopp.net/joomla/content/view/19/14/ – biblioteka SDL_gfx;
• http://www.libsdl.org/projects/SDL_image/ – biblioteka SDL_image;
• http://www.libsdl.org/projects/SDL_mixer/ – biblioteka SDL_mixer;
• http://www.libsdl.org/projects/SDL_net/ – biblioteka SDL_net;
• http://icculus.org/SDL_sound/ – biblioteka SDL_sound;
• http://www.libsdl.org/projects/SDL_ttf/ – biblioteka SDL_ttf;
• http://galaxygameworks.com/ – kręcenie biznesu opartego o SDL;
• http://koti.mbnet.fi/haviital/index.shtml?projects_sdl – pierwszy port biblioteki na Symbiana;
• http://koti.mbnet.fi/mertama/sdl.html – „oficjalny” port SDL-a na Symbiana;
• http://www.embeddev.se/agroot/scummvm.php – SDL w wydaniu AnotherGuesta;
• http://repo.or.cz/w/SDL.s60v3.git – repozytorium kodu czwartego portu Simple DirectMedia Layer;

88 SDJ Extra 34 Biblia


Programowanie Android

Android Market
bliżej dewelopera
Stworzenie aplikacji to dopiero początek.
Dowiedz się, jak ją sprzedać!
Głównym zmartwieniem deweloperów planujących sprzedaż swoich
programów jest dotarcie do odpowiedniej grupy odbiorców. Android
Market, jako oficjalna platforma dystrybucyjna, zakłada nieograniczony
dostęp do wszystkich posiadaczy urządzeń z oprogramowaniem Google.
Czy zastanawiałeś się, jak wykorzystać ten ogromny potencjał?
nane dla wszystkich uczestników rynku, ale
Dowiesz się: Powinieneś wiedzieć: taka sytuacja z pewnością nie będzie trwać
• Jak poruszać się w gąszczu aplikacji jako • Jakie są podstawowe założenia budowy i wiecznie.
użytkownik telefonu Google; działania aplikacji Android;
• Jak opublikować swoją aplikację na Android • Jakie są zasady funkcjonowania finansowych Android Market
Market; transakcji online. z perspektywy użytkownika
• Jak trafić do największej liczby odbiorców. Aplikacja Android Market dostępna jest na li-
ście programów każdej standardowej dystry-
bucji systemu, zainstalowanej na urządzeniu
Od ukazania się na rynku pierwszego urzą- z logo Google. Można ją uruchomić poprzez
dzenia HTC G1, wyposażonego w system wybranie charakterystycznej ikony z logo An-
Poziom trudności Android, minęło niewiele ponad pół roku. droid oraz podpisem Market. Po uruchomie-
Choć z perspektywy polskiego użytkowni- niu programu górną część okna zajmuje lista
ka platforma ta może wydawać się niszową, ikon promowanych aplikacji oraz ich nazw.
należy pamiętać o tym, że do końca bieżące- Niżej do dyspozycji użytkowników są nastę-

N
aturalną konsekwencją ukazania go roku planowana jest sprzedaż aż 18 zupeł- pujące przyciski:
się na rynku nowego systemu ope- nie nowych modeli urządzeń Android! Do
racyjnego Android była koniecz- gry wkraczają kolejni operatorzy telefonii ko-
ność zapewnienia mu odpowiednich kana- mórkowej, obejmujący swym zasięgiem co-
łów dystrybucji oprogramowania. Tak jak raz nowsze rynki na całym świecie. Obecność
wiele osób dopatruje się analogii pomiędzy systemu w świadomości użytkowników na-
platformami Android oraz iPhone, trudno biera coraz silniejszych akcentów i systema-
było nie oczekiwać udostępnienia przez Go- tycznie staje się on coraz bardziej liczącym
ogle systemu sprzedaży podobnego do iPho- się graczem na rynku platform mobilnych.
ne App Store. Tak się rzeczywiście stało, kie- Faktu tego nie mogą zignorować producen-
dy to oficjalnie ogłoszone zostało uruchomie- ci i dystrybutorzy oprogramowania. Znajo-
nie Android Market. mość Android Market staje się zatem punk-
Z perspektywy użytkownika telefonu An- tem wyjścia do zaznaczenia swojej obecno-
droid Market jest programem, po którego ści na rynku.
uruchomieniu możliwe jest odnalezienie W chwili powstawania tego artykułu An-
wybranej aplikacji, jej zakup, pobranie i in- droid Market zawiera prawie 6000 różnych
stalacja. aplikacji, z czego około tysiąc stanowią gry.
Dla producentów oprogramowania jest on Wraz z przewidywanym rozwojem rynku
jednak zdecydowanie czymś więcej – inter- liczba produktów wzrastać będzie lawino-
fejsem, za pośrednictwem którego dotrzeć wo. Wiąże się to nie tylko ze wzrostem listy
można do wielkiej liczby odbiorców. Zgod- potencjalnych użytkowników, ale i krysta-
nie z zapowiedziami twórców, Android Mar- lizowaniem się grona dużych graczy wśród
ket ma różnić się od podobnych rozwiązań ła- producentów oprogramowania dla systemu
twością procesu publikacji oraz przejrzysto- Android. W początkowym okresie – przy- Rysunek 1. Główny ekran klienta Android
ścią działania. najmniej teoretycznie – szanse są wyrów- Market

90 SDJ Extra 34 Biblia


Android Market bliżej dewelopera

• Applications; Po otwarciu dowolnej kategorii w trybie kanie) wyświetlony zostaje jej krótki opis
• Games; przeglądania aplikacji mamy możliwość wy- oraz informacje dodatkowe, takie jak licz-
• Search; świetlenia dwóch widoków: ba i średnia wartość ocen, numer wersji,
• My Downloads. rozmiar oraz liczba ściągnięć, przy czym
• By popularity – aplikacje posortowane są nie jest podawana dokładna wartość, a je-
Wybranie pierwszych dwóch pozycji skut- według popularności: od największej do dynie przedział liczbowy dający ogólne wy-
kuje wyświetleniem pełnej listy podkate- najmniejszej liczby ściągnięć; obrażenie o popularności danego produk-
gorii oprogramowania, a także opcji All ap- • By date – na szczycie listy wyświetlane tu. Dla najbardziej rozchwytywanych apli-
plications, za pomocą której możliwy jest są najnowsze produkty. kacji przewidziana jest etykieta >250,000
dostęp do całości listy w dziale, bez po- downloads.
działu na kategorie. Więcej na temat kate- W dodatkowych opcjach (po wciśnięciu Poniżej opisu widoczne są najnowsze ko-
gorii aplikacji na Android Market dowiesz przycisku Menu) mamy również możli- mentarze użytkowników, możliwe jest też
się w dalszej części artykułu, poświęco- wość zmiany widoku poprzez wyświetle- wyświetlenie ich pełnej listy. Końcową sek-
nej publikacji programów. Przycisk Search nie jedynie płatnych lub darmowych pro- cję ekranu opisowego stanowią informa-
umożliwia wprowadzenie nazwy poszu- gramów, bądź też obu tych grup naraz. Li- cje o twórcy, odnośnik do pełnej listy pro-
kiwanego produktu, natomiast My Down- sta płatnych programów dostępna jest je- duktów danego dewelopera, adres strony
loads uruchamia listę wszystkich progra- dynie dla użytkowników w krajach, w któ- WWW oraz przycisk umożliwiający na-
mów zainstalowanych dotychczas na da- rych Android Market umożliwia sprze- tychmiastowe wysłanie do niego wiadomo-
nym urządzeniu. Jest to bardzo przydatne daż. Użytkownicy z Polski w poszukiwa- ści e-mail. Dodano również opcję Flag as in-
narzędzie, ponieważ przy nazwie każdego niu płatnych programów muszą jak na ra- appropriate, po której wybraniu możemy
z produktów widoczny jest również jego zie skorzystać z konkurencyjnych plat- zgłosić naruszenie przez dewelopera zasad
bieżący status. Najczęściej zaobserwujemy form dystrybucji. dystrybucji.
jeden z dwóch stanów: Installed bądź też Po wybraniu określonej aplikacji w któ- W dolnej części ekranu dostępny jest
Update Available, co oznacza, że na serwe- rymkolwiek z trybów (przeglądanie we- przycisk Install. Po jego wciśnięciu ukaże
rze pojawiała się nowsza wersja programu dług kategorii, zaznaczenie jednej z promo- się lista uprawnień, z których korzysta apli-
i możliwe jest jej ściągnięcie. wanych aplikacji albo bezpośrednie wyszu- kacja. Przykładowo, dla programu wyko-

Rysunek 2. Widok listy aplikacji kategorii Rysunek 3. Prezentacja listy wyników Rysunek 4. Informacje szczegółowe dla
Entertainment posortowanych według daty wyszukiwania wybranej aplikacji

Kamienie milowe rozwoju Android Market


• 23.10.2008 – oficjalne uruchomienie Android Market z około 50 darmowymi aplikacjami do zainstalowania;
• 28.11.2008 – po miesiącu działalności przedstawiono pierwsze statystyki dla rynku. Najpopularniejszą aplikacją okazała się gra Pac-Man fir-
my Namco, z ponad 250.000 instalacjami;
• 19.01.2009 – zapowiedź uruchomienia darmowej wersji Android Market w krajach europejskich, w tym w Polsce, co w niedługim czasie sta-
je się faktem;
• Luty 2009 – oficjalne zakończenie okresu Beta dla platformy. Nowa jej wersja wspiera aktualizację wersji oprogramowania;
• 13.02.2009 – uruchomiono sprzedaż aplikacji na rynkach amerykańskim i brytyjskim;
• Marzec 2009 – operator T-Mobile ogłosił kolejne statystyki, według których przeciętny użytkownik telefonu G1 zainstalował 40 aplikacji;
• 06.05.2009 – rozwinięcie listy krajów, gdzie dostępny jest darmowy Android Market, oraz dodanie wsparcia dla nowych języków, w tym ję-
zyka polskiego. Wprowadzono również konieczność określania przez programistów wspieranej przez aplikacje wersji SDK.

www.sdjournal.org 91
Programowanie Android

rzystującego ciągłe połączenie z siecią wy- Rejestracja konta dewelopera cji programu na rynku Android Market.
świetlony zostanie komunikat: This applica- Proces produkcji naszej aplikacji dobiegł wła- Wejście do tej części serwisu wymaga już
tion has access to the following: Network com- śnie końca. Programista, dumny ze swoje- zalogowania. W tym celu potrzebne jest
munication – full Internet access. Po zaak- go dzieła, ma teraz chwilę zasłużonego od- stworzenie konta użytkownika Google.
ceptowaniu uprawnień wymaganych przez poczynku (tak, tak, po prostu wpada w wir Wbrew pozorom nie jest ono jednoznacz-
program uruchomione jest jego pobiera- pracy nad kolejnym programem). Jeżeli ce- ne ze stworzeniem konta poczty Gmail,
nie, którego postęp widoczny jest w syste- lem przyświecającym stworzeniu aplikacji choć taki wybór jest najwygodniejszy i po-
mowym pasku notyfikacji. Gdy zapisywa- było udostępnienie jej szerszej publiczności, lecany przez autora artykułu.
nie zostanie ukończone, następuje powrót nadszedł czas na publikację i sprzedaż pro- Po pierwszym zalogowaniu się do dewelo-
do listy aplikacji, gdzie obok nazwy pobra- gramu. perskiej części serwisu Android Market, nale-
nego programu ustawiony zostaje status In- Publikacja aplikacji za pośrednictwem ży wprowadzić podstawowe informacje o na-
stalled. Po ponownym wybraniu zainstalo- Android Market nie jest zadaniem trud- szym profilu. Są to:
wanej już aplikacji możliwa jest już jej oce- nym, w wielu przypadkach zajmuje się
na, poprzez nadanie odpowiedniej liczby tym sam programista. Spora część czyn- • nazwa dewelopera – informację, którą
gwiazdek. ności związanych z udostępnieniem apli- tu wprowadzimy, użytkownicy końco-
Interfejs użytkownika Android Market kacji wykonywana jest tylko raz i wynika wi zobaczą pod nazwą aplikacji widocz-
jest w zasadzie tak prosty jak to tylko moż- bezpośrednio z rejestracji konta użytkow- ną na liście programów. Powinna to być
liwe dla aplikacji, której zadaniem jest wy- nika. Publikacja każdej kolejnej aplikacji dobrze przemyślana nazwa, ponieważ
szukiwanie i instalacja oprogramowania. staje się procesem trywialnym, niemal au- nie ma możliwości cofnięcia wybranych
Według wielu opinii, z prostotą związa- tomatycznym. kroków rejestracji. Dane możemy zmie-
ne są też znaczne ograniczenia w funkcjo- Aby mieć możliwość publikacji programu nić dopiero w trybie edycji działającego
nalności klienta. Z pewnością bardzo od- na Android Market, konieczna jest najpierw konta;
czuwalny jest brak możliwości publikacji rejestracja konta użytkownika. W uproszcze- • kontaktowy adres e-mail – standardo-
przez deweloperów zrzutów ekranu dzia- niu, zgodnie z instrukcją Google, składają się wo wyświetlony zostaje adres, za po-
łających aplikacji, co ułatwiałoby podjęcie na nią następujące kroki: mocą którego logujemy się do nasze-
decyzji użytkownikom, zwłaszcza przed go konta, możliwe jest podanie innego
pobraniem płatnych programów. Na pew- • rejestracja konta użytkownika; adresu, ale nie jest to polecane rozwią-
no nie zaszkodziłoby poprawienie możli- • uiszczenie opłaty rejestracyjnej; zanie;
wości systemu w zakresie prezentacji listy • wyrażenie zgody na warunki umowy • adres strony internetowej dewelopera;
aplikacji, choćby przez rozwinięcie kryte- Android Market Developer Distribution • numer telefonu kontaktowego.
riów ich filtrowania. Należy jednak pamię- Agreement.
tać, że Android Market, jak i cała platfor- Podczas rejestracji możemy również wyra-
ma, jest systemem bardzo młodym i podle- Punktem wyjścia jest oczywiście oficjal- zić zgodę na otrzymywanie informacji do-
gającym ciągłemu procesowi rozwoju. Mo- na strona serwisu http://www.android.com/ tyczących produkcji i publikacji programów
żemy zatem liczyć na pojawianie się z cza- market/. Uwagę zwraca przede wszystkim na platformę Android. Jeżeli chcemy być na
sem kolejnych poprawek mających na celu lista reklamowanych aplikacji, ale to nie bieżąco z rozwojem systemu, zdecydowanie
usprawnienie nawigacji. Zanim to nastąpi, one są naszym głównym punktem zain- powinniśmy zaznaczyć tę opcję. Zatwier-
możemy skorzystać na przykład z interne- teresowania. Pod niepozornym linkiem dzając wszystkie powyższe informacje, na-
towych klientów Android Market, pełnią- Learn more kryje się możliwość publika- leży pamiętać, że proces rejestracji nie daje
cych funkcję przeglądarek aplikacji. możliwości powrotu i bezpośredniej mody-
fikacji danych.
Kolejna strona wiąże się z uiszczeniem
opłaty rejestracyjnej w wysokości 25$. Jedy-
ną możliwą (jak dotąd) formą płatności jest
płatność kartą kredytową za pośrednictwem
systemu Google Checkout. Jeżeli nie prowa-
dziliśmy wcześniej transakcji w tym trybie,
konieczne jest dodanie informacji finanso-
wych do naszego konta Google. Operacja
ta jest jednorazowa dla całego konta, a więc
dzięki wprowadzonym przy tej okazji danym
będziemy mogli korzystać z Google Checko-
ut również w celach niezwiązanych z Andro-
id Market.
Wymagane są informacje standardowo
podawane przy transakcjach finansowych,
takie jak numer i data ważności karty, na-
zwisko i adres jej posiadacza. Możliwe też
jest podanie adresu korespondencyjnego, je-
żeli różni się od danych posiadacza karty. Z
kontem Google Checkout skojarzyć też nale-
Rysunek 6. Informacja o wszystkich ży adres e-mail, standardowo polecane jest
Rysunek 5. Lista wymaganych uprawnień zainstalowanych aplikacjach przedstawiona jest wprowadzenie tego samego adresu, którego
wyświetlona przed instalacją w dziale My downloads użyliśmy w początkowej fazie rejestracji do

92 SDJ Extra 34 Biblia


Android Market bliżej dewelopera

kont Google oraz Android Market. Tym ra- mocą certyfikatu, którego klucz prywatny w chwili instalacji programu na urządze-
zem również wymagana jest od nas akcepta- znajduje się w rękach twórcy. Odstępstwa od niu docelowym.
cja umowy (wszak jest to niezależna usługa tej reguły nie stanowią aplikacje uruchamia- Jeżeli termin ważności upłynie już po in-
Google). Wkrótce po zatwierdzeniu wszyst- ne na urządzeniu w procesie produkcji. stalacji, fakt ten nie wpływa na poprawne
kich wprowadzonych przez nas danych, na W tym przypadku podpisywane są one funkcjonowanie programu.
podany adres e-mail otrzymamy wiadomość automatycznie w trybie debug, który jed- Zalecane jest stosowanie tego samego cer-
z potwierdzeniem dokonania zamówienia nocześnie uniemożliwia publikację na An- tyfikatu dla wszystkich aplikacji publikowa-
na produkt o nazwie Android – Developer Re- droid Market. nych przez danego twórcę. Powodów takiego
gistration Fee. Dozwolone, a nawet powszechnie stoso- podejścia jest kilka:
Do finalizacji rejestracji konta deweloper- wane jest samodzielne generowanie klucza
skiego potrzebna jest jeszcze akceptacja umo- po stronie deweloperów, jako że nadrzęd- • możliwość łatwej aktualizacji oprogra-
wy Android Market Developer Distribution nym celem jego zastosowania jest zapewnie- mowania przez użytkownika końcowe-
Agreement, której tekst ukaże się przed pu- nie jednoznacznej identyfikacji twórcy opro- go w przypadku potwierdzenia tożsa-
blikacją pierwszego programu. Treść umowy gramowania. mości twórcy;
można też przeczytać w dowolnej chwili po- Ze względu na fakt, że każdy certyfikat • możliwość uruchomienia kilku aplika-
przez kliknięcie odpowiedniego odnośnika w posiada swój termin ważności, informa- cji podpisanych tym samym certyfika-
głównym panelu konta. cja ta poddawana jest sprawdzeniu tylko tem w ramach jednego procesu (w takiej
Bezpośrednio po otrzymaniu wiadomo-
ści z potwierdzeniem płatności możliwe
jest wysłanie aplikacji na serwer. Nie jest
to jednak równoznaczne z jej publikacją – z
tym musimy poczekać do momentu, kiedy
transakcja zostanie przetworzona po stro-
nie Google. Z reguły nie trwa to więcej niż
kilka godzin.

O czym należy
pamiętać przed publikacją
Po zakończeniu procesu rejestracji oraz po
każdym kolejnym logowaniu do Android
Market, otwarta zostanie strona panelu ad-
ministracyjnego konta dewelopera. Tak jak
w przypadku poprzednich widoków, rów-
nież ten dział serwisu utrzymany jest w
minimalistycznym stylu Google, a infor-
macje tu zawarte ograniczone są tylko do
najistotniejszych danych. Większość miej-
sca zajmuje sekcja Your Android Market Li-
stings, w której zawarty jest zestaw najważ-
niejszych informacji dotyczących udostęp-
nianych przez nas programów. Informa- Rysunek 7. Wprowadzanie podstawowych informacji o deweloperze podczas rejestracji konta. Ekran
cje te wyświetlane są w następujących ko- edycji danych konta ma praktycznie tę samą zawartość
lumnach:

• nazwa, numer wersji oraz kategoria, do


której należy aplikacja;
• średnia ocena aplikacji wśród użytkow-
ników (wyrażona w 5-gwiazdkowej ska-
li) i liczba głosów;
• całkowita liczba ściągnięć i instalacji;
• cena, w jakiej oferujemy program, bądź
informacja FREE, jeśli jest to aplikacja
darmowa;
• informacja o tym, czy aplikacja została
upubliczniona.

Początkowo lista aplikacji jest oczywiście pu-


sta. Do wysłania programu na serwer zachę-
ca przycisk Upload Application. Zanim jed-
nak przejdziemy do wysłania aplikacji, nale-
ży ją podpisać.
Zgodnie z założeniami systemu Android, Rysunek 8. Główny panel zarządzania kontem dewelopera. Uwaga: widoczna aplikacja Global
wszystkie instalowane (a zatem i publikowa- Factbook European Edition jest produktem firmy Gamelion Studios, który został opublikowany w
ne) aplikacje muszą zostać podpisane za po- wersji darmowej już w początkowym okresie działania Android Market

www.sdjournal.org 93
Programowanie Android

sytuacji kilka współdziałających ze sobą Obie informacje określa się w pliku Android- Jako programista należy być świadomym
aplikacji możemy traktować jako jeden Manifest.xml. Udzielenie pierwszej z nich nie zagadnienia wstecznej kompatybilności
program); jest wymagane przez system, chociaż każdy oprogramowania. Zalecane jest więc za-
• współdzielenie kodu i danych przez apli- zdaje sobie sprawę z wagi nadawania nume- pewnienie działania aplikacji zbudowanej
kacje podpisane określonymi certyfika- rów wersji programom i trudno jest wyobra- na starszym SDK, na nowszych urządze-
tami. zić sobie pominięcie tej informacji. Koniecz- niach.
ne jest natomiast sprecyzowanie drugiego z Powyżej wymienionych zostało kilka naj-
Dział Dev Guide strony wymienionych parametrów w celu zapew- ważniejszych zagadnień, z którymi trzeba się
http://developer.android.com/ zawiera bardzo nienia kompatybilności oprogramowania z liczyć przed publikacją programu na Andro-
dobrze przygotowaną sekcję wyczerpują- docelowymi urządzeniami, na których zo- id Market. Po bardziej szczegółowe informa-
cą tematykę podpisywania aplikacji. Dowie- stanie zainstalowane. Do pliku manifest na- cje odsyłam na oficjalną stronę dla dewelope-
my się z niej między innymi, że do wygene- leży dodać zatem następującą linię: rów aplikacji Android, przyjrzyjmy się nato-
rowania klucza i podpisania aplikacji wyko- miast właściwemu procesowi publikacji pro-
rzystać możemy darmowe narzędzia Keytool <uses-sdk android:minSdkVersion=”1”></ gramu.
oraz Jarsigner. uses-sdk>
Kolejną rzeczą, o której należy pamiętać Publikacja aplikacji
przed publikacją aplikacji, jest jej poprawne Możliwe przy tym jest nadanie atrybutowi na Android Market
wersjonowanie. Jak się okazuje, termin ten android:minSdkVersion trzech wartości, w Najważniejszą informacją, jakiej musimy
dotyczy w rzeczywistości dwóch aspektów: zależności od wspieranego SDK: udzielić po wciśnięciu przycisku Upload
Application jest oczywiście lokalizacja pli-
• wersji aplikacji; • "1" dla SDK 1.0; ku apk, który chcemy umieścić na serwe-
• wersji Android SDK, którą wspiera na- • "2" dla SDK 1.1; rze. Po jego wskazaniu (i wysłaniu) zosta-
sza aplikacja. • "3" dla SDK 1.5. ją automatycznie sprawdzone parametry
pliku AndroidManifest.xml. Jeżeli znajdu-
ją się w nim jakieś nieprawidłowe wpisy
bądź brakuje jakichś niezbędnych informa-
cji, zostaniemy o tym powiadomieni i po-
proszeni o ponowne wysłanie poprawione-
go pliku programu. Jednym ze sprawdza-
nych parametrów jest wspomniany wcze-
śniej numer wspieranego SDK. W związku
z tym, jeżeli z jakichś przyczyn próbujesz
wysłać ponownie aplikację, która została
już przyjęta do publikacji przed wprowa-
dzeniem nowej wersji SDK, może Cię spo-
tkać niemiła niespodzianka, jako że wcze-
śniej podanie tej informacji nie było ko-
nieczne.
Jeżeli aplikacja korzysta z jakichś chronio-
nych zasobów urządzenia, plik manifest zosta-
nie również sprawdzony pod kątem informa-
cji o odpowiednich uprawnieniach. W przy-
padku ich braku będziemy musieli uzupeł-
nić również te dane. Przykładowo, aplikacja
wykorzystująca funkcjonalność GPS powin-
na zawierać następujący wpis w pliku Andro-
idManifest.xml:

<uses-permission android:name="android.
permission.ACCESS_FINE_LOCATION"></
Rysunek 9. W ramach jednego ekranu realizowane jest wysłanie aplikacji na serwer, a także uses-permission>
wprowadzenie jej opisu i parametrów publikacji
Po udanym załadowaniu pliku na stro-
nę powinniśmy wprowadzić jeszcze in-
formacje dodatkowe, od których w dużej
mierze uzależniona jest widoczność apli-
kacji dla zamierzonej grupy odbiorców.
Możemy zdecydować się na dowolną licz-
bę języków, w których opiszemy nasz pro-
gram, spośród dostępnej listy lokalizacyj-
nej. W czasie pisania artykułu poza stan-
dardowym zestawem EFIGS (język angiel-
ski, francuski, włoski, niemiecki i hiszpań-
Rysunek 10. Wybór lokacji, w których program będzie dostępny dla odbiorców ski) dostępne są również języki: polski, ho-

94 SDJ Extra 34 Biblia


Android Market bliżej dewelopera

lenderski, czeski, portugalski, tajwański i na świecie (służy do tego opcja All Current Android Market, powinniśmy koniecznie
japoński. and Future Countries), możemy wybrać do- zapoznać się z kilkoma zasadami związa-
W każdym z wybranych języków nale- wolne kraje z dostępnej listy. Należy jednak nymi z finansowymi aspektami tego kana-
ży określić nazwę aplikacji widoczną przez pamiętać o tym, że zawartość listy zmienia łu dystrybucji. Podstawową dla sprzedają-
użytkowników końcowych oraz jej opis. się dość dynamicznie wraz ze zwiększaniem cego informacją będzie na pewno tzw. pay-
Niestety, liczba znaków dla obu pól ograni- się zasięgu Android Market na świecie. out, czyli rzeczywisty odsetek ceny aplika-
czona została dość znacznie – maksymalna Na tym kończy się zestaw informacji wy- cji, jaki w wyniku sprzedaży trafia do dewe-
długość nazwy aplikacji wynosi 30 znaków, maganych przy wysyłaniu aplikacji na ser- lopera. Odsetek ten jest stały i wynosi 70%,
a opis nie może być dłuższy niż 325 zna- wer. Możliwa jest jeszcze modyfikacja da- podobnie jak w przypadku konkurencyjne-
ków. Na załączonej ilustracji widać, jakie nych kontaktowych, o ile dla danej aplika- go rynku dla urządzeń iPhone – App Store.
wyzwanie stanowić może taka długość tek- cji różnią się od informacji podanych przy Informację tę należy brać pod uwagę w mo-
stu. Poza opisem czeka nas określenie typu tworzeniu konta. Po zaakceptowaniu wa- mencie określania ceny naszego programu.
i kategorii aplikacji, informacji, na podsta- runków umowy dystrybucyjnej możemy Pozostała część wpływu ze sprzedaży trafia
wie których nasz program zostanie zakwa- wcisnąć przycisk Publish bądź skorzystać z prawdopodobnie do operatorów sieci, choć
lifikowany do odpowiedniej grupy tema- opcji Save. informacja ta nie została oficjalnie potwier-
tycznej. Niezależnie od wybranej opcji, nasz pro- dzona przez Google. Cena z kolei zmieścić
Jak widać na ilustracji, kolejną ważną, o gram ukaże się na liście aplikacji widocznej się musi w narzuconych przez system wi-
ile nie najważniejszą informacją towarzyszą- w panelu głównym konta dewelopera. Je- dełkach. Jako że na razie dostępne są dwie
cą naszej aplikacji jest jej cena. Podkreślić na- żeli jednak skorzystaliśmy z przycisku Sa- waluty dla przetwarzania transakcji, okre-
leży, że waluta, w której możemy wprowa- ve, obok nazwy aplikacji widoczny będzie ślono następujące limity cenowe:
dzić cenę naszej aplikacji, odpowiada walucie przypis Unpublished, co oznacza, że nie jest
określonej w szczegółowych danych naszego ona jeszcze widziana przez użytkowników • 0,99 – 200,00 USD;
konta Google Checkout. Stworzone dla po- końcowych. Status ten można w dowolnej • 0,50 – 100,00 GBP.
trzeb artykułu konto zostało zarejestrowane chwili zmienić.
w Wielkiej Brytanii, stąd kod dostępnej walu- Po ponownym wybraniu aplikacji z listy Zabezpieczono się w ten sposób przed usta-
ty to GBP. Cena dla użytkowników Android widocznej w panelu głównym możliwe jest laniem przez sprzedających absurdalnych
Market spoza wysp brytyjskich zostanie prze- skorzystanie z jednej z dodatkowych opcji: cen aplikacji, co miało już miejsce na wymie-
liczona na odpowiednią jednostkę zgodnie z nionym App Store (jak w znanym przypad-
bieżącym kursem walut. Niewątpliwie jest • upload upgrade – udostępnienie aktuali- ku zupełnie bezużytecznej aplikacji I am rich
to dowodem niedojrzałości systemu, zwłasz- zacji programu – użytkownicy zauważą za 999,99USD). Z kolei minimum cenowe
cza z perspektywy deweloperów, którzy jak powiadomienie Update available w dzia- opiera się na założeniu, że niższa cena progra-
na razie muszą zdać się jedynie na zgrubną le My Downloads; mu nie ma uzasadnienia finansowego, i lepiej
kalkulację zysków generowanych przez apli- • unpublish – wyłączenie widoczności przeznaczyć go do dystrybucji bezpłatnej.
kację sprzedawaną jednocześnie na różnych aplikacji na rynku. Mechanizm przetwarzania płatności jest
rynkach. stosunkowo prosty i opiera się w całości
Pozostałe parametry publikacji programu Co powinieneś na systemie Google Checkout. Wszystkim
związane są z jego dostępnością na określo- wiedzieć o sprzedaży? transakcjom towarzyszą odpowiednie ko-
nych rynkach. Jeżeli nie interesuje nas dotar- Jeżeli poważnie podchodzimy do zagadnie- munikaty e-mail wysyłane do zaintereso-
cie do wszystkich użytkowników systemu nia sprzedaży aplikacji za pośrednictwem wanych stron. Dzieje się tak w przypadku

Android Market Developer Distribution Agreement


• Akceptując umowę deweloperską, zgadzamy się na wszystkie zawarte w niej warunki, dlatego konieczne jest zapoznanie się z ca-
łą jej zawartością. Warto zwrócić uwagę na to, że umowa zabrania między innymi publikacji programów, które stanowią konku-
rencję dla Android Market, jako jedynego oficjalnego źródła dystrybucji aplikacji Android. Obecnie niedozwolone jest również
rozpowszechnianie aplikacji umożliwiających tzw. tethering, czyli korzystanie z telefonu jako modemu 3G/GPRS dla komputera
osobistego.
• Android Market, tak jak i sam system, nieustannie się rozwija. Niektóre zmiany wymuszają wprowadzanie nowych bądź rozwinięcie istnieją-
cych punktów umowy Android Market Developer Distribution Agreement. Bardzo prawdopodobne jest zatem, że po zalogowaniu do kon-
ta, z którego regularnie korzystamy, zostaniemy poproszeni o zaakceptowanie nowych warunków umowy. Bez akceptacji aktualnej umowy
nie jest możliwe korzystanie z dalszej części serwisu.
• W chwili pisania tego artykułu, możliwy jest wybór jednego z kilku języków umowy, niestety brakuje wciąż wersji polskiej.

Kto kupi, a kto zainstaluje darmową wersję?


Urządzenia wyposażone w system Android oraz system dystrybucji aplikacji Android Market pojawiają się na coraz to nowych rynkach. Jest to
jednak proces stopniowy i stosunkowo długotrwały, dlatego musimy liczyć się z tym, że początkowo trafimy do użytkowników jedynie z pew-
nej ograniczonej liczby lokacji.

• W czasie gdy przygotowywany jest ten numer magazynu, bezpłatne aplikacje dostępne są dla większości użytkowników z Unii Europej-
skiej, w tym z Polski, a także z Norwegii, Szwajcarii, USA, Australii, Kanady, Hong Kongu, Japonii, Singapuru i Tajwanu.
• Lista krajów, w których możliwa jest dystrybucja płatnych aplikacji ograniczona jest obecnie do USA, Wielkiej Brytanii, Australii, Austrii,
Włoch, Francji, Niemiec Hiszpanii i Holandii.
• Lista krajów, w których możliwa jest rejestracja deweloperów chcących sprzedawać swoje aplikacje jest jeszcze krótsza od powyższej (nie
zawiera Włoch). Twórcy oprogramowania z Polski muszą zatem zdobyć się na dodatkową cierpliwość.

www.sdjournal.org 95
Programowanie Android

zarówno zakupu aplikacji, jak i zwrotu po- nego mechanizmu. Służy do tego przycisk liwia wgląd w szczegóły wszystkich transak-
niesionych kosztów. W rezultacie zakupu Refund Some Money, po którego wybraniu cji składających się na daną kwotę, wraz z in-
odpowiednia kwota pobierana jest z karty należy określić numer zamówienia, które- formacją o zamówieniach, z którymi są one
płatnika i kierowana na konto, którego da- go dotyczy zwrot pieniędzy, powód oraz związane.
ne zostały określone w naszym profilu, w wysokość zwrotu. Istnieją dwa rodzaje raportów, jakie mo-
przeciągu 2 dni roboczych. Google zastrze- Kupujący zostanie automatycznie po- żemy wygenerować z konta Google Checko-
ga sobie przy tym dodatkowe 3 dni, argu- wiadomiony o zwrocie poprzez e-mail. Po- ut. Są to:
mentując to różnymi trybami przetwarza- lecenia zwrotu nie można anulować, jako
nia transakcji przez banki. Przychody wy- że jest ono wykonywane natychmiastowo. • Payout details – widok wszystkich wy-
generowane przez sprzedawane aplikacje Jeżeli wysokość zwrotu przekracza bilans płat, jakich dokonaliśmy z naszego kon-
widoczne są w zakładce Payouts konta Go- konta deweloperskiego, odpowiednia kwo- ta w ciągu danego dnia;
ogle Checkout. Z pewnością cieszy fakt, że ta zostanie pobrana wprost z naszego kon- • Transaction details – szczegółowy widok
nie istnieje praktycznie żadne minimum ta bankowego. wszystkich płatnych zamówień zrealizo-
bilansu konta, aby możliwe było pobranie Konto Google Checkout oferuje bardzo wanych dla naszego konta w ciągu dane-
zarobionych pieniędzy na konto bankowe prosty system przeglądania i raportowania go dnia.
– symboliczna kwota minimalna wynosi wszystkich transakcji finansowych. W za-
1,00 USD. kładce Payouts mamy dostęp do następują- Aby wygenerować którykolwiek z nich, na-
Spore kontrowersje wśród deweloperów cych pozycji: leży wybrać zakładkę Payouts i w sekcji Do-
wzbudza polityka zwrotów aplikacji, na wnload data to spreadsheet (.csv) wybrać inte-
którą musimy się zgodzić, publikując swo- • Starting Balance – należności będące re- resującą nas nazwę raportu. Plik raportu dla
je programy na Android Market. Użytkow- zultatem dotychczasowej sprzedaży; określonej przez nas daty zostanie zapisany
nikom przysługuje możliwość anulowania • Purchases – przychody z bieżącej sprze- na dysku. Historia transakcji, dla których
zakupu w czasie jednej doby od momentu daży, które możemy pobrać; możliwe jest stworzenie raportów, obejmu-
pobrania płatnej aplikacji (poprzez jej od- • Other Activity – obsługa nietypowych je okres 2 miesięcy.
instalowanie). Z tego powodu rzeczywisty transakcji, w tym zwrotów; Pomocnym narzędziem kontroli dystry-
okres oczekiwania na gotówkę przez pro- • Payout – kwota przeznaczona do prze- bucji aplikacji jest też główny panel konta
ducenta wydłuża się o 24 godziny. Jeże- lewu na konto bankowe (aktualizowana dewelopera, który wprawdzie nie daje do-
li w tym czasie kupujący nie zażąda zwro- do 24 godzin po wypłacie środków); stępu informacji finansowych, ale stano-
tu, dopiero wtedy pobierana jest z jego kar- • Ending Balance – bilans końcowy po wi dobre źródło wiedzy o zainteresowaniu
ty gotówka. przetworzeniu wypłaty środków. aplikacją ze strony użytkowników. W opisa-
Konto Google Checkout umożliwia rów- nej wcześniej sekcji Your Android Market Li-
nież udzielanie przez dewelopera zwrotów Wybranie jednej z wymienionych opcji poza stings widoczne jest porównanie liczby ścią-
pieniędzy, niezależnie od wyżej wymienio- bilansem początkowym i końcowym umoż- gnięć i instalacji. Różnica tych dwóch war-

Uzupełnienie Android Market


Android Market, choć stanowi oficjalną platformę sprzedaży aplikacji dla systemu Android, nie jest jedynym miejscem, w którym możemy pro-
wadzić dystrybucję naszego oprogramowania czy też śledzić dokonania konkurencji. W ramach kampanii dystrybucyjnej prowadzonej dla na-
szego oprogramowania powinniśmy dobrze zapoznać się z innymi serwisami. Kilka najciekawszych z nich wymieniam poniżej.

• Dyskusji nie podlegają raczej korzyści wynikające z umieszczenia swojej aplikacji w serwisach poświęconych sprzedaży innych niż An-
droid Market. Bardzo popularne wśród użytkowników systemu są Handango (http://www.handango.com) oraz Mobihand (http://
www.mobihand.com). Zaskakującym może być fakt, że ceny tych samych aplikacji w ramach tych dwóch serwisów bardzo często się różnią.
Wynikać to może z nieco innych grup odbiorców, do jakich są one adresowane. Zachęcam również do rozważenia wysłania swojego pro-
gramu także do innych serwisów, jak np. specjalizujący się dotychczas w aplikacjach Java GetJar (http://www.getjar.com).
• Warto zaznaczyć swoją obecność i zasygnalizować obecność na rynku nowej aplikacji na forach internetowych poświęconych tematyce An-
droid. Do najbardziej znanych należą Talk Android (http://www.talkandroid.com) oraz Android Forums (http://androidforums.com/).
• Bardzo popularną i szczególnie godną polecenia stroną oferującą wgląd w aktualną listę dostępnych aplikacji jest Cyrket (http://
www.cyrket.com/). Strona ta jest klientem Android Market i służy do wygodnego przeglądania, a nie dystrybucji, oprogramowania. Oferuje
przy tym wiele możliwości prezentowania wyników, sortowania aplikacji według różnych kryteriów oraz – co najważniejsze i niedostępne
na stronie Android Market – pozwala na czytanie wszystkich komentarzy użytkowników.
• Droideo (http://www.droideo.com/) – serwis przeznaczony do publikacji wszelkich nagrań wideo dotyczących zarówno urządzeń, jak i apli-
kacji adresowanych dla systemu Android. Jest to zarówno ogromne źródło informacji o rynkowych nowościach, jak i świetne medium do
publikacji materiałów promocyjnych dla naszych programów.
To jedynie wybrane przykłady spośród ogromnej liczby stron internetowych poświęconych tematyce Android.

Kategorie aplikacji w Android Market


• Możliwy jest wybór jednego z dwóch typów aplikacji publikowanych za pośrednictwem Android Market: gry lub aplikacje. Każdy z nich
dzieli się na szereg kategorii.
• W grupie aplikacji zawartych jest 14 kategorii: Communication, Demo, Entertainment, Finance, Lifestyle, Multimedia, News & Weather, Producti-
vity, Reference, Shopping, Social, Software Libraries, Tools, Travel.
• Typ gier podzielony został dotychczas na następujące kategorie: Arcade & Action, Brain & Puzzle, Cards & Casino, Casual.
• System nie umożliwia niestety zaznaczenia kilku kategorii dla jednej aplikacji, dlatego wybór musi być dobrze przemyślany i zbieżny z ob-
raną przez nas strategią marketingową. Pewnego rodzaju ucieczką od tego ograniczenia mogłaby się wydawać kilkukrotna publikacja tej
samej aplikacji pod różnymi kategoriami, jednak takie rozwiązanie wprowadza więcej zamieszania wśród osób poszukujących aplikacji, niż
rzeczywistych korzyści dla dewelopera.

96 SDJ Extra 34 Biblia


Android Market bliżej dewelopera

tości stanowi o liczbie przypadków odinsta- kim ikony takich programów wyekspono- związanych ze sprzedażą oprogramowania
lowania programu. Wskaźnik ten jest dobrą wane są ponad standardową listą aplika- na Android Market.
reprezentacją poziomu satysfakcji użytkow- cji widzianych przez użytkowników koń- Największym i najbardziej aktywnym
ników z produktu, co ma szczególne znacze- cowych. źródłem informacji o wszystkich aspek-
nie w przypadku aplikacji płatnych, dla któ- Dodatkowo, informacja na temat wy- tach platformy Android jest jednak sekcja
rych usunięcie programu w przeciągu do- branych produktów wyświetlona jest na Community, z której uzyskamy dostęp do
by od instalacji może być związane z żąda- oficjalnej stronie http://www.android.com/ tematycznych grup dyskusyjnych i forów
niem zwrotu. market (która nie służy do przeglądania za- internetowych.
Dość oczywistym parametrem repre- wartości platformy i nie zawiera listy pro-
zentującym powodzenie aplikacji na ryn- gramów), która ponadto umożliwia zapo- Podsumowanie
ku jest liczba gwiazdek przyznawanych znanie się ze zrzutami ekranów działają- Zbudowanie aplikacji jest uwieńczeniem pra-
przez użytkowników, których średnia war- cych aplikacji. cy zespołu deweloperskiego, ale to dopiero
tość widoczna jest w panelu dewelopera. Z początek wysiłków innych osób, których za-
pewnością źródłem bardziej szczegółowych Jak być na bieżąco? daniem jest jej skuteczna sprzedaż. Narzę-
informacji są komentarze pisane przez in- Nawet jeżeli proces produkcji twojej apli- dziem towarzyszącym platformie Android
dywidualnych odbiorców naszego oprogra- kacji jest w szczerym polu i nie masz jesz- właściwie od samego jej początku jest Andro-
mowania. Niestety, obecna forma Andro- cze sprecyzowanych planów dotyczących id Market – oficjalna platforma dystrybucji,
id Market nie umożliwia przeglądania ko- jej sprzedaży, warta rozważenia, pomimo której głównym założeniem jest powszech-
mentarzy z perspektywy właściciela konta. związanego z tym kosztu, jest rejestracja ność dostępu wśród użytkowników urzą-
Możliwość zapoznania się z opiniami zosta- konta dewelopera Android. Zarejestrowa- dzeń Android oraz łatwość instalacji. Pomi-
ła dana jedynie samym użytkownikom, któ- ni użytkownicy na bieżąco powiadamia- mo pewnych braków system wciąż rozwija
rzy mogą je przeczytać po uruchomieniu ni są o wszystkich zmianach wprowadza- się i rzeczywiście trafia do coraz większej licz-
Android Market zainstalowanego w tele- nych na Android Market. Wszystkie do- by odbiorców. Każdy z tych odbiorców może
fonie. Konieczność każdorazowego wyszu- tychczasowe informacje związane z po- być potencjalnym nabywcą naszego oprogra-
kiwania naszych produktów przez telefon szerzaniem rynku płatnych czy też dar- mowania.
w celu zapoznania się z reakcją użytkowni- mowych aplikacji, zanim trafiły do porta- Artykuł stanowi nie tylko przegląd
ków jest mało wygodnym rozwiązaniem, na li informacyjnych, najpierw wysyłane były funkcjonalności Android Market, ale
szczęście taką funkcjonalność oferują inne do zarejestrowanych użytkowników. Posia- przede wszystkim przybliża go do twórcy
strony internetowe. danie konta Android gwarantuje zatem in- oprogramowania jako niezbędne w pracy
Żaden system oceniania aplikacji nie za- formacje z pierwszej ręki, co jest niezmier- narzędzie umożliwiające łatwą dystrybu-
stąpi bezpośredniej wymiany informacji po- nie ważne, jeżeli chcemy mieć możliwość cję programów na rynku. Informacje tu
między twórcą a odbiorcą aplikacji. System dobrego rozpoznania rynku i przemyślanej przedstawione są wystarczające do samo-
Android Market na szczęście oferuje możli- dystrybucji aplikacji w późniejszym czasie. dzielnej publikacji aplikacji i stanowią so-
wość wysłania wiadomości e-mail przez użyt- Posiadanie konta dewelopera daje też moż- lidną podstawę do pogłębiania swojej wie-
kownika. Opcja taka wyświetlana jest pod na- liwość zakupu deweloperskiej wersji urzą- dzy na temat rynku oprogramowania An-
zwą aplikacji widzianej w programie Andro- dzenia z systemem Android, z którego ce- droid. Znajomość mechanizmów jego dzia-
id Market. chami zapoznać się można na stronie An- łania jest konieczna, aby zaistnieć w świa-
Deweloperzy, których aplikacja uzyskała droid Dev Phone 1: http://android.bright- domości odbiorców.
wysokie notowania w systemie gwiazdko- starcorp.com/ .
wym, mogą liczyć na to, że ich program do- Niezależnie od rejestracji konta, jako ofi-
stąpi zaszczytu umieszczenia na liście tzw. cjalne źródło informacji należy traktować
featured apps. Niestety nie są znane precy- stronę http://www.android.com/. To właśnie ADAM SKRZYSZEWSKI
zyjne kryteria zakwalifikowania aplikacji tutaj ukazały się ogłoszenia dotyczące kon- Producent w firmie Gamelion Studios. Zadaniem
do tego prestiżowego grona, bardzo możli- ferencji Google I/O Developer Conferen- Adama jest prowadzenie projektów związanych
we, że w pewnym stopniu jest to wynikiem ce czy też przygotowania nowego SDK An- z produkcją gier i aplikacji multimedialnych dla
subiektywnej decyzji Google. Aplikacja droid 1.5. Również z tej strony z łatwością platform mobilnych, w tym dla systemu Android.
zaliczona do grupy featured apps bardzo przeczytamy najświeższe wpisy na blogu, Kontakt z autorem: adam.skrzyszewski@game-
zyskuje na widoczności. Przede wszyst- czy to w wątkach typowo technicznych czy lion.com

W Sieci
• http://www.android.com/market/ – oficjalna strona platformy dystrybucyjnej Android Market;
• http://market.android.com/publish/Home – strona główna konta dewelopera;
• http://android-developers.blogspot.com/ – oficjalny blog deweloperski, z dedykowaną sekcją dla Android Market;
• http://developer.android.com/guide – przewodnik programisty, zawiera również sekcję poświęconą publikacji oprogramowania;
• http://www.talkandroid.com/ – portal poświęcony wyłącznie tematyce Android;
• http://androidcommunity.com/ – konkurencyjny serwis dla użytkowników Android;
• http://www.androidal.pl/ – dobrze poinformowany rodzimy serwis tematyczny;
• http://www.cyrket.com/ – przeglądarka zawartości Android Market;
• http://androidstats.com/ – podobna przeglądarka Android Market, oferująca garść ciekawych statystyk;
• http://www.handango.com – portal dystrybucyjny dla aplikacji mobilnych, również dedykowanych platformie Android;
• http://www.mobihand.com – konkurencyjna strona o podobnym przeznaczeniu;
• http://androidfeeder.com/ – miejsce promocji i wymiany informacji o aplikacjach dostępnych na Android Market;
• http://www.droideo.com/ – serwis z nagraniami wideo w tematyce Android.

www.sdjournal.org 97
Programowanie Android

Android na przykładzie:
geotagging zdjęć
Praktyczne wykorzystanie najciekawszych funkcji mobilnej
platformy firmy Google
Android to nowa, otwarta platforma mobilna od Google. W artykule
przedstawiono ciekawsze jej możliwości, na przykładzie aplikacji
umożliwiającej geotagging zdjęć wykonanych za pomocą telefonu
komórkowego opartego na tej platformie.

pouczające niż czytanie suchego opisu jego


Dowiesz się: Powinieneś wiedzieć: działania i implementacji. Kod jest udostęp-
• Jak stworzyć aplikację androidową oraz jej • Jak programować w języku Java; niony jako własność publiczna (public doma-
interfejs użytkownika; • Jak korzystać ze środowiska programistycz- in), więc nie ma żadnych ograniczeń na jego
• Jak skorzystać z kamery oraz GPS; nego Eclipse. wykorzystanie.
• Jak przechowywać dane, korzystając z syste-
mu plików oraz bazy danych; Nowy projekt
• Jak skorzystać z Google Maps we własnej Mając gotowe środowisko pracy (patrz ram-
aplikacji. ka Szybki start), można przystąpić do utwo-
rzenia nowego projektu. Konieczne jest po-
danie nazwy projektu, pakietu (org.sdjour-
glądanie zrobionych wcześniej zdjęć. Łącz- nal.galbum), nazwy aktywności (GAlbu-
nikiem dla tych modułów jest ekran starto- mActivity) oraz nazwy aplikacji, jaka ma
Poziom trudności wy pozwalający użytkownikowi wybrać, co być zaprezentowana użytkownikowi. Eclip-
chce zrobić. se automatycznie utworzy szkielet aplika-
Pierwszym krokiem będzie stworzenie cji (standardowe Hello World!), w którego
szkieletu aplikacji oraz jej ekranu starto- skład wchodzi:

J
ak wiadomo, nowych rzeczy najłatwiej wego. Przy okazji zademonstruję, jak ko-
i najprzyjemniej uczyć się na przykła- rzystać z zasobów dołączonych do aplikacji. • deskryptor aplikacji (AndroidMani-
dach. Ta idea przyświeca temu arty- Następnie pokażę, jak wykorzystać wbudo- fest.xml);
kułowi, który koncentruje się na pokaza- waną w telefon kamerę oraz zapisać na kar- • klasa GAlbumActivity, która jest punk-
niu kilku ciekawszych funkcji oferowanych cie pamięci zrobione zdjęcie. Kolejnym kro- tem wejścia dla aplikacji;
przez Androida oraz sposobie ich wykorzy- kiem będzie dodanie obsługi GPS do pobie- • katalog z zasobami dołączanymi do apli-
stania przy budowaniu aplikacji. Przykła- rania danych o lokalizacji oraz skojarzenie kacji, zawierający ikonę oraz pliki XML
dowa aplikacja, której stworzenie zaprezen- tych danych ze zrobionym zdjęciem. Za- definiujące zasoby tekstowe oraz układ
tuję w tym artykule, nie jest jakoś specjal- demonstruję, jak można w tym celu wy- elementów GUI.
nie rozbudowana ani skomplikowana. Jej korzystać bazę danych opartą na SQLite.
funkcjonalność można krótko podsumo- Na samym końcu pokażę, jak można wy- Zawartość wygenerowanej klasy
wać jednym zdaniem. Umożliwia ona ro- korzystać Google Maps do prezentacji ko- GAlbumActivity ogranicza się do defi-
bienie zdjęć i przypisanie im danych geo- lekcji zdjęć wykonanych przy pomocy tej nicji jednej tylko metody i jest przedsta-
graficznych (geotagging) oraz przeglądanie aplikacji. wiona na Listingu 1. Metoda onCreate()
tych zdjęć z poziomu mapy z wykorzysta- Zachęcam do zapoznania się z kodem źró- jest wywoływana przez system podczas
niem Google Maps. dłowym prezentowanej tu aplikacji, który tworzenia Activity, w przypadku na-
Wchodząc w szczegóły, z punktu widze- znajduje się na załączonej do pisma płycie. Ze szej klasy stanie się tak w odpowiedzi
nia użytkownika można wyróżnić 2 głów- względu na sporą ilość kodu oraz ograniczo- na kliknięcie przez użytkownika ikon-
ne moduły aplikacji. Pierwszy odpowia- ną ilość miejsca na łamach magazynu nie je- ki aplikacji (takie zachowanie jest zde-
da za wykonanie zdjęcia, pobranie pozycji stem w stanie przedstawić listingów dla każ- finiowane w AndroidManifest.xml, do
geograficznej, oraz zapisanie tego wszyst- dej z omawianych funkcji. Uważam tez, że czego wrócimy w dalszej części artyku-
kiego w pamięci nieulotnej telefonu. Dru- modyfikowanie i eksperymentowanie na go- łu). Wywołanie onCreate(Bundle) kla-
gi moduł pozwala użytkownikowi na prze- towym przykładzie może być nawet bardziej sy nadrzędnej jest konieczne do po-

98 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

prawnego działania aplikacji. Natomiast


setContentView(int) ustawia zawartość Listing 1. Zawartość wygenerowanej aktywności
ekranu. Osoby, które nie miały styczności
z Androidem mogą być nieco zaskoczone package org.sdjournal;
parametrem przekazywanym do metody
setContentView(int). Szybkie podejrze- import android.app.Activity;
nie zawartości klasy R w edytorze ujaw- import android.os.Bundle;
ni nam, że R.layout.main jest nic nie mó-
wiącą stałą numeryczną. Ta magiczna licz- public class GAlbumActivity extends Activity {
ba to identyfikator zasobu, skompilowa- public void onCreate(Bundle savedInstanceState) {
nego przez narzędzie aapt. W tym wypad- super.onCreate(savedInstanceState);
ku odpowiada on plikowi main.xml z kata- setContentView(R.layout.main);
logu /res/layout/. W tym katalogu przecho- }
wywane są tzw. layouty dla UI naszej apli- }
kacji. Jest to jeden ze sposobów tworzenia
interfejsu aplikacji w Androidzie. Listing 2. Zawartość pliku main.xml definiującego układ elementów ekranu startowego

Ekran startowy <?xml version="1.0" encoding="utf-8"?>


Dysponując działającym szkieletem aplika- <LinearLayout
cji, możemy przystąpić do dzieła. Ekran star- xmlns:android="http://schemas.android.com/apk/res/android"
towy ma zawierać dwa przyciski, z których je- android:orientation="vertical"
den uruchomi moduł aparatu, a drugi galerię android:layout_height="wrap_content"
zdjęć. Najprostszym sposobem osiągnięcia te- android:layout_width="wrap_content"
go efektu będzie zmodyfikowanie wygene- android:layout_gravity="center">
rowanego już layoutu. Otworzenie w Eclip- <Button
se wspomnianego wcześniej pliku main.xml android:layout_height="wrap_content"
uruchamia edytor GUI, który pozwala na wy- android:layout_width="fill_parent"
godne edytowanie hierarchii elementów wi- android:id="@+id/PhotoButton"
doku, bez konieczności ręcznych zmian w android:text="@string/strTakeAPhoto">
pliku XML. </Button>
Wygenerowany element typu TextView <Button
należy usunąć, gdyż nie będzie on po- android:layout_height="wrap_content"
trzebny. Zostaje nam tylko element typu android:layout_width="fill_parent"
LinearLayout. Jest to jeden z najprostszych android:id="@+id/GalleryButton"
układów, polegający na umieszczaniu kon- android:text="@string/strGallery">
trolek liniowo, jedna za drugą. Następnie </Button>
dodajemy dwa elementy typu Button. W </LinearLayout>
rezultacie otrzymujemy dwa białe, kwadra-

Szybki start
Zanim przystąpimy do tworzenia aplikacji, należy się zaopatrzyć w Android SDK, który umożliwi budowanie aplikacji na Androida. Zawiera on
pełny zestaw narzędzi, w tym emulator. Zachęcam też do skorzystania z plugina dla środowiska Eclipse, który dodaje integrację z SDK oraz za-
pewnia wiele udogodnień, jak np. edytor UI, automatyczną kompilację zasobów oraz wiele innych. Plugin można zainstalować, korzystając z
panelu zarządzania pluginami i aktualizacjami (w wersji 3.4 będzie to Help>Software Updates...) i dodając nową lokację z adresem https://dl-
ssl.google.com/android/eclipse/. Po zainstalowaniu, w ustawieniach (Window>Preferences) pojawi się nowa sekcja zatytułowana Android. Pierw-
szym krokiem po zainstalowaniu plugina jest podanie w tym miejscu ścieżki do SDK. Uwaga, nowa wersja SDK wymaga najnowszej wersji
wtyczki.
Android oferuje bardzo prosty, ale przydatny i wygodny mechanizm logowania. Korzystać możemy z niego poprzez klasę Log, która pozwala
logować wiadomości na poszczególnych stopniach ważności. Na przykład:
Log.i("tag", "wiadomosc");
spowoduje dodanie wiadomości informacyjnej z podanym tagiem i treścią.
Po zainstalowaniu wtyczki, Eclipse udostępnia nam dodatkowy widok, umożliwiający podgląd loga wraz z funkcjami filtrowania. Widok ten
można aktywować z menu Window>ShowView>Other... i wybierając LogCat w sekcji Android. Funkcja ta działa nie tylko dla emulatora, ale rów-
nież dla urządzenia podłączonego do komputera za pomocą kabla USB.

Android: GUI
Podstawą GUI w Androidzie jest widok, reprezentowany przez klasę View. Jest to podstawowy element GUI, który służy do wyświetlania kon-
kretnych elementów interfejsu, takich jak przyciski czy pola tekstowe. Specjalnym wariantem widoku jest klasa ViewGroup, która reprezentuje
grupę widoków. Jest to element, który najczęściej sam nic sobą nie reprezentuje, służy jako kontener dla innych widoków. Obiekty tych dwóch
klas tworzą hierarchiczną, drzewiastą strukturę, w której liśćmi są widoki z grupą widoków jako rodzicem. Korzeniem hierarchii, która ma więcej
niż jeden poziom, będzie obiekt typu ViewGroup. Grupa widoków jest najczęściej określana mianem layoutu, czyli obiektu definiującego spo-
sób umieszczania kontrolek w ramach pewnego obszaru. Aby podpiąć taką hierarchę widoków do ekranu celem wyświetlenia, należy skorzy-
stać z metody setContentView() klasy Activity.

www.sdjournal.org 99
Programowanie Android

towe przyciski umieszczone jeden pod dru- fill_parent sprawi, że dany element wy- tym samym musimy je jakoś jednoznacz-
gim w lewym górnym rogu ekranu. Nie jest pełni całkowicie dostępną jemu przestrzeń, nie identyfikować. Do tego celu służy pa-
to widok specjalnie imponujący, więc spró- wrap_content natomiast użyje jej tylko ty- rametr Id. Oba przyciski domyślnie posia-
bujmy to zmienić. Po pierwsze, przyciski le, ile jest konieczne, aby wyświetlić da- dają taki identyfikator, jednak jego wartość
potrzebują etykiet, które rzuciłyby nieco ny element. Zmiana tych właściwości dla jest mało intuicyjna, toteż zmieniamy war-
światła na ich przeznaczenie. W tym celu LinearLayout na wrap_content sprawi, tości domyślne na @+id/PhotoButton oraz
w okienku Properties odnajdujemy właści- że nie będzie wypełniał on całego ekranu. @+id/GalleryButton. Po drugie, umiesz-
wość o nazwie Text i wpisujemy tam ety- Z kolei za rozmieszczenie elementu od- czanie etykiet tekstowych bezpośrednio w
kietę dla przycisku (Take a photo oraz Gal- powiada parametr Layout gravity, usta- layoucie nie jest zbyt rozsądne. Taka prakty-
lery). Różna długość tekstu pokazuje nam, wienie go na center umieści nasz layout ka sprawia, że lokalizacja takiej aplikacji by-
że przyciski są dosunięte do lewej stro- (i tym samym przyciski) na środku ekra- łaby bardzo pracochłonna. Rozwiązaniem
ny obszaru, co nie jest specjalnie przyjem- nu. Aby przyciski miały jednakową sze- jest zdefiniowanie tych etykiet jako zaso-
ne dla oka. Aby uzyskać przyciski o tej sa- rokość, należy ustawić Layout width na by. W tym celu otwieramy plik strings.xml,
mej szerokości, wyśrodkowane na ekranie, fill_parent. i dodajemy dwa elementy typu String (na-
konieczne jest zmodyfikowanie kilku para- W tym momencie osiągnęliśmy zamie- zwiemy je strGallery i strTakeAPhoto).
metrów. Po pierwsze, wysokość i szerokość rzony efekt wizualny, jednak to nie wszyst- Następnie wracamy do edycji layoutu i
elementu (Layout width i Layout height) ko, co musimy wziąć pod uwagę. Po pierw- zmieniamy parametr Text dla obu przyci-
mogą być ustalone na stałe lub przyjąć sze, do elementów typu przyciski będzie- sków. Na szczęście Eclipse przychodzi nam
wartość fill_parent lub wrap_content. my musieli odwoływać się z poziomu kodu, z pomocą i pozwala wybrać nowo dodane
teksty z listy, dzięki czemu unikamy żmud-
nego wpisywania identyfikatorów. Wyni-
kowy plik XML z layoutem przedstawiony
����������
jest na Listingu 2.
�������� Tak zmodyfikowana aplikacja goto-
wa jest do uruchomienia, jednak jest ma-
ło ciekawa, gdyż nic nie robi ;). Aby cokol-
������������������ wiek mogło się dziać, nasz program mu-
����������
������������� si reagować na działania użytkownika. W
tym celu musimy przechwycić zdarzenia
generowane przez system. Klasa View do-
����������� ��������� starcza kilka interfejsów służących wła-
śnie do tych celów. Nas na razie interesu-
je View.OnClickListener, który definiu-
���������� je jedną metodę – onClick(View). Obiekt
implementujący ten interfejs rejestrujemy
poprzez metodę View.setOnClickListene
r(View.OnClickListener) (pamiętamy, że
���������� ��������� �����������
����������� ������ �������� wszystkie elementy UI dziedziczą po kla-
���������� sie View, a więc nasze przyciski też). W tym
momencie zapewne zastanawiasz się, drogi
Czytelniku, skąd wytrzasnąć obiekt odpo-
����������������������
����������������������� wiadający przyciskowi zdefiniowanemu w
naszym layoucie. Tu do akcji wkraczają zde-
finiowane przez nas identyfikatory oraz
���������������
���������
������������������
metoda findViewById(int). Poniżej linij-
ka kodu, która robi to, co chcemy:

������������������������������� ((Button) findViewById(R.id.PhotoButton)).s


etOnClickListener(m
TakePhotoListener);
���������������������
��������
���������� Zanim przejdziemy do następnego etapu
tworzenia aplikacji, chciałbym przedsta-
wić ciekawy mechanizm związany z łado-
�����������
waniem zasobów przez Androida. Niecier-
pliwi mogą przeskoczyć prosto do punk-
tu Intencje.
����������
������������
Zasoby a różne konfiguracje
Android daje możliwość zdefiniowania al-
ternatywnych zasobów w zależności od kon-
figuracji środowiska, w którym uruchamia-
na jest aplikacja. Aby skorzystać z tej możli-
Rysunek 1. Diagram cyklu życia aktywności wości, wystarczy alternatywną wersję zasobu

100 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

umieścić w katalogu zawierającym w nazwie


jeden lub kilka kwalifikatorów zdefiniowa- Listing 3. Przykład użycia metody onSaveInstanceState(Bundle)
nych przez Androida. Takim kwalifikatorem public class EditorActivity extends Activity {
może być np. język, orientacja ekranu czy je-
go rozdzielczość. Po szczegółową listę odsy- private static final String BUNDLE_KEY = "saved_message";
łam do dokumentacji.
My wykorzystamy ten mechanizm public void onCreate(Bundle savedInstanceState) {
w celu przygotowania ekranu startowe- super.onCreate(savedInstanceState);
go przystosowanego specjalnie dla pozio- setContentView(R.layout.main);
mej orientacji ekranu. W tym celu otwie- if (savedInstanceState != null) {
ramy katalog res i robimy kopię podkata- String text = savedInstanceState.getString(BUNDLE_KEY);
logu layout pod nazwą layout-land. Część ((EditText) findViewById(R.id.editMsg)).setText(text);
nazwy po myślniku jest kwalifikatorem }
oznaczającym poziomą orientację ekranu. }
Gdy podajemy ich więcej, wszystkie mu-
szą być od siebie oddzielone myślnikiem. protected void onSaveInstanceState(Bundle outState) {
Następnie modyfikujemy plik main.xml EditText editText = (EditText) findViewById(R.id.editMsg);
z nowo utworzonego katalogu. Wybiera- String message = editText.getText().toString();
my LinearLayout i zmieniamy parametr outState.putString(BUNDLE_KEY, message);
Orientation na horizontal, dzięki czemu }
przyciski zostaną umieszczone poziomo je- }
den za drugim.
Spróbuj uruchomić tak zmodyfikowaną Listing 4. Zawartość pliku AndroidManifest.xml dla naszej aplikacji
aplikację na telefonie (albo emulatorze) i <?xml version="1.0" encoding="utf-8"?>
zauważ, że gdy zmienisz orientację ekra- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
nu ([Ctrl+F12] na emulatorze), rozłożenie package="org.sdjournal.galbum"
przycisków zmienia się na to zdefiniowa- android:versionCode="1"
ne w alternatywnym layoucie. Nietrudno android:versionName="1.0.0">
sobie wyobrazić, jak pomocny może być <application android:icon="@drawable/icon" android:label="@string/app_name">
ten mechanizm, gdy tworzy się aplikację <activity android:name=".GAlbumActivity"
mającą działać na wielu odmiennych pod android:label="@string/app_name">
względem konfiguracji telefonach. Cho- <intent-filter>
ciaż trzy obecnie dostępne telefony z An- <action android:name="android.intent.action.MAIN" />
droidem na pokładzie mają prawie iden- <category android:name="android.intent.category.LAUNCHER" />
tyczną konfigurację, to można się spodzie- </intent-filter>
wać, że z biegiem czasu pojawią się urzą- </activity>
dzenia odbiegające parametrami od obec- <activity android:name="PhotoCaptureActivity" android:screenOrientation="land
nej generacji. scape">
No dobrze, wiemy już, jak wykorzystać </activity>
ten mechanizm i w jakim celu, jednak jak <activity android:name="PhotoInspectActivity" android:screenOrientation="landscape
on działa? Odpowiedź na to pytanie jest "></activity>
bardzo prosta. W momencie zmiany kon-
figuracji (tutaj: zmiana orientacji ekranu), <activity android:name="MapGalleryActivity"></activity>
aktualnie wyświetlana aktywność jest re- <uses-library android:name="com.google.android.maps"></uses-library>
startowana, tzn. niszczona, a następnie <activity android:name="PhotoViewActivity" android:configChanges="keyboardHidden|ori
tworzona ponownie, zgodnie z cyklem ży- entation" android:screenOrientation="sensor"></activity>
cia (patrz ramka Cykl życia aktywności). </application>
Aktywności znajdujące się w tle zostaną <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-
zrestartowane w momencie ich przywró- permission>
cenia na wierzch. Takie zachowanie ozna- <uses-permission android:name="android.permission.CAMERA"></uses-permission>
cza, że tracimy stan tych aktywności. Wy- <uses-permission android:name="android.permission.INTERNET"></uses-permission>
obraźmy sobie, że użytkownik jest w trak- <uses-sdk android:minSdkVersion="3"></uses-sdk>
cie pisania e-maila, gdy zmiana konfigu- </manifest>
racji wymusza restart aktywności. Utra-
ta treści niedokończonej wiadomości jest
nieakceptowalna. Na szczęście Andro-

Cykl życia aktywności


Aktywność ma ściśle określony cykl życia oraz posiada metody wywoływane przez system w momencie przejścia z jednego stanu do drugiego.
Cały cykl życia aktywności ma miejsce między wywołaniami metody onCreate() a onDestroy(). Można też wyróżnić widoczny cykl życia, kie-
dy aktywność jest wyświetlana na ekranie. Ma to miejsce pomiędzy onStart() a onStop(). Część cyklu życia, kiedy aktywność jest na pierw-
szym planie, zachodzi między onResume() a onPause(). Należy pamiętać, że przesłaniając te metody, powinniśmy jako pierwszą czynność wy-
wołać jej wersję dla klasy nadrzędnej. Diagram cyklu życia jest przedstawiony na Rysunku 1.

www.sdjournal.org 101
Programowanie Android

id udostępnia mechanizm, dzięki które- Przy restarcie takiej aktywności będzie PhotoCaptureActivity. Przesłaniamy me-
mu możemy zapisać i odtworzyć stan ak- można w onCreate(Bundle) odczytać zapi- todę onCreate(Bundle) i mamy już szkie-
tywności. sane dane jedną z metod get. let aktywności, którą można już urucho-
Skoro wiemy już, jak zareagować na akcję mić. A podstawą mechanizmu urucha-
Zachowywanie stanu użytkownika, warto, by wreszcie zrobić coś miania aktywności są tzw. intencje, czyli
Czas wrócić do metody onCreate(Bundle). ciekawego. Chcemy, aby kliknięcie w przy- obiekty klasy Intent, które zawierają in-
Pominięty dotychczas milczeniem para- cisk Take a Photo uruchamiało moduł kame- formacje na temat naszych zamiarów. Z ni-
metr tej metody jest częścią omawianego ry, który umożliwi użytkownikowi zrobie- mi związane są filtry intencji ( IntentFil-
mechanizmu. Obiekt typu Bundle prze- nie zdjęcia. ter), definiujące, które komponenty mogą
kazywany przy tworzeniu aktywności mo- Zanim jednak przejdziemy do realizacji po- obsłużyć dane intencje. Informacje o tym
że zawierać różnorakie dane, potrzebne do wyższego, proponuję zapoznać się z ramką znajdują się w deskryptorze aplikacji, czyli
odtworzenia jej stanu. Przy pierwszym uru- Anatomia Aplikacji. w pliku AndroidManifest.xml. Otworzenie
chomieniu (lub gdy aktywność nic nie zapi- go z poziomu Eclipse uruchomi wygodny
sała) będzie miał wartość null. Aby skorzy- Intencje edytor, dzięki któremu nie będziemy mu-
stać z dobrodziejstw tego mechanizmu, na- Wiedząc już co nieco o tym, co jest apli- sieli ręcznie modyfikować pliku XML. W
leży jakoś te dane zapisać. W tym celu wy- kacją z punktu widzenia Androida, nie- zakładce Application znajduje się sekcja
starczy w klasie typu Activity przesłonić trudno dojść do wniosku, że nasz moduł Application Nodes, zawierająca drzewiasty
metodę onSaveInstanceState(Bundle) i obsługujący kamerę będzie osobną ak- widok elementów definiujących składniki
zapisać potrzebne dane, tak jak to pokaza- tywnością. Tworzymy więc nową klasę naszej aplikacji. W naszym przypadku za-
no na Listingu 3. dziedziczącą po Activity i nazywamy ją wiera definicję głównej aktywności, czy-
li GAlbumActivity. Rozwinięcie widoku
Listing 5. Kod aktywności startowej naszej aplikacji tego węzła ukaże naszym oczom element
Intent Filter oraz dwoje jego dzieci, ak-
public class GAlbumActivity extends Activity { cję (android.intent.action.MAIN) i kate-
gorię (android.intent.category.LAUN-
public void onCreate(Bundle savedInstanceState) { CHER). Więc co to wszystko znaczy?
super.onCreate(savedInstanceState); Aby to łatwiej zrozumieć, przyjrzyjmy się
bliżej intencjom. Obiekt klasy Intent może
setContentView(R.layout.main); zawierać następujące informacje:

((Button) findViewById(R.id.GalleryButton)) • nazwę komponentu, który ma obsłużyć


.setOnClickListener(mGalleryListener); tę intencję – podajemy tę informację,
((Button) findViewById(R.id.PhotoButton)) jeżeli chcemy uruchomić konkretną ak-
.setOnClickListener(mTakePhotoListener); tywność;
• akcję, którą chcemy wykonać – na pod-
PhotoManager.init(this); stawie samej akcji system może odszu-
} kać aktywności, które mogą je obsłużyć,
często konkretna akcja wymaga dodat-
private View.OnClickListener mGalleryListener = new View.OnClickListener() { kowych danych;
public void onClick(View view) { • adres zasobu w postaci URI – może
launchActivity("MapGalleryActivity"); być wymagany dla konkretnych akcji,
} przykładowo akcja wyświetlenia obraz-
}; ka może wymagać jego lokalizacji (np.
ścieżki do pliku lub adresu interneto-
private View.OnClickListener mTakePhotoListener = new View.OnClickListener() { wego);
public void onClick(View view) { • kategorię komponentu, który może ob-
launchActivity("PhotoCaptureActivity"); służyć tę intencję;
} • dodatkowe dane w postaci pary klucz-
}; wartość;
• dodatkowe flagi (zdefiniowane w klasie
private void launchActivity(String activityName) { Intent).
ComponentName componentName = new ComponentName("org.sdjournal.galbum",
"org.sdjournal.galbum." + activityName); Wracając do naszej aplikacji, zdefinio-
wany dla niej filtr intencji mówi syste-
Intent runActivity = new Intent(); mowi, że GAlbumActivity jest aktywno-
runActivity.setComponent(componentName); ścią startową naszej aplikacji (akcja MA-
startActivity(runActivity); IN) oraz że może być wyświetlana na li-
} ście aplikacji w menu telefonu (kategoria
LAUNCHER). Aby aktywność mogła zo-
protected void onDestroy() { stać uruchomiona, musi zostać zdefinio-
PhotoManager.release(); wana w AndroidManifest.xml. W tym ce-
} lu dodajemy naszą nowo utworzoną ak-
} tywność do listy, używając przycisku
Add w sekcji Application Nodes i ustawia-

102 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

jąc dla niej atrybut Name. Kliknięcie przy- cji auto focus, status usługi pozycjonowania się z bliska oraz podjęcia decyzji, czy je za-
cisku Browse wyświetli listę znalezionych (np. GPS) czy informacje o parametrach pisać, czy nie.
aktywności w projekcie, więc nie ma po- zdjęcia lub dostępnej pojemności na karcie Zanim przystąpimy do pisania kodu ob-
trzeby ręcznego wpisywania nazwy ak- pamięci. Po zrobieniu zdjęcia użytkownik sługującego kamerę, musimy ustawić od-
tywności. powinien mieć możliwość przyjrzenia mu powiednie uprawnienia dla naszej aplika-
Dla ciekawskich finalna zawartość mani-
festu przedstawiona jest na Listingu 4. Znak Listing 6. Kod odpowiedzialny za tworzenie i inicjalizację kontrolera kamery
@ na początku niektórych parametrów ozna-
cza, że jest on referencją do zasobu. public class CameraController {
private static CameraController mInstance;
Uruchamianie aktywności
Mamy już naszą nową aktywność zdefinio- private Camera mCamera;
waną w manifeście, więc czas najwyższy
pokazać, jak ją uruchomić. Jako że wiemy private CameraController() {
dokładnie, którą aktywność chcemy uru- mCamera = Camera.open();
chomić, to musimy utworzyć intencję za- }
wierającą nazwę komponentu. Proces ten
jest bardzo prosty. Tworzymy obiekt klasy public static CameraController getCameraController() {
ComponentName zawierający nazwę pakie- if (mInstance == null) {
tu, w którym znajduje się aktywność, któ- mInstance = new CameraController();
rą chcemy uruchomić, oraz pełną nazwę }
klasy (wraz z pakietem). I tu mała, aczkol-
wiek istotna, uwaga. Często w dokumen- return mInstance;
tacji, jak i w samym manifeście pojawia się }
termin package.
Nie zawsze jednak oznacza on pakiet w public void release() {
sensie używanym przez Javę. Tym termi- if (mCamera != null) {
nem określa się nazwę paczki aplikacji. Jest mCamera.stopPreview();
to unikalny identyfikator zdefiniowany w mCamera.release();
manifeście. W naszym przypadku jest to mCamera = null;
org.sdjournal.galbum, co odpowiada również }
pakietowi w rozumieniu Javy, jednak wcale mInstance = null;
tak nie musi być. }
Mając tę drobnostkę wyjaśnioną, kon-
tynuujemy pracę. Tworzymy obiekt kla- Listing 7. Kod callbacka zarządzającego powierzchnią podglądu
sy Intent i wywołujemy jego metodę se
tComponent(ComponentName) z wcześniej private SurfaceHolder.Callback mSurfaceHolderCallback = new
stworzoną nazwą komponentu. W tym SurfaceHolder.Callback() {
momencie mamy gotową intencję, którą
przekazujemy jako parametr do metody public void surfaceCreated(SurfaceHolder holder) {
startActivity(Intent) z klasy Activity. try {
Efektem tych działań będzie czarny ekran, mCamera.setPreviewDisplay(holder);
gdyż nie ustawiliśmy żadnego widoku dla } catch (IOException e) {
nowej aktywności. Aby wrócić do ekranu e.printStackTrace();
startowego, należy wcisnąć przycisk back }
telefonu. Tak jak wcześniej wspomniałem, }
odbywa się to automatycznie, nie musi-
my pisać ani linijki kodu, aby uzyskać ten public void surfaceChanged(SurfaceHolder holder, int format, int width,
efekt. Kod źródłowy naszej aktywności int height) {
startowej można znaleźć na Listingu 5. if (mCamera != null) {
Camera.Parameters parameters = mCamera.getParameters();
Obsługa kamery parameters.setPreviewSize(width, height);
Na pewno masz już, drogi Czytelniku, dość mCamera.setParameters(parameters);
opisu podstaw programowania aplikacji an- mCamera.startPreview();
droidowej. Obiecuję, że od tego momen- }
tu zajmiemy się zdecydowanie ciekawszy- }
mi rzeczami.
Zacznijmy od zdefiniowania, co chcemy public void surfaceDestroyed(SurfaceHolder holder) {
osiągnąć. Po uruchomieniu modułu kame- if (mCamera != null) {
ry oczom użytkownika powinien ukazać mCamera.stopPreview();
się podgląd, tak jak to się dzieje w przy- }
padku aparatu cyfrowego. Oprócz podglą- }
du zdjęcia, na ekranie powinny się znaleźć };
informacje dodatkowe, takie jak stan funk-

www.sdjournal.org 103
Programowanie Android

cji. W tym celu otwieramy AndroidMani- my wpis Uses Permission z parametrem w manifeście informuje system, że nasza
fest.xml na zakładce Permissions i dodaje- android.permission.CAMERA. Taki wpis aplikacja korzysta z danej funkcjonalno-
ści i potrzebuje w tym celu pozwolenia.
Listing 8. Kod odpowiedzialny za ustawianie podglądu i parametrów zdjęcia Użytkownik zostanie o tym poinformo-
wany podczas instalacji. Skoro już jeste-
public void setPhotoQuality(int quality) { śmy przy edytowaniu manifestu, ustawia-
if (mCamera != null) { my poziomą (landscape) orientację ekra-
if (quality < 0 || quality > 100) { nu dla PhotoCaptureActivity (atrybut
quality = 80; Screen orientation). Teraz możemy za-
} cząć zabawę.
Parameters params = mCamera.getParameters(); Pierwszym krokiem do zaimplemento-
params.set("jpeg-quality", quality); wania opisanej wcześniej funkcjonalności
mCamera.setParameters(params); będzie napisanie kodu służącego do ob-
} sługi wbudowanej w telefon kamery. Kla-
} sa, która nas interesuje to Camera z pakietu
android.hardware wraz z zagnieżdżonymi
public void setPreview(SurfaceHolder holder) { klasami i interfejsami.
if (mCamera != null) { Zaczniemy od napisania klasy pomocni-
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); czej, która nieco uprości korzystanie z ka-
holder.addCallback(mSurfaceHolderCallback); mery i będzie czymś w rodzaju kontrole-
} ra. Powodów zastosowania takiego podej-
} ścia jest kilka. Po pierwsze, w razie zmiany
Camera API może nam to oszczędzić spo-
Listing 9. Kod odpowiedzialny za obsługę auto focusa ro pracy przy przenoszeniu kodu ze sta-
rego na nowe API. To oczywiście ma ma-
public interface FocusChangeListener { łe znaczenie dla kodu, który stanowi tyl-
int AUTO_FOCUS_READY = 0; ko przykład na potrzeby tego artykułu, ale
int AUTO_FOCUS_IN_PROGRESS = 1; dobrze o takich rzeczach pamiętać w przy-
int AUTO_FOCUS_FAILED = 2; padku pisania aplikacji produkcyjnych. Po
int AUTO_FOCUS_SUCCEDED = 3; drugie, łatwiej jest w ten sposób pokazać,
jak korzystać z nieznanej funkcjonalności.
public void onCameraFocusChange(int focusState); Po trzecie, nieumiejętnie obchodząc się z
} zasobem kamery, możemy Androidowi
zrobić kuku. A to dlatego, że kamera nie
private FocusChangeListener mFocusChangeListener; jest zasobem współdzielonym. Zaczynając
pracę z kamerą, musimy o tym pamiętać i
public void setFocusChangeListener(FocusChangeListener listener) { sprzątać po sobie, w przeciwnym wypad-
mFocusChangeListener = listener; ku możemy ten zasób permanentnie za-
} blokować. I tu drobna rada. Jeżeli ekspery-
mentujesz z kodem, który korzysta z API
private void focusChangeNotify(int newState) { dla kamery i coś pójdzie nie tak, powodu-
if (mFocusChangeListener != null) { jąc nieoczekiwane zamknięcie aplikacji, to
mFocusChangeListener.onCameraFocusChange(newState); zanim znowu ją odpalisz (np. w nowej, po-
} prawionej wersji), sprawdź, czy systemo-
} wa aplikacja Camera działa poprawnie. Je-
żeli uda ci się zrobić przy jej użyciu zdję-
private Camera.AutoFocusCallback mAutoFocusCallback = new Camera.AutoFocusCal cie, to znak, że możesz kontynuować swoje
lback() { eksperymenty. Jeżeli jednak aplikacja nie
działa, jak należy, to znaczy, że twój kod
public void onAutoFocus(boolean success, Camera camera) { robi z kamerą straszne rzeczy. W takiej sy-
tuacji po prostu zrestartuj telefon. Dodam
if (success) { tylko, że osobiście udało mi się kilkakrot-
focusChangeNotify(FocusChangeListener.AUTO_FOCUS_SUCCEDED); nie doprowadzić do drugiej z powyższych
} else { sytuacji.
focusChangeNotify(FocusChangeListener.AUTO_FOCUS_FAILED); Mając ten wstęp za sobą, przejdźmy do
} kodu, czyli klasy CameraController. W
} związku z naturą zasobu, z którym mamy
}; do czynienia, skorzystamy ze wzorca Single-
ton, czyli wymusimy jedną instancję nasze-
public void autoFocus() { go kontrolera. Zastosujemy podejście opar-
focusChangeNotify(FocusChangeListener.AUTO_FOCUS_IN_PROGRESS); te na zdarzeniach, tzn. użytkownik kontro-
mCamera.autoFocus(mAutoFocusCallback); lera będzie informowany o interesujących
} go wydarzeniach poprzez zarejestrowane
obiekty nasłuchujące. Oznacza to, że sam

104 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

kontroler nie przechowuje np. stanu funk- Po co nam natomiast callback? Otóż dzięki Listingu 7. I tak w surfaceCreated() usta-
cji auto focus ani danych właśnie zrobione- niemu zostaniemy poinformowani o fak- wiamy powierzchnię dla podglądu, korzy-
go zdjęcia. Jest jedynie biernym przekaźni- cie stworzenia, zmiany i zniszczenia ob- stając z metody setPreviewDisplay(Surf
kiem informacji. szaru zdatnego do wyświetlania. Próba na- aceHolder). W surfaceChanged() mamy
Zacznijmy od inicjalizacji. Statyczna rysowania podglądu na powierzchni nieza- już gwarancję, że inicjalizacja powierzch-
metoda Camera.open() zwróci nam in- inicjalizowanej lub zniszczonej skończy- ni już się zakończyła, ustawiamy więc roz-
stancję klasy Camera, na której będziemy łaby się niepowodzeniem. Dlatego zanim miar podglądu pasujący do rozmiaru po-
dalej operować. W momencie zakończe- ustawimy podgląd, musimy upewnić się, wierzchni i rozpoczynamy podgląd. W
nia pracy z kamerą należy wywołać jej me- że powierzchnia, na której będzie on ry- surfaceDestroyed() zatrzymujemy pod-
todę release(), która zwolni zasób kame- sowany, istnieje. Zanim podgląd włączy- gląd, gdyż dalsza próba jego wyświetlania
ry, umożliwiając korzystanie z niego in- my, musimy ustawić poprawne parame- na zniszczonej powierzchni zakończy się
nym aplikacjom. Prywatny konstruktor i try, a w momencie zniszczenia powierzch- niepowodzeniem.
statyczna metoda CameraController.getC ni musimy zadbać o zatrzymanie podglą- Metoda setPhotoQuality(int) usta-
ameraController() uniemożliwiają próbę du. Do tego właśnie wykorzystamy ów cal- wia jakość zdjęcia, czyli stopień kompre-
stworzenia więcej niż jednej instancji ka- lback, którego kod możemy podziwiać na sji obrazu JPEG, który zostanie stwo-
mery. Czujni czytelnicy zapewne zauważą
szachrajstwo w metodzie release(), które Listing 10. Kod odpowiedzialny za wykonanie zdjęcia
wraz ze zwolnieniem zasobów nulluje in-
stancję kontrolera. Oznacza to, że de facto public interface PhotoCaptureListener {
nie jest on singletonem, gdyż po jego zwol- public void onJpgPhotoCaptured(byte[] jpgData);
nieniu metoda getCameraController() }
zwróci nową jego instancję. Możliwe jest
więc istnienie kilku kontrolerów, jednak private PhotoCaptureListener mCaptureChangeListener;
wciąż mamy gwarancję, że tylko jeden z
nich jest powiązany z kamerą. Jest to za- public void setCaptureStateListener(PhotoCaptureListener l) {
bieg celowy. W dalszej części artykułu po- mCaptureChangeListener = l;
każę również, dlaczego jest to złe rozwią- }
zanie.
Inicjalizację (której kod przedstawio- private void photoCapturedNotify(byte[] data) {
ny jest na Listingu 6) mamy z głowy, czas if (mCaptureChangeListener != null) {
ustawić podgląd kamery. Do tego celu po- mCaptureChangeListener.onJpgPhotoCaptured(data);
służy metoda setPreview(SurfaceHolder }
) kontrolera, gdzie jako parametr przeka- }
zujemy obiekt posiadający we władaniu ka-
wałek obszaru wyświetlacza (nazwijmy go Camera.PictureCallback jpgPictureCallback = new Camera.PictureCallback() {
sobie posiadacz). Najczęściej obiekt ten jest public void onPictureTaken(byte[] data, Camera camera) {
pozyskiwany z obiektu klasy SurfaceView, photoCapturedNotify(data);
który z kolei zapewnia bezpośredni dostęp focusChangeNotify(FocusChangeListener.AUTO_FOCUS_READY);
do powierzchni ekranu (będziemy jeszcze mCamera.startPreview();
mieli okazję przyjrzeć mu się bliżej). Za- }
uważ, że metoda ta w ogóle nie operuje };
na obiekcie kamery. Zamiast tego ustawia
typ powierzchni oraz rejestruje callback public void capture() {
na rzecz posiadacza. SURFACE_TYPE_PUSH_ if (mCamera != null) {
BUFFERS oznacza, że dana powierzchnia mCamera.takePicture(null, null, jpgPictureCallback);
nie posiada własnych buforów. Ustawie- }
nie takiego typu jest konieczne, gdyż bu- }
forami podglądu zarządza usługa kamery.

Anatomia aplikacji
Główną częścią składową aplikacji androidowej są tzw. aktywności, reprezentowane przez klasę Activity. Przy pierwszym kontakcie z Androidem mo-
że się wydawać, że to Activity jest właśnie aplikacją, jednak nie jest to prawda. Pojedyncza aplikacja może składać się z wielu aktywności. Z reguły ak-
tywność odpowiada jednemu ekranowi aplikacji. Oczywiście możliwe jest stworzenie nawet bardzo skomplikowanej aplikacji z wieloma widokami w ra-
mach jednej aktywności, jednak efekt końcowy będzie najprawdopodobniej mało intuicyjny dla użytkownika. Ponadto konieczność żonglowania wido-
kami zapewne doprowadzi do kodu zagmatwanego dużo bardziej niż to konieczne. Stosując się do tej filozofii tworzenia aplikacji, automatycznie zmu-
szeni jesteśmy do logicznego podzielenia jej na mniejsze części składowe, co jest dobre z każdego punktu widzenia.
W dokumentacji Androida często zamiast używać słowa aplikacja stosuje się termin zadanie (task). Jest to grupa aktywności, którą użytkownik postrzega
jako aplikację. Ma ona postać stosu, gdzie każda nowa aktywność jest odkładana na jego szczyt. Dzięki temu tworzy się swoista historia poczynań użyt-
kownika. W momencie wciśnięcia przycisku back, aktualna aktywność jest niszczona i na ekranie pojawia się aktywność ze szczytu stosu, czyli poprzed-
ni ekran, na którym operował użytkownik.
Co ważniejsze, Android umożliwia uruchamianie aktywności należących do innych aplikacji. I właśnie w tym należy szukać powodu używania terminu
zadanie zamiast aplikacja. Użytkownik, wykonując jakąś czynność, może nawet nieświadomie korzystać z wielu różnych aplikacji.
Aktywności uruchamiane są za pomocą mechanizmu intencji, reprezentowanych przez klasę Intent. Każda aktywność może mieć własny filtr intencji
(IntentFilter), dzięki któremu aktywność reaguje tylko na intencje pasujące do kryteriów zdefiniowanych w filtrze.

www.sdjournal.org 105
Programowanie Android

Listing 11. Kod źródłowy aktywności obsługującej kamerę

public class PhotoCaptureActivity extends Activity {


public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.camera_preview);
initCamera();
initGps();
}
CameraController mCameraController;
private void initCamera() {
SurfaceView previewView = (SurfaceView) findViewById(R.id.previewSurfaceView);
mCameraController = CameraController.getCameraController();
mCameraController.setPreview(previewView.getHolder());
mCameraController.setFocusChangeListener(mFocusListener);
mCameraController.setCaptureStateListener(mPhotoListener);
findViewById(R.id.cameraLayout).setOnKeyListener(mKeyListener);
}
protected void onDestroy() {
super.onDestroy();
mCameraController.release();
}
private CameraController.FocusChangeListener mFocusListener = new CameraController.FocusChangeListener() {
public void onCameraFocusChange(int state) {
ImageView afIcon = (ImageView) findViewById(R.id.focusIndicator);
switch (state) {
case AUTO_FOCUS_READY:
afIcon.setImageResource(R.drawable.af_none); break;
case AUTO_FOCUS_IN_PROGRESS:
afIcon.setImageResource(R.drawable.af_yellow); break;
case AUTO_FOCUS_SUCCEDED:
afIcon.setImageResource(R.drawable.af_green); break;
case AUTO_FOCUS_FAILED:
afIcon.setImageResource(R.drawable.af_red); break;
}
}
};
private CameraController.PhotoCaptureListener mPhotoListener = new CameraController.PhotoCaptureListener() {
public void onJpgPhotoCaptured(byte[] jpgData) {
PhotoManager.addPendingPhoto(jpgData, mLastLocation);
launchPhotoInspectActivity();
};
};
private View.OnKeyListener mKeyListener = new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_FOCUS) {
if (event.getRepeatCount() == 0 && event.getAction() == KeyEvent.ACTION_DOWN) {
mCameraController.autoFocus();
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_CAMERA) {
if (event.getRepeatCount() == 0 && event.getAction() == KeyEvent.ACTION_DOWN) {
mCameraController.capture();
}
return true;
}
return false;
}
};
}

106 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

rzony przez moduł kamery. Korzysta-


Listing 12. Kod inicjalizujący GPS
my z metody getParameters(), aby po-
brać aktualne parametry kamery. Parame- private void initGps() {
try te przechowywane są w obiekcie kla- mGpsStatusLocationIndicator = (TextView) findViewById(R.id.gpsLocation);
sy Camera.Parameters. Po ustawieniu żą- mGpsStatusIndicator = (TextView) findViewById(R.id.gpsStatus);
danego parametru, za pomocą metody
set(String, int) przekazujemy je do ka- mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mery używając metody setParameters(). boolean isGpsEnabled = mLocationManager.isProviderEnabled(LocationM
Kod odpowiedzialny za ustawianie pod- anager.GPS_PROVIDER);
glądu i parametrów zdjęcia przedstawia Li- if (isGpsEnabled) {
sting 8. mGpsStatusIndicator.setText(R.string.gpsEnabled);
Kolejnym krokiem będzie obsługa funk- }
cji auto focus. Robienie zdjęcia prawdzi- }
wym aparatem cyfrowym z reguły odby-
wa się dwustopniowo. Użytkownik wciska Listing 13. Kod odpowiadający za pobieranie pozycji geograficznej
nieznacznie przycisk migawki i czeka aż private static final int GPS_UPDATE_TIME = 5000;
funkcja auto focus ustawi ostrość i da mu protected void onResume() {
znać, że skończyła. Wtedy wciśnięcie przy- super.onResume();
cisku do końca powoduje zrobienie wspa- mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
niałego, ostrego jak brzytwa zdjęcia. Do- GPS_UPDATE_TIME, 0, mLocationListener);
brze by było zapewnić użytkownikowi na- }
szej aplikacji podobne emocje, dzięki cze- protected void onPause() {
mu może na chwilę zapomni, że jego zdję- super.onPause();
cie nie będzie tak wspaniałe i ostre jak to mLocationManager.removeUpdates(mLocationListener);
wykonane aparatem cyfrowym kosztują- }
cym połowę tego co jego telefon. Zacznij- protected void onDestroy() {
my od tego, że funkcję auto focus kamery super.onDestroy();
uruchamiamy, wywołując adekwatnie na- mCameraController.release();
zwaną metodę autoFocus() na obiekcie mGpsStatusIndicator = null;
kamery. Metoda ta jako parametr przyj- mGpsStatusLocationIndicator = null;
muje obiekt implementujący interfejs mCameraController = null;
Camera.AutoFocusCallback , dzięki któ- mLocationManager = null;
remu możemy się dowiedzieć, jaki był re- }
zultat działania auto focusa. To wystarczy, private LocationListener mLocationListener = new LocationListener() {
aby nasz kontroler wiedział, co się dzieje, public void onLocationChanged(android.location.Location location) {
teraz wystarczy to przekazać użytkowniko- synchronized (this) {
wi kontrolera. mLastLocation = location;
My zamiast stosować konwencję udało }
się lub nie, wprowadzimy cztery stany dla updateLocationIndicator(location);
auto focusa. Będą to: };
public void onProviderDisabled(String provider) {
• gotowy do użycia (np. bezpośrednio po mGpsStatusIndicator.setText(R.string.gpsDisabled);
uruchomieniu kamery lub po zrobieniu updateLocationIndicator(null);
zdjęcia); };
• w trakcie działania; public void onProviderEnabled(String provider) {
• zakończony niepowodzeniem; mGpsStatusIndicator.setText(R.string.gpsEnabled);
• zakończony powodzeniem. };
public void onStatusChanged(String provider, int status, Bundle extras) {
Nie będziemy specjalnie oryginalni w switch (status) {
kwestii mechanizmu informowania użyt- case LocationProvider.OUT_OF_SERVICE:
kownika o zmianie stanu i wykorzysta- mGpsStatusIndicator.setText(R.string.gpsOutOfService);
my powszechnie używany mechanizm updateLocationIndicator(null);
callbacków (zwanych również z angielska break;
listenerami lub obiektami nasłuchujący- case LocationProvider.TEMPORARILY_UNAVAILABLE:
mi). Do tego celu posłuży interfejs Camer mGpsStatusIndicator.setText(R.string.gpsUnavailable);
aController.FocusChangeListener, me- updateLocationIndicator(null);
toda setFocusChangeListener() do re- break;
jestrowania callbacka oraz prywatna me- case LocationProvider.AVAILABLE:
toda pomocnicza focusChangeNotify(). mGpsStatusIndicator.setText(R.string.gpsAvailable);
Do uruchomienia całej tej machinerii break;
służy jakże oryginalnie nazwana metoda }
autoFocus() naszego kontrolera. Kod ob- };
sługujący funkcję auto focus można zna- };
leźć na Listingu 9.

www.sdjournal.org 107
Programowanie Android

Pozostało nam już tylko zaimplemen- le. SurfaceView, który będzie wypełniał ca- formowani o zdarzeniach związanych z in-
towanie najważniejszej funkcji, czyli zro- łą dostępną powierzchnię ekranu, posłuży terakcją użytkownika (np. wciśnięcia kla-
bienie zdjęcia. Do tego celu nasz kontro- do wyświetlania podglądu. Natomiast do wiszy), widok, który podsłuchujemy, mu-
ler udostępnia metodę capture(), która pokazania ikonki statusu auto focusa użyje- si mieć możliwość zyskania focusa. Moż-
pod maską wywołuje na kamerze metodę my ImageView. Będzie on wyświetlał jeden na to uzyskać, wywołując na nim metodę
takePicture(). Tak jak w przypadku meto- z czterech obrazków w zależności od stanu. setFocusable(true) lub setFocusableInT
dy autoFocus(), korzysta ona z callbacków, Obrazki znajdują się w podkatalogu drawa- ouchMode(true) , lub ustawiając analogicz-
aby poinformować użytkownika o rezulta- ble, dzięki czemu są dostępne przy użyciu ne atrybuty w definicji layoutu.
tach jej działania. I tak ShutterCallback mechanizmu zarządzania zasobami Andro- Obsługa przycisków jest trywialna. W
przekazuje informację o fakcie zamknię- ida. Dodatkowo wstawimy dwie kontrolki metodzie onKey() sprawdzamy, czy kod
cia migawki, co oznacza moment wykona- TextView, które posłużą nam do wyświetla- klawisza odpowiada przyciskowi auto focu-
nia zdjęcia, kiedy dane nie są jeszcze do- nia informacji o statusie GPS. sa lub kamery. Jeżeli tak, to bierzemy pod
stępne. Jednak dużo istotniejszą rolę peł- Kod gotowej aktywności przedstawio- uwagę tylko pierwsze z serii zdarzeń gene-
ni PictureCallback, gdyż to za jego pomo- ny jest na Listingu 11. Dzięki wykorzysta- rowanych przez wciśnięcie klawisza i wy-
cą możemy pozyskać wynik działania me- niu naszego kontrolera powinien on być wołujemy pożądaną metodę naszego kon-
tody takePicture(), czyli dane samego stosunkowo czytelny. Podsumowując, oto, trolera kamery. Zwrócenie true oznacza,
zdjęcia. Mamy możliwość zarejestrowania co się tam dzieje. Na początku, za pomo- że obsłużyliśmy zdarzenie i tym samym nie
dwóch takich callbacków, jednego dla suro- cą metody requestWindowFeature() po- jest ono propagowane dalej. Gdybyśmy po-
wych danych, drugiego dla formatu JPEG. zbywamy się belki tytułowej, aby uzyskać minęli ten szczegół, zdarzenie o wciśnię-
Nasz kontroler jest zainteresowany wyłącz- większą powierzchnię dla podglądu. Na- ciu klawisza kamery trafiłoby do systemu,
nie obrazem w formacie JPEG, które to da- stępnie metoda initCamera() ustawia gdzie spowodowałoby wygenerowanie in-
ne przekazuje na zewnątrz mechanizmem layout oraz pobiera obiekt SurfaceView, tencji CAMERA_BUTTON, co doprowadziło-
analogicznym do przekazywania stanu au- który posłuży nam do ustawienia pod- by do uruchomienia systemowej aplikacji
to focusa. Gotowy kod można znaleźć na Li- glądu. Tworzymy kontroler kamery, ini- Camera.
stingu 10. cjalizujemy podgląd oraz rejestrujemy Ikonkę stanu auto focusa podmieniamy
Mając gotowy kontroler, wystarczy wy- obiekty klas FocusChangeListener oraz w metodzie onCameraFocusChange(). Na-
korzystać go w naszej aplikacji. Zaczynamy PhotoCaptureListener. Na końcu rejestru- tomiast w onJpgPhotoCaptured() otrzy-
od utworzenia layoutu dla widoku kamery. jemy w głównym widoku OnKeyListener, mujemy dane zdjęcia. Przeciążoną meto-
Nie będzie on zbyt skomplikowany. Użyje- dzięki któremu będziemy informowani o dę onDestroy() używamy do zwolnienia
my RelativeLayout do rozłożenia naszych wciśnięciach klawiszy. I tutaj mała, aczkol- zasobów używanych przez kontroler ka-
kontrolek, których zresztą nie będzie wie- wiek istotna, uwaga. Abyśmy mogli być in- mery.
W tym momencie mamy gotową ak-
Listing 14. Klasa widoku zdjęcia
tywność do obsługi aparatu i możemy jej
użyć do zrobienia zdjęcia. Jest to też do-
public class FullscreenImageView extends View { bry moment, żeby pokazać konsekwen-
Bitmap image; cje złego zaprojektowania naszego kontro-
private int width; lera kamery. Podczas działania naszej apli-
private int height; kacji przejdź do ekranu startowego telefo-
private float xPos; nu, wciskając przycisk home, i uruchom
private float yPos; aplikację systemową Camera. Efektem bę-
dzie okienko z wiadomością o wystąpie-
public FullscreenImageView(Context context, AttributeSet attrs) { niu błędu. Dzieje się tak dlatego, że nasza
super(context, attrs); aktywność wciąż używa zasobu kamery.
} Owszem, zatrzymuje podgląd w momen-
cie, gdy aplikacja nie jest na widoku, jed-
public FullscreenImageView(Context context) { nak zwolnienie kamery następuje dopiero
super(context); w momencie zamknięcia aktywności. Ma-
} ło tego, system nie gwarantuje, że metoda
onDestroy() zostanie wywołana. W szcze-
public void setImage(Bitmap bitmap) { gólnych przypadkach system może zabić
image = bitmap; aktywność, kończąc jej cykl życia na meto-
width = bitmap.getWidth(); dzie onPause(). W takim przypadku użyt-
height = bitmap.getHeight(); kownik nie będzie miał dostępu do kame-
} ry i będzie zmuszony do restartu telefo-
nu, co jest nie do zaakceptowania. Pisząc
protected void onDraw(Canvas canvas) { aplikacje na Androida, musisz pamiętać,
super.onDraw(canvas); że jest to system wielozadaniowy, a Two-
if (image != null) { ja aplikacja może być jedynie jedną z wie-
canvas.drawBitmap(image, xPos, yPos, null); lu, które są w danej chwili uruchomione.
} Zadbaj więc o to, by dobrze obchodziła się
} z zasobami systemu. Dotyczy to również
} bardziej powszechnych zasobów, jak pamięć
czy moc obliczeniowa procesora. Kwestię
poprawienia kontrolera tak, aby uniknąć

108 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

tego typu problemów, pozostawiam Czy- PROVIDER), minimalnym odstępem cza- szyć zużycie baterii, np. wyłączając odbior-
telnikowi. sowym pomiędzy aktualizacjami pozycji nik GPS w chwili, kiedy użytkownik nie
oraz minimalną odległością, po której no- korzysta z naszej aplikacji. Gdybyśmy za-
Pozyskiwanie wa pozycja zostanie przekazana. Wywoła- pomnieli o wywołaniu removeUpdates(),
pozycji geograficznej nie tej metody następuje w onResume(), to nasz LocationListener wciąż otrzymy-
Skoro możemy już zrobić zdjęcie, zajmijmy natomiast w onPause() z pomocą metody wałby aktualizacje pozycji, wymuszając ak-
się drugą funkcją naszej aplikacji, czyli geotag- LocationManager .removeUpdates() in- tywność GPSa nawet po wyjściu z aplika-
gingiem. Chcemy do zdjęcia przypisać dane o formujemy usługę lokalizacyjną, że nie je- cji. Jeżeli natomiast w onDestroy() wynul-
pozycji geograficznej. Oczywiście pierwszym steśmy już zainteresowani aktualizacjami lujemy obiekty, do których nasz listener się
krokiem jest pozyskanie tych informacji. Do pozycji. Dzięki temu system może zmniej- odwołuje, to na wyjściu z aplikacji użyt-
tego celu wykorzystamy wbudowany w tele-
fon odbiornik GPS. Listing 15. Obsługa przewijania zdjęcia za pomocą ekranu dotykowego
Udostępniane przez Androida Location
API jest wyjątkowo proste w użyciu i wystar- private float lastX;
czy kilka linijek kodu, aby zyskać dostęp do private float lastY;
informacji o położeniu geograficznym tele-
fonu. Klasy związane z usługą lokalizacyjną private void touchStart(float x, float y) {
znajdują się w pakiecie android.location. lastX = x;
Nam potrzebne będą LocationManager lastY = y;
oraz LocationListener. Pierwsza umożli- }
wia dostęp do systemowej usługi lokaliza- private void touchUpdate(float x, float y) {
cyjnej, natomiast LocationListener jest lastX = x;
interfejsem, za pośrednictwem którego je- lastY = y;
steśmy informowani o zmianie położenia. }
Ponadto aplikacja korzystająca z GPSa mu-
si ustawić w swoim manifeście odpowied- private void updatePosition(final float x, final float y) {
nie uprawnienia, analogicznie jak dla kame- float xOff = x - lastX;
ry. Czyli dodajemy wpis Uses Permission z float yOff = y - lastY;
parametrem android.permission.ACCESS_
FINE_LOCATION. if (width > getWidth()) {
Tym razem podarujemy sobie opa- xPos += xOff;
kowywanie androidowego API i użyje- xPos = Math.min(xPos, 0);
my wyżej wspomnianych klas bezpo- xPos = Math.max(xPos, -(width - getWidth()));
średnio z poziomu naszej aktywności. }
Po pierwsze, musimy stworzyć instan- if (height > getHeight()) {
cję klasy LocationManager , za pośred- yPos += yOff;
nictwem której będziemy mogli korzy- yPos = Math.min(yPos, 0);
stać z usługi lokalizacyjnej. Do tego słu- yPos = Math.max(yPos, -(height - getHeight()));
ży metoda getSystemService(String) , }
której jako parametr podajemy nazwę żą- }
danej usługi, czyli w naszym przypadku
Context.LOCATION_SERVICE. Robimy to public boolean onTouchEvent(MotionEvent event) {
podczas tworzenia aktywności, czyli w me- float x = event.getX();
todzie onCreate(). Zajmuje się tym meto- float y = event.getY();
da initGps(), której kod źródłowy przed-
stawiony jest na Listingu 12. Tym razem switch (event.getAction()) {
będziemy chcieli obejść się łaskawie z za- case MotionEvent.ACTION_DOWN:
sobami, co oznacza, że ograniczymy się do touchStart(x, y);
korzystania z GPSu tylko gdy aktywność invalidate();
jest na pierwszym planie. Stąd obecność break;
przesłoniętych metod onResume() oraz
onPause(). Po opis cyklu życia aktywności case MotionEvent.ACTION_MOVE:
odsyłam do ramki. updatePosition(x, y);
Jak wspomniałem wcześniej, dane o invalidate();
zmianie pozycji geograficznej przekazywa- break;
ne są poprzez interfejs LocationListener,
a konkretnie jego metodę onLocationCh default:
anged(Location). Pozostałe trzy metody break;
służą do informowania o zmianie statusu }
usługi. Tworzymy obiekt implementujący touchUpdate(x, y);
ten interfejs i przekazujemy go jako para-
metr do metody LocationManager.reque return true;
stLocationUpdates(), wraz z typem usłu- }
gi lokalizacyjnej (LocationManager.GPS_

www.sdjournal.org 109
Programowanie Android

kownik zobaczy komunikat o błędzie po- Na szczęście zadanie jest bardzo pro- dzie aktualizujemy pozycję wyświetlanej
chodzącym z aplikacji, z której właśnie wy- ste. Tworzymy klasę FullscreenImageView bitmapy zgodnie ze zmianami położenia
szedł. Dlatego podkreślam konieczność do- dziedziczącą po View. Dodajemy pole ty- palca na ekranie dotykowym. Po aktuali-
kładnego sprzątania po sobie. Opisany po- pu Bitmap, które będzie przechowywa- zacji wywołujemy metodę invalidate(),
wyżej kod znajduje się na Listingu 13. Me- ło zdjęcie w postaci nadającej się do ryso- która informuje system, że życzymy sobie,
toda updateLocationIndicator() aktuali- wania. Następnie przesłaniamy metodę aby widok został odrysowany. Nie powo-
zuje kontrolkę tekstową wyświetlaną na onDraw(Canvas), w której odbywać się bę- duje to jednak natychmiastowego odświe-
podglądzie zdjęcia i zawierającą dane o ak- dzie rysowanie. Metody służące do ryso- żenia ekranu w momencie wywołania me-
tualnej pozycji. wania są udostępniane przez obiekt, któ- tody. To system zdecyduje, kiedy opera-
ry dostajemy jako argument onDraw(). Sam cja ta zostanie wykonana. A jej wykona-
Wyświetlanie zdjęcia kod rysujący zdjęcie to tylko sprawdzenie, nie oznacza wywołanie przez system me-
Zgodnie z początkowym opisem funkcjo- czy bitmapa istnieje, i wywołanie metody tody onDraw().
nalności, użytkownik powinien mieć moż- drawBitmap(), która ją rysuje w zadanej po- Drobna uwaga. Metoda invalidate()
liwość obejrzenia zdjęcia w pełnej krasie, zycji. Ostatni parametr służy do przekaza- może zostać wywołana jedynie z głów-
zanim zdecyduje się je zapisać. W tym ce- nia dodatkowych informacji o sposobie ry- nego wątku aplikacji. Jeżeli chcemy za-
lu skorzystamy z niskopoziomowych opera- sowania, za pomocą którego możemy np. żądać odrysowania widoku z jakie-
cji rysowania i stworzymy własną klasę wi- używać filtrów. My go ignorujemy, przeka- goś innego wątku, należy skorzystać z
doku. zując null. Kod naszej klasy widoku przed- postInvalidate() .
Chcemy, aby nasz widok wyświetlał stawiony jest na Listingu 14. Mamy już własną klasę widoku, teraz na-
zdjęcie w jego rzeczywistej rozdzielczości Większa część kodu naszej klasy to obsłu- leży tylko dodać aktywność, która ten wi-
oraz umożliwiał użytkownikowi przewi- ga zdarzeń pochodzących z ekranu dotyko- dok wykorzysta. Na szczęście twórcy sys-
janie w wypadku, gdy zdjęcie jest większe wego. można go znaleźć na Listingu 15. temu przewidzieli potrzebę obsługi nie-
niż powierzchnia ekranu. Oprócz tego, na Wcześniej używaliśmy w podobnym celu standardowych widoków w layoutach. Aby
ekranie znajdą się dwa przyciski, z których listenerów, jednak w tym przypadku mo- użyć własnej klasy, należy w pliku XML
jeden powoduje zapisanie zdjęcia, a drugi żemy przesłonić metodę onTouchEvent() użyć pełnej nazwy klasy jako nazwy ele-
jego skasowanie. (analogicznie możemy postąpić dla klawi- mentu.
Po wciśnięciu któregokolwiek z nich ak- szy i trackballa). Pamiętajmy, że aby nasz Możemy również definiować własne
tywność jest zamykana i użytkownik powra- widok otrzymywał zdarzenia, musi mieć atrybuty. Aby nasza klasa widoku mo-
ca do podglądu kamery. ustawiony atrybut Focusable. W tej meto- gła być w ten sposób wykorzystywana,
musimy zadeklarować konstruktor, któ-
Listing 16. Layout dla ekranu zatwierdzania zdjęcia. ry przyjmuje jako parametry obiekt klasy
Context oraz AttributeSet . Ten drugi słu-
<?xml version="1.0" encoding="utf-8"?> ży do przekazywania atrybutów zdefinio-
<RelativeLayout wanych w pliku XML. W tej sytuacji kod
android:layout_width="fill_parent" nowej aktywności sprowadza się do usta-
android:layout_height="fill_parent" wienia layoutu oraz obsługi dwóch przyci-
xmlns:android="http://schemas.android.com/apk/res/android" sków. Gotowy layout przedstawiono na Li-
android:id="@+id/RelativeLayout" stingu 16.
>
<Button Przechowywanie danych
android:layout_width="wrap_content" Do szczęścia brakuje już nam tylko zapisu
android:layout_height="wrap_content" danych. Możemy tu niejako wyróżnić dwa
android:layout_alignParentLeft="true" aspekty problemu. Po pierwsze, zapisanie pli-
android:layout_alignParentTop="true" ku ze zdjęciem. Po drugie, zapisanie danych
android:id="@+id/buttonSave" o lokalizacji oraz powiązanie ich z konkret-
android:text="@string/photoSave" nym zdjęciem.
></Button> Problem pierwszy jest banalnie prosty
<Button do rozwiązania. Android udostępnia stan-
android:layout_width="wrap_content" dardowe klasy wejścia/wyjścia z JavySE. W
android:layout_height="wrap_content" wypadku zapisu na kartę pamięci, musi-
android:layout_alignParentTop="true" my tylko znać do niej ścieżkę oraz upewnić
android:layout_alignParentRight="true" się, że karta jest podmontowana w syste-
android:text="@string/photoDiscard" mie. Wywołanie Environment.getExterna
android:id="@+id/buttonDelete" lStorageDirectory() zwraca nam obiekt
></Button> File zawierający właśnie tę ścieżkę. Nato-
<org.sdjournal.galbum.viewer.FullscreenImageView miast stan karty możemy pozyskać za po-
android:layout_below="@id/buttonDelete" mocą metody Environment.getExternal
android:layout_width="wrap_content" StorageState(). Dalej możemy postępo-
android:layout_height="wrap_content" wać tak, jak byśmy pisali przy użyciu desk-
android:id="@+id/photoView" topowej Javy.
android:focusable="true" Drugi aspekt jest nieco bardziej intere-
></org.sdjournal.galbum.viewer.FullscreenImageView> sujący, ze względu na to, że nie tylko mu-
</RelativeLayout> simy zapisać dane o lokalizacji, ale rów-
nież skojarzyć je z konkretnym zdjęciem.

110 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

Można by tu wymyślić multum potencjal-


nych rozwiązań, jednak my nie będziemy Listing 17. Zapis zdjęcia na kartę pamięci oraz jego wczytywanie i tworzenie obiektu Bitmap
odkrywać na nowo Ameryki. Twórcy An-
droida również postąpili podobnie i sys- public class Photo {
tem ten udostępnia mechanizm zapisy- private String mFilename;
wania danych poprzez zastosowanie bazy private String mTitle;
danych opartej na SQLite. Skorzystamy z private Bitmap mImage;
tego właśnie rozwiązania. Nasza baza da- private byte[] mJpgData;
nych będzie zawierać zaledwie jedną ta-
belę, w której dla każdego zdjęcia zapisa- boolean saveToFile(String filename) {
ne będą: identyfikator (klucz główny), na- boolean success = false;
zwa pliku, tytuł, szerokość i długość geo-
graficzna oraz wysokość. if (mJpgData != null) {
Do obsługi naszej bazy utworzymy dwie success = true;
klasy pomocnicze. PhotoManager będzie za- String sdcardState = Environment.getExternalStorageState();
rządzał bazą danych oraz oferował możli-
wość dodawania i pobierania zdjęć z bazy. if (sdcardState.equals(Environment.MEDIA_MOUNTED)) {
Natomiast klasa Photo będzie reprezentowa- try {
ła konkretne zdjęcie oraz zajmowała się zapi- File sdCardDir = Environment.getExternalStorageDirectory();
sem i odczytem danych konkretnego zdjęcia z File photoFile = new File(sdCardDir, filename);
systemu plików. FileOutputStream fos = new FileOutputStream(photoFile);
Klasa Photo nie wymaga specjalnego ko- BufferedOutputStream bos = new BufferedOutputStream(fos);
mentarza. Posiada pola do przechowywa- bos.write(mJpgData);
nia pozycji geograficznej, nazwy pliku od- } catch (IOException e) {
powiadającego zdjęciu oraz samych danych e.printStackTrace();
zdjęcia. Udostępnia też metodę do pobra- success = false;
nia obiektu typu Bitmap, który można wy- }
świetlić na ekranie. } else {
Ze względu na ograniczone zasoby pa- success = false;
mięci, bitmapa oraz dane zdjęcia są łado- }
wane tylko na żądanie. Ponadto klasa udo- } else {
stępnia metody do zwolnienia zasobów zaj- success = false;
mowanych przez te dane. Może wyrażenie }
zwolnienie zasobów jest nieco nie na miej-
scu, w końcu mamy do czynienia z Javą. return success;
Jednak przypisanie tym polom wartości }
null zapewni, że odśmiecacz będzie mógł
odzyskać pamięć przez nie zajmowaną boolean saveToFile() {
(oczywiście pod warunkiem, że nie korzy- if (mFilename != null) {
stamy z nich w innym miejscu programu). return saveToFile(mFilename);
Należy pamiętać, że zdjęcie w swojej orygi- } else {
nalnej rozdzielczości zajmuje sporo pamię- return false;
ci. Na Listingu 17 znajduje się kod źródło- }
wy metod odpowiadających za zapis i od- }
czyt zdjęcia.
Klasa PhotoManager jest zdecydowanie public void initBitmap() {
ciekawsza, gdyż zajmuje się obsługą bazy if (mImage == null) {
danych. Dla uproszczenia wszystkie me- if (mJpgData != null) {
tody są statyczne, a metoda init() wy- mImage = BitmapFactory.decodeByteArray(mJpgData, 0,
woływana jest na starcie naszej aplika- mJpgData.length);
cji i zajmuje się inicjalizacją bazy. Nato- } else {
miast release() zamyka połączenie z ba- String sdcardState = Environment.getExternalStorageState();
zą i jest wywoływane przy wyjściu z aplika-
cji. Do tworzenia oraz pozyskiwania obiek- if (sdcardState.equals(Environment.MEDIA_MOUNTED)) {
tu reprezentującego bazę danych posługuje- File sdCardDir = Environment.getExternalStorageDirectory();
my się klasą pomocniczą DatabaseHelper, File photoFile = new File(sdCardDir, mFilename);
która dziedziczy po SQLiteOpenHelper. mImage = BitmapFactory.decodeFile(photoFile.getAbsolutePath());
Jej kod jest przedstawiony na Listingu }
18. Wykorzystujemy przesłonięte metody }
onCreate() oraz onUpgrade() do wyko- }
nywania akcji w momencie tworzenia ba- }
zy danych oraz jej aktualizacji. Stosujemy }
uproszczony jednoargumentowy konstruk-
tor, który z kolei wywołuje konstruktor kla-

www.sdjournal.org 111
Programowanie Android

sy nadrzędnej z nazwą oraz numerem wer- Wspomniany numer wersji możemy wy- szej aplikacji zmienia strukturę bazy da-
sji bazy. korzystać w sytuacji, gdy nowa wersja na- nych i użytkownik starej wersji ją zaktu-
alizuje. Wtedy mamy możliwość dosto-
Listing 18. Klasa pomocnicza korzystająca z SQLiteOpenHelper sowania struktury bez utraty danych. W
private static class DatabaseHelper extends SQLiteOpenHelper { wypadku naszej aplikacji nas to nie inte-
DatabaseHelper(Context context) { resuje i przy aktualizacji kasujemy naszą
super(context, DATABASE_NAME, null, DATABASE_VERSION); jedyną tabelę i wywołujemy onCreate().
} Aby otworzyć (lub stworzyć, jeśli nie ist-
public void onCreate(SQLiteDatabase db) { nieje) bazę danych, korzystamy z metody
db.execSQL("CREATE TABLE " + PhotoManager.PHOTOS_TABLE_NAME + " (" getWritableDatabase() naszej klasy po-
+ PhotoManager.COLNAME_ID + " INTEGER PRIMARY KEY," mocniczej.
+ PhotoManager.COLNAME_TITLE + " TEXT," W rezultacie dostaniemy obiekt klasy
+ PhotoManager.COLNAME_FILE_NAME + " TEXT," SQLiteDatabase, który udostępnia metody
+ PhotoManager.COLNAME_ALTITUDE + " NUMERIC," do operowania na bazie, takie jak query()
+ PhotoManager.COLNAME_LATITUDE + " NUMERIC," oraz insert().
+ PhotoManager.COLNAME_LONGITUDE + " NUMERIC" Funkcjonalność związana z ładowa-
+ ");"); niem oraz zapisywaniem danych zdjęć
} do bazy zaimplementowana jest w me-
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { todach loadPhoto() (Listing 19) oraz
db.execSQL("DROP TABLE IF EXISTS " + PhotoManager.PHOTOS_TABLE_NAME); storePhoto() (Listing 20). Natomiast
onCreate(db); metoda photoCount() zwraca liczbę wpi-
} sów w bazie. Przyjrzyjmy się bliżej meto-
} dzie loadPhoto(). Jako parametr przyj-
muje ona indeks zdjęcia, którego dane
Listing 19. Metoda loadPhoto() służąca do odczytu danych z bazy ma pobrać, zwraca natomiast obiekt kla-
private static SQLiteDatabase mDb; sy Photo zainicjalizowany wartościami po-
private static Cursor mPhotoData; branymi z bazy. Do pozyskania danych uży-
private static boolean mPhotoDataInvalid; wana jest wspomniana wcześniej meto-
public static Photo loadPhoto(int index) { da SQLiteDatabase.query(). W związku
if (mPhotoData == null) { z tym, że nasze zapytanie jest bardzo pro-
mPhotoData = mDb.query(PHOTOS_TABLE_NAME, new String[] { ste (chcemy pobrać zawartość całej tabeli),
COLNAME_ID, COLNAME_FILE_NAME, COLNAME_TITLE, to jako parametry przekazujemy tylko na-
COLNAME_LATITUDE, COLNAME_LONGITUDE }, null, null, null, zwę tabeli oraz listę nazw kolumn. Pozosta-
null, null); łe parametry mają wartość null, co ozna-
} cza, że nie korzystamy z dodatkowej funk-
if (mPhotoDataInvalid) { cjonalności, jaką oferuje SQL (np. klauzula
synchronized (mDb) { where, sortowanie). Tak naprawdę nie mu-
mPhotoData.requery(); simy nawet podawać listy kolumn, w ta-
mPhotoDataInvalid = false; kim wypadku zwrócona zostałaby zawar-
} tość wszystkich.
} W normalnych warunkach raczej nie
Photo photo = null; powinno się pobierać całej zawartości ta-
boolean isValid = mPhotoData.moveToPosition(index); beli, jednak baza danych dla naszej przy-
if (isValid) { kładowej aplikacji nie będzie miała wiel-
String filename = mPhotoData.getString(mPhotoData kich rozmiarów. Metoda query() zwraca
.getColumnIndexOrThrow(PhotoManager.COLNAME_FILE_NAME)); obiekt typu Cursor (zwany dalej po pro-
String title = mPhotoData.getString(mPhotoData stu kursorem), który przechowuje wszyst-
.getColumnIndexOrThrow(PhotoManager.COLNAME_TITLE)); kie zwrócone wiersze tabeli oraz udostęp-
int id = mPhotoData.getInt(mPhotoData nia interfejs do pobierania danych z dowol-
.getColumnIndexOrThrow(PhotoManager.COLNAME_ID)); nego wiersza.
double lon = mPhotoData.getDouble(mPhotoData Aby nie powtarzać tego samego zapyta-
.getColumnIndexOrThrow(PhotoManager.COLNAME_LONGITUDE)); nia za każdym razem, zapamiętujemy wy-
double lat = mPhotoData.getDouble(mPhotoData nik do dalszego wykorzystania. Aby móc
.getColumnIndexOrThrow(PhotoManager.COLNAME_LATITUDE)); pobierać dane z danego wiersza, należy
photo = new Photo(); przy pomocy metody Cursor.moveToPos
photo.setFilename(filename); ition(int) ustawić kursor na odpowied-
photo.setTitle(title); niej pozycji.
photo.setId(id); Następnie możemy skorzystać z jed-
photo.setLatitude(lat); nej z metod get, które zwracają dane róż-
photo.setLongitude(lon); nych typów (np. getString(), getInt(),
} getDouble() itd.). Jako parametr meto-
return photo; dy te przyjmują indeks kolumny, z której
} chcemy pobrać dane. Z kolei do pobrania
tego indeksu dla kolumny o podanej na-

112 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

zwie służy metoda Cursor.getColumnInd Wynikiem wykonania takiego zapytania Wyświetlanie mapy
exOrThrow(). jest pojedyncza wartość (czyli tabelę za- za pomocą Google Maps
Dodawaniem wpisów do bazy danych zaj- wierającą jeden wiersz i jedną kolumnę) Android, będąc produktem Google, ma do-
muje się metoda storePhoto(). Robi ona będąca liczbą wierszy w naszej tabeli. Te- skonałe wsparcie dla jednej z najpopular-
użytek z metody SQLiteDatabase.insert(), go typu zapytania możemy zoptymalizo- niejszych usług tego giganta, czyli Google
której należy jako parametry podać nazwę wać poprzez skompilowanie ich do posta- Maps. Pakiet com.google.android.maps
tabeli, nazwę kolumny, której zostanie przy- ci obiektów klasy SQLiteStatement, któ- zawiera klasy, przy użyciu których moż-
pisana wartość NULL w wypadku dodawa- re możemy zachować do ponownego wy- na korzystać z tego serwisu. Jednak za-
nia pustego wiersza (ignorujemy ten para- korzystania. nim zagłębimy się w przykłady użycia
metr, przekazując null) oraz wartości ko- Wykonanie tak skompilowanego zapyta- tych klas, musimy wykonać kilka dodat-
lumn do dodania. nia następuje w momencie wykonania me- kowych czynności. Po pierwsze, od wersji
Wynikiem tej metody jest indeks nowe- tody simpleQueryForLong(), która zwra- 1.5 systemu, Google Maps API jest osobną
go wiersza lub -1 w wypadku niepowodze- ca rezultat w postaci numerycznej (sim- biblioteką i korzystanie z niej wymaga do-
nia operacji. Dane do dodania przekazujemy pleQueryForString() zwraca wartość tek- datkowego wpisu w manifeście. Otwiera-
w obiekcie typu ContentValues, który jest stową). my AndroidManifest.xml na zakładce Ap-
zbiorem danych w postaci klucz-wartość. Pozostałe metody klasy PhotoManager po- plication i w sekcji Application Nodes do-
Dane dodajemy, korzystając z jednej z prze- wstały na użytek funkcji zatwierdzania no- dajemy nowy element typu Uses Libra-
ciążonych metod put. wo wykonanego zdjęcia. Jest to prymitywny ry i ustawiamy jego wartość na jedyną z
Wspomniałem wcześniej, że obiekt sposób przekazania danych zdjęcia z aktyw- dostępnych opcji, czyli com.google.andro-
Cursor zawierający rezultat zapytania o ności kamery do aktywności podglądu i za- id.maps. Ponadto we właściwościach pro-
zawartość tabeli zapamiętamy do ponow- twierdzania nowego zdjęcia. jektu, w sekcji Android, wybieramy Go-
nego wykorzystania. Jednak jego zawar- Ta pierwsza dodaje dane obraz- ogle APIs w okienku z wyborem platfor-
tość deaktualizuje się w momencie doda- ka jpg oraz lokalizacji poprzez meto- my docelowej. Druga kwestia wiąże się z
nia nowego wiersza do bazy. Nie oznacza dę addPendingPhoto(), która utwo- korzystaniem z samej usługi. Podobnie jak
to wcale, że musimy ponownie tworzyć rzy dla nich obiekt Photo i zacho- w wypadku aplikacji webowej, potrzebuje-
zapytanie. Możemy odświeżyć dane, wy- wa go dopóki nie zostanie wywoła- my klucza API dla naszej aplikacji. Różni-
wołując metodę Cursor.requery(), któ- na metoda discardPendingPhoto() lub ca jest taka, że w przypadku aplikacji an-
ra wykona to samo zapytanie, które zo- storePendingPhoto(). Docelowa aktyw- droidowej klucz jest generowany dla cer-
stało użyte do utworzenia naszego kurso- ność używa metody getPendingPhoto() , tyfikatu, którym jest podpisana nasza
ra. Taka operacja jest szybsza niż wywo- aby pobrać nowe zdjęcie i pozyskać z niego aplikacja. Proces generowania klucza jest
łanie query(), które tworzy nowy kursor. bitmapę do wyświetlenia na ekranie. szczegółowo opisany na stronie Google
Jednak my nie będziemy aktualizować go
natychmiast, tylko dopiero w momencie, Listing 20. Metody do zapisu zdjęcia w bazie i sprawdzenia ich liczby
kiedy zajdzie taka potrzeba, czyli w me-
todzie loadPhoto(). Jednak aby nieaktu- public static void storePhoto(Photo photo) {
alne dane nie zajmowały pamięci, deak- ContentValues values = new ContentValues(5);
tywujemy kursor metodą deactivate() i values.put(COLNAME_FILE_NAME, photo.getFilename());
zaznaczamy ten fakt, ustawiając pole ty- values.put(COLNAME_TITLE, photo.getTitle());
pu boolean. W stanie nieaktywnym wszel- values.put(COLNAME_LATITUDE, photo.getLatitude());
kie operacje na kursorze zakończą się nie- values.put(COLNAME_LONGITUDE, photo.getLongitude());
powodzeniem aż do momentu wywołania values.put(COLNAME_ALTITUDE, photo.getAltitude());
requery() .
Ostatnią metodą operującą na bazie da- long newRow = mDb.insert(PHOTOS_TABLE_NAME, null, values);
nych jest photoCount(), która zwraca liczbę if (newRow == -1) {
zapisanych zdjęć. Najprostszym sposobem // wstawienie nowego rekordu nie powiodło się
na zaimplementowanie tej funkcji jest wy- }
wołanie na kursorze metody getCount(),
która zwraca liczbę jego wierszy. Nie jest if (mPhotoData != null) {
to jednak najlepsze rozwiązanie, gdyż nasz synchronized (mDb) {
kursor może być w tym momencie nieak- mPhotoData.deactivate();
tywny, a my chcemy go aktywować dopiero mPhotoDataInvalid = false;
w chwili, kiedy rzeczywiście potrzebujemy }
danych w nim zawartych. }
Ponadto zadowolenie się tym rozwią- }
zaniem pozbawiłoby mnie szansy zapre-
zentowania kolejnej ciekawej funkcji ofe- public static int photoCount() {
rowanej przez androidowe API. Osobom if (countStatement == null) {
zaznajomionym z SQL na pewno nieob- countStatement = mDb.compileStatement("select count(" + COLNAME_ID
ca jest funkcja count(), która służy wła- + ") from " + PHOTOS_TABLE_NAME);
śnie do zliczania wierszy. Dla naszej bazy, }
zapytanie SQL z jej użyciem wyglądałoby
następująco: return (int) countStatement.simpleQueryForLong();
}
select count(id) from photos

www.sdjournal.org 113
Programowanie Android

(http://code.google.com/intl/pl/android/add- stania z Internetu z poziomu aplikacji. W manifeście, z wartością android.permissi


ons/google-apis/mapkey.html). Ostatnią rze- sposób opisany już we wcześniejszej czę- on.INTERNET .
czą jest ustawienie uprawnień dla korzy- ści artykułu dodajemy odpowiedni wpis w Możemy wreszcie rozpocząć pracę nad
wykorzystaniem map w naszej aplikacji.
Listing 21. Aktywność korzystająca z mapy Chcemy, aby po kliknięciu w przycisk Gal-
lery oczom użytkownika ukazała się mapa
public class MapGalleryActivity extends MapActivity { z markerami oznaczającymi miejsca zro-
private MapController mMapController; bienia zdjęć. Mapa wycentrowana będzie
private final static String MAPS_API_KEY = "TWÓJ KLUCZ DO MAPS API"; na ostatnio wykonanym zdjęciu. Tworzy-
my nową klasę aktywności, jednak tym ra-
protected void onCreate(Bundle icicle) { zem dziedziczymy po MapActivity, któ-
super.onCreate(icicle); ra jest częścią biblioteki i zajmuje się ma-
ło nas interesującymi szczegółami związa-
MapView mapView = new MapView(this, MAPS_API_KEY); nymi z obsługą usługi Google Maps. Dru-
mapView.setClickable(true); gą ważną klasą pomocniczą udostępnianą
setContentView(mapView); przez bibliotekę jest MapView. Jak sama na-
zwa wskazuje, służy do wyświetlania mapy.
int c = PhotoManager.photoCount(); Ponadto,mocno polega na wnętrznościach
Photo lastPhoto = PhotoManager.loadPhoto(c -1); MapActivity, i w rezultacie może być uży-
GeoPoint lastPoint = new GeoPoint( wana wyłącznie w parze z tą klasą. Wyko-
(int) (lastPhoto.getLatitude() * 1e6), rzystanie obu tych klas przedstawione jest
(int) (lastPhoto.getLongitude() * 1e6)); na Listingu 21. Tym razem odpuścimy so-
bie definiowanie layoutu w pliku XML i
mMapController = mapView.getController(); zrobimy to w kodzie. Tworzymy obiekt ty-
mMapController.setCenter(lastPoint); pu MapView, podając konstruktorowi naszą
mMapController.setZoom(16); aktywność oraz klucz API. Następnie włą-
mapView.setBuiltInZoomControls(true); czamy dla niego obsługę kliknięć poprzez
wywołanie metody setClickable(true).
Drawable bitmapDrawable = getResources().getDrawable(R.drawable.photo_ Jest to konieczne, aby użytkownik miał
icon); możliwość przesuwania mapy.
PhotoOverlay overlay = new PhotoOverlay(bitmapDrawable); Następnie ustawiamy widok dla ak-
tywności, przekazując go do metody
overlay.setOnFocusChangeListener(new OnFocusChangeListener() { setContentView(). Po tych czynnościach
public void onFocusChanged(ItemizedOverlay overlay, OverlayItem uruchomienie naszej nowej aktywności
newFocus) { spowoduje wyświetlenie mapy, którą użyt-
if (newFocus != null) { kownik może przesuwać.
mMapController.animateTo(newFocus.getPoint()); Niestety, domyślna skala mapy oraz brak
} możliwości jej zmiany oraz fakt, że najpraw-
} dopodobniej jest ona wycentrowana gdzieś
}); daleko, raczej nie zrobi pozytywnego wraże-
nia. I tu na ratunek przybywa klasa o jakże
overlay.setDoubleTapListener(new PhotoOverlay.DoubleTapListener() { adekwatnej nazwie MapController. Obiekt
public void onDoubleTap(int index) { tego typu pozyskujemy z instancji kla-
lauchPhotoView(index); sy MapView poprzez wywołanie jej metody
} getController().
}); A kontroler udostępnia tak przydatne me-
mapView.getOverlays().add(overlay); tody jak:
}
• setZoom(int) do ustawiania skali (za-
protected boolean isRouteDisplayed() { kres od 1 do 21 włącznie);
return false; • zoomIn() oraz zoomOut() do płynnej
} zmiany skali o jeden stopień;
• setCenter(GeoPoint) do wycentrowa-
private void lauchPhotoView(int photoIndex) { nia mapy na danym punkcie.
ComponentName activityName = new ComponentName(
"org.sdjournal.galbum", Tym samym możemy zrealizować jed-
"org.sdjournal.galbum.PhotoViewActivity"); ną z zaplanowanych funkcji, tzn. wycen-
Intent intent = new Intent(); trować mapę na lokalizacji najświeższe-
intent.setComponent(activityName); go zdjęcia. Sytuację, w której nie mamy
intent.putExtra(PhotoViewActivity.INTENT_EXTRA_PHOTO_TO_VIEW, photoIndex); w bazie ani jednego zdjęcia ignorujemy,
startActivity(intent); jako niegodną naszej uwagi. Należałoby
} się zastanowić, czy w takiej sytuacji war-
} to w ogóle dawać użytkownikowi możli-
wość uruchomienia mapy. W końcu po co

114 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

wchodzić do pustej galerii? Po zignorowa- tworzeniem obiektów zawartych w war- i dodać naszą warstwę, korzystając z meto-
niu tego niewygodnego problemu pobie- stwie. Jako parametr dostajemy numer ele- dy add().
ramy zdjęcie i tworzymy obiekt GeoPoint mentu do stworzenia. My ładujemy z ba- Użytkownik może już zobaczyć na ma-
odpowiadający jego pozycji, a następnie zy danych zdjęcie odpowiadające temu nu- pie miejsca wykonania zdjęć. Należy jesz-
przekazujemy go do metody setCenter(). merowi, a następnie pobieramy jego pozy- cze dać mu możliwość obejrzenia dowol-
Warto w tym miejscu zwrócić uwagę na cję i zapisujemy w obiekcie GeoPoint. Lo- nego z nich. Zanim zaimplementujemy tę
fakt, że Maps API do określania pozy- kalizację, tytuł oraz skrócony opis prze- funkcję, proponuję dodać jeszcze jeden,
cji korzysta z mikrostopni, stąd mnoże- kazujemy do konstruktora obiektu klasy aczkolwiek miły dla użytkownika szcze-
nie i konwersja przy tworzeniu obiektu OverlayItem, czyli naszego elementu na gół. Sprawimy, że kiedy wskaże on któryś z
GeoPoint. Zupełnie przy okazji ustawia- warstwie. markerów, widok mapy płynnie się na nim
my skalę na jakąś sensowną wartość (np. Mamy już gotową klasę, która umożliwi wyśrodkuje. Do tego celu wykorzystamy
16, która nie tylko jest wartością sensow- nam wyświetlenie zdjęć na mapie. Tworzy- interfejs OnFocusChangeListener zawar-
ną, ale również elegancką). Oczywiście my więc nowy obiekt klasy PhotoOverlay, ty w klasie ItemizedOverlay. Implemen-
ustawienie skali na stałe bez możliwości przekazując mu domyślny marker. My uży- tując jego metodę onFocusChanged() oraz
jej zmiany przez użytkownika nie jest za- jemy do tego celu niewielką ikonkę przed- rejestrując taki obiekt metodą setOnFocu
lecane. Należy dodać funkcję przybliża- stawiającą zdjęcie, którą załadujemy ze sChangeListener(), będziemy informo-
nia i oddalania mapy. Można dodać wła- spakowanych zasobów przy pomocy Reso wani o fakcie zmiany aktywnego elementu
sne kontrolki, które do tego posłużą, gdyż urces.getDrawable(int). Gotowy obiekt warstwy. Taka zmiana następuje właśnie w
MapView dziedziczy po ViewGroup, a więc warstwy może zostać dodany do mapy ce- momencie wskazania markera palcem. Ja-
może zawierać inne widoki (patrz ram- lem wyświetlenia danych w nim zawartych. ko parametry dostaniemy obiekt warstwy
ka Android: GUI). My jednak skorzysta- Aby to osiągnąć, należy pobrać listę warstw oraz element, którego dotyczy zdarzenie.
my z wbudowanego mechanizmu ofero- za pomocą metody MapView.getOverlays() Pobieramy pozycję markera i przekazuje-
wanego przez MapView poprzez wywoła-
nie setBuiltInZoomControls() z parame- Listing 22. Klasa odpowiedzialna za obsługę warstwy danych ze zdjęciami
trem true.
Teraz wystarczy już tylko zaznaczyć na public class PhotoOverlay extends ItemizedOverlay<OverlayItem> {
mapie miejsca wykonania zdjęć. MapView public PhotoOverlay(Drawable defaultMarker) {
daje nam możliwość dodawania warstw super(defaultMarker);
zawierających dodatkowe informacje, któ- boundCenter(defaultMarker);
re są rysowane na mapie. Klasą bazową dla populate();
tego typu obiektów jest Overlay, jednak }
biblioteka oferuje dla naszej wygody kla- protected OverlayItem createItem(int i) {
sy ItemizedOverlay oraz OverlayItem. Photo photo = PhotoManager.loadPhoto(i);
Tworzymy nową klasę PhotoOverlay dzie- OverlayItem item = null;
dziczącą po ItemizedOverlay, która bę- if (photo != null) {
dzie reprezentowała warstwę zdjęć na GeoPoint point = new GeoPoint((int) (photo.getLatitude() * 1e6),
mapie (Listing 22). Konstruktor tej kla- (int) (photo.getLongitude() * 1e6));
sy przyjmuje jako parametr obiekt ty- String title = photo.getTitle();
pu Drawable, który posłuży jako marker String snippet = photo.getFilename();
dla obiektów na tej warstwie. Wywołuje- item = new OverlayItem(point, title, snippet);
my konstruktor klasy nadrzędnej, przeka- }
zując domyślny marker. Aby mógł on zo- return item;
stać poprawnie narysowany, należy okre- }
ślić jego granice. Możemy tego dokonać public int size() {
przy pomocy metod boundCenter() lub return PhotoManager.photoCount();
boundCenterBottom() . Pierwsza spowo- }
duje, że marker będzie wyśrodkowany na private int lastTap = -1;
pozycji, którą reprezentuje. public interface DoubleTapListener {
W drugim przypadku marker będzie public void onDoubleTap(int index);
przypięty do pozycji środkiem dolnej kra- }
wędzi. Ostatnią operacją w konstruktorze private DoubleTapListener tapActionListener;
jest wywołanie metody populate(), któ- public void setDoubleTapListener(DoubleTapListener l) {
ra uruchomi proces zapełniania warstwy tapActionListener = l;
danymi. }
Mechanizm dodawania danych do war- protected boolean onTap(int index) {
stwy polega na dwóch metodach, któ- if (index == lastTap) {
re musimy zaimplementować. Pierwszą z tapActionListener.onDoubleTap(index);
nich jest size(), która ma zwrócić liczbę }
elementów na warstwie, jest ona wywoły- lastTap = index;
wana przez populate() jeden raz, a jej re- return true;
zultat jest zapamiętywany. W przypadku }
naszej aplikacji zwracana jest liczba zdjęć }
zapamiętanych w bazie danych. Drugą me-
todą jest createItem(int) ,i zajmuje się

www.sdjournal.org 115
Programowanie Android

my ją do metody MapController.animate nizm informowania świata zewnętrznego nimi parametrami (Listing 23). Po drugie,
To(), w wyniku czego oczom użytkowni- o zajściu podwójnego tapnięcia. Do tego ce- zdjęcie jest pobierane z PhotoManagera na
ka ukaże się animacja przejścia do nowe- lu służy interfejs DoubleTapListener, któ- podstawie indeksu przekazanego w inten-
go punktu. ry zawiera definicję jednej tylko metody, cji. Po trzecie, posłuży nam do nieco bliż-
Po tej krótkiej dygresji wracamy do głów- onDoubleTap(), która jako parametr otrzy- szego poznania mechanizmu zmian konfi-
nego tematu, czyli wyświetlenia żądane- muje indeks elementu, któremu przyda- guracji podczas działania aplikacji.
go zdjęcia. Aby móc zrealizować tę funk- rzyło się interesujące nas zdarzenie. Z po-
cję, należy najpierw określić, w jaki spo- ziomu MapGalleryActivity, za pomo- Wykrywanie orientacji telefonu
sób użytkownik ma poinformować apli- cą metody setDoubleTapListener() reje- Dla wygody użytkownika chcielibyśmy, aby
kację o swoich zamiarach. Skoro wskaza- strujemy nowy listener, którego zadaniem aktywność wyświetlająca zdjęcie dostoso-
nie markera powoduje zmianę focusa i wy- jest uruchomienie nowej aktywności, któ- wywała się do orientacji ekranu. Jak wspo-
środkowanie na nim mapy, to załóżmy, że ra wyświetli żądane zdjęcie. Zajmuje się mniałem we wcześniejszej części artykułu,
ponowienie tej akcji na aktualnym marke- tym metoda lauchPhotoView(int). w wypadku zmiany konfiguracji (np. orien-
rze spowoduje uruchomienie aktywności Tym razem korzystamy z intencji do tacji ekranu) aktywność jest restartowa-
wyświetlającej to zdjęcie. W celu zaimple- przekazania nowej aktywności numeru na. Takie zachowanie nam nie odpowiada,
mentowania takiego zachowania przesło- zdjęcia do wyświetlenia. Zajmuje się tym gdyż ponowne ładowanie zdjęcia jest dość
nimy metodę onTap(int) w naszej klasie metoda Intent.putExtra(String, int). kosztowne.
PhotoOverlay. Dzięki temu będziemy in- Tę dodatkową informację można odczy- Ponadto chcemy, aby do określenia
formowani o fakcie tapnięcia przez użyt- tać za pomocą metody getIntExtra(), orientacji został użyty wbudowany w te-
kownika w marker. Parametr tej metody in- co też robi nasza nowa aktywność, lefon sensor. Zapewne niektórych zdziwi,
formuje nas, którego z elementów dotyczy PhotoViewActivity. O tej klasie nie będę jak prosto osiągnąć taki efekt. Jedyne, co
akcja. Zapamiętujemy tę informację, i jeże- się specjalnie rozpisywał. Jest bardzo po- musimy zrobić, to zmodyfikować w ma-
li następnym razem otrzymamy ten sam dobna do PhotoInspectActivity, z kilko- nifeście kilka ustawień dla naszej aktyw-
indeks co zapamiętany, to znaczy, że użyt- ma drobnymi różnicami. Po pierwsze, nie ności. Wybieramy w Application Nodes ak-
kownik dwa razy z rzędu wskazał ten sam wyświetla nic oprócz zdjęcia, a ponadto wy- tywność i w polu Screen orientation wybie-
marker. Jako że z poziomu PhotoOverlay korzystuje tryb pełnoekranowy. Efekt ten ramy sensor, dzięki czemu system automa-
nie za bardzo mamy możliwość wywoła- osiągamy dzięki ustawieniu odpowiedniej tycznie wykryje zmiany położenia telefo-
nia nowej aktywności (a przynajmniej nie flagi dla okna aktywności, poprzez wywoła- nu. Natomiast aby aktywność nie była re-
w naturalny sposób), to dodajemy mecha- nie getWindow().setFlags() z odpowied- startowana podczas zmiany konfiguracji,
musimy zaznaczyć odpowiednią wartość
Listing 23. Klasa odpowiedzialna za wyświetlenie pełnoekranowego widoku zdjęcia w polu Config changes. W ten sposób in-
formujemy system, że aktywność sama ob-
public class PhotoViewActivity extends Activity { służy zmianę konfiguracji. My zaznaczamy
public final static String INTENT_EXTRA_PHOTO_TO_VIEW = "photoIdx"; orientation oraz keyboardHidden. Ta druga
private Photo photo; wartość oznacza fakt wysunięcia lub wsu-
protected void onCreate(Bundle savedInstanceState) { nięcia klawiatury telefonu.
super.onCreate(savedInstanceState); Tym sposobem osiągnęliśmy zamierzo-
requestWindowFeature(Window.FEATURE_NO_TITLE); ny cel, jednak szkoda byłoby na tym po-
getWindow().setFlags( przestać i zmarnować doskonałą szansę
WindowManager.LayoutParams.FLAG_FULLSCREEN, żeby się jeszcze trochę pobawić. Przecież
WindowManager.LayoutParams.FLAG_FULLSCREEN w manifeście zaznaczyliśmy, że sami ob-
); służymy zmiany konfiguracji, a nie doda-
FullscreenImageView photoView = new FullscreenImageView(this); liśmy nawet jednej linijki kodu. Czas na-
photoView.setFocusable(true); prawić ten niewybaczalny błąd. Zacznij-
Intent intent = getIntent(); my od kwestii wykrywania zmian konfigu-
if (intent != null) { racji. W tym celu należy przeciążyć meto-
int photoIdx = intent.getIntExtra(INTENT_EXTRA_PHOTO_TO_VIEW, -1); dę Activity.onConfigurationChanged(),
if (photoIdx != -1) { której parametr zawiera nową konfigurację
photo = PhotoManager.loadPhoto(photoIdx); w postaci obiektu Configuration. Bieżący
if (photo.getBitmap() != null) { stan możemy odczytać z publicznych pól
photoView.setImage(photo.getBitmap()); udostępnianych przez ten obiekt. W przy-
} padku używania tego mechanizmu należy
} pamiętać o dwóch sprawach. Po pierwsze,
} onConfigurationChanged() będzie wywo-
setContentView(photoView); łane tylko dla zmian, które zaznaczone są
} w manifeście. Po drugie, należy pamiętać
protected void onDestroy() { o przekazaniu informacji wyżej poprzez
super.onDestroy(); wywołanie super. onConfigurationCha
if (photo != null) { nged().
photo.releaseBitmap(); Powyższy sposób ma tę wadę, że sys-
} tem ma całkowitą kontrolę nad tym, kie-
} dy zmieni się orientacja ekranu. Co, je-
} żeli to my chcielibyśmy o tym zadecydo-
wać? W taki celu możemy skorzystać z kla-

116 SDJ Extra 34 Biblia


Android na przykładzie: geotagging zdjęć

sy OrientationEventListener z pakietu lenie w prawo do poziomu itd. W wy- nego powyżej mechanizmu przedstawia
android.view. padku, gdy nie można określić orien- Listing 24.
Korzysta ona z wbudowanego sensora i tacji z powodu położenia telefonu, to
udostępnia cztery metody: przekazane zostanie ORIENTATION _ Podsumowanie
UNKNOWN. Efekt finalny, czyli nasza przykładowa apli-
• canDetectOrientation()powie nam, kacja, nie jest może specjalnie imponują-
czy wykrycie orientacji jest możliwe; Wiemy już, jak wykrywać fizyczne od- ca, jednak przy jej tworzeniu wykorzystali-
• enable() włączy monitorowanie senso- chylenie telefonu, jak natomiast użyć tej śmy wiele z ciekawych funkcji oferowanych
ra w celu informowania o zmianie orien- informacji do ręcznej zmiany orienta- przez Androida. Puryści mogą być nieco
tacji; cji ekranu? W tym celu możemy posłu- zniesmaczeni uproszczeniami, zaniedba-
• disable() wyłączy powyższą funkcję; żyć się metodą Activity.setRequestedO niami i ewidentnymi skrótami w kodzie,
• abstrakcyjna metoda onOrientationC rientation(int), przekazując jako para- które nie powinny mieć miejsca w przypad-
hanged(int) poinformuje nas o zmia- metr jedną ze stałych zdefiniowanych w ku aplikacji przeznaczonej do dystrybucji.
nie, przekazując w argumencie orienta- ActivityInfo. Analogicznie, do sprawdze- Jednak celem artykułu nie było napisanie
cję w stopniach, gdzie 0 oznacza natu- nia aktualnej orientacji służy getRequest aplikacji ani popisywanie się znajomością
ralne położenie telefonu, 90 to wychy- edOrientation(). Przykład użycia opisa- inżynierii oprogramowania, a jedynie po-
służenie się nią jako pretekstem do zabawy
Listing 24. Przykład użycia OrientationEventListener do wykrycia wychylenia telefonu i zmiany i poznawania ciekawie zapowiadającego się
orientacji ekranu systemu operacyjnego.
Autor tego artykułu, będąc mocno zako-
OrientationEventListener orientationListener; rzeniony w realiach urządzeń mobilnych,
protected void onCreate(Bundle savedInstanceState) { często podkreśla, jak to trzeba oszczędzać
// inicjalizacja jak w PhotoViewActivity zasoby, bo tak mało ich jest do dyspozycji
setContentView(photoView); na telefonach komórkowych. Może to spra-
orientationListener = new OrientationEventListener(this) { wiać wrażenie, że Android jest w jakimś
public void onOrientationChanged(int orientation) { stopniu upośledzony. Nic bardziej myl-
int curOrient = getRequestedOrientation(); nego, warto spojrzeć na Androida z szer-
if (curOrient == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || szej perspektywy, której tylko częścią jest
curOrient == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { świat telefonów komórkowych. Jest to no-
if (orientation > 60 && orientation < 315) { woczesny system operacyjny oparty na Li-
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_ nuksie z jądrem serii 2.6. Do tego jego źró-
LANDSCAPE); dła są otwarte. Ten fakt zaoszczędził auto-
} rowi sporo czasu przy pisaniu kodu obsłu-
} gującego kamerę.
if (curOrient == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || Lektura źródeł systemowej aplikacji Ca-
curOrient == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { mera była bardzo pouczająca. Wszystko
if (orientation > 315 || orientation < 60) { wskazuje na to, że producenci sprzętu kom-
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_ puterowego również dostrzegają potencjał
PORTRAIT); Androida. W chwili pisania tego artykułu
} jeden z nich ogłosił oficjalnie plany wpro-
} wadzenia do sprzedaży netbookó'w pod
} kontrolą systemu ze stajni Google. Czas po-
}; każe, czy takie rozwiązanie się przyjmie,
} jednak sama możliwość sprawia, że war-
protected void onStart() { to przyglądać się Androidowi z zaintere-
super.onStart(); sowaniem.
orientationListener.enable();
}
PIOTR BUŁA
protected void onStop() { Pracuje jako programista Java w firmie Game-
super.onStop(); lion, wchodzącej w skład Grupy BLStream. Jego
orientationListener.disable(); pasją są gry komputerowe, a specjalizacją Java-
} ME, którą to technologię wykorzystuje do tworze-
nia gier na telefony komórkowe.
Kontakt z autorem: piotr.bula@gmail.com

W Sieci
• http://android.com/ – strona domowa Androida;
• http://source.android.com/ – strona projektu open source Android;
• http://developer.android.com/ – portal developerski Androida – zawiera pełną dokumentację oraz artykuły wprowadzające do programowa-
nia androidowych aplikacji;
• http://android-developers.blogspot.com/ – oficjalny blog twórców Androida.

www.sdjournal.org 117
Programowanie Android

Google Android
Programowanie z wykorzystaniem Google Maps

W artykule „JME – MIDlet – Przelicznik walut na podstawie kursów NBP”


opisuje architekturę JME oraz prezentuje sposób, w jaki można rozpocząć
przygodę z programowaniem z wykorzystaniem tej technologii.
Artykuł poświęcony jest platformie Android, na której można tworzyć
zaawansowane aplikacje m.in. z wykorzystaniem Google Maps.
• w nowym oknie, pośredniku dopisuje-
Dowiesz się: Powinieneś wiedzieć: my ścieżkę do katalogu Tools w folderze
• Co to jest Google Android; • Podstawy języka Java; z SDK, w moim przypadku jest to: D:\
• Jak tworzyć i uruchamiać programy na plat- • Podstawy języka XML. android-sdk-windows-1.5_r2\tools.
formie Android;
• Jak programować z wykorzystaniem Google Pamiętajmy, że jeśli w przyszłości zmienimy lo-
Maps. kalizację SDK , musimy także wprowadzić od-
powiednią zmianę do tej zmiennej systemowej.
Kolejnym krokiem jest konfiguracja środowiska
nie i testowanie utworzonych aplikacji, a Eclipse do działania z Android SDK. W moim
także obszerną i niezwykle pomocną doku- przypadku jest to Eclipse 3.4.2 Ganymode:
Poziom trudności mentację. Darmowe środowisko pobierze-
my ze strony: http://developer.android.com/ • uruchamiamy środowisko Eclipse;
sdk/ (zakładka Download); • z menu Help wybieramy opcję Software
• dowolnego środowiska deweloperskiego, Updates...;

N
aszym celem jest napisanie wła- np. Eclipse, które możemy pobrać za darmo • przechodzimy na zakładkę Available So-
snej aplikacji działającej w oparciu ze strony: http://www.eclipse.org/ (z zakładki ftware;
o platformę Android, w której wy- Download wybieramy Eclipse IDE for Java • klikamy przycisk Add Site...;
korzystamy usługę Google Maps. Zakładamy, Developers). Oczywiście moglibyśmy pisać • wpisujemy adres: https://dl-ssl.google.com/
że nasza aplikacja będzie umożliwiała wyko- kod naszej aplikacji w dowolnym edytorze android/eclipse/ (jeśli wystąpi problem z
nywanie następujących operacji: tekstowym (np. systemowy Notatnik), a na- pobraniem aplikacji z tej lokalizacji, nale-
stępnie uruchamiać z poziomu wiersza po- ży zamienić https na http) i klikamy OK;
• wyświetlanie map Google; leceń, lecz chyba nie muszę nikomu tłuma- • zaznaczmy pole przy Developer Tools i
• funkcja zoom (powiększanie i zmniejsza- czyć, jak bardzo katorżnicza byłaby to praca. klikamy Install;
nie map); • upewniamy się, że pola przy Android
• zmiana trybu wyświetlania: normalny, Instalacja i konfiguracja DDMS i Android Development Tools są
satelita, ulice; Po ściągnięciu Android SDK należy wypako- zaznaczone i klikamy Next;
• automatyczne wyświetlenie/przejście do wać archiwum. Domyślnie archiwum wypa- • akceptujemy warunki licencyjne i klika-
określonej lokalizacji; kowuje się do katalogu o nazwie w konwen- my Finish;
• wyświetlenie komunikatu z informacją cji android_sdk_<platform>_<release>, gdzie • restartujemy środowisko Eclipse.
o adresie klikniętego punktu na mapie. platform oznacza platformę np. Windows, a
release to wersja SDK. Ścieżkę z wypakowa- Po restarcie środowiska, z menu Window wy-
Zakładam również, że tworzymy aplikację nym Android SDK musimy dodać do zmien- bieramy Preferences, a następnie przechodzimy
na platformę Windows. Do rozpoczęcia pro- nej systemowej Path, w tym celu: na zakładkę Android. W oknie wpisujemy ścież-
gramowania potrzebujemy tak naprawdę tyl- kę do SDK (np.: D:\android-sdk-windows-1.5_r2)
ko trzech komponentów: • klikamy Mój komputer prawym przyci- (Rysunek 1), a następnie klikamy Apply i OK.
skiem myszy i wybieramy Właściwości Jak już pisałem wcześniej, w naszej aplika-
• JAVA Software Development Kit (SDK) (Properties); cji chcemy wykorzystać usługę Google Maps.
– opisane przeze mnie w poprzednim • przechodzimy na zakładkę Zaawansowa- W tym celu musimy uzyskać darmowy tzw.
artykule; ne (Advanced); klucz Google Maps API. W tym celu musimy
• Android Software Development Kit (SDK) • klikamy Zmienne Środowiskowe (Environ- wykonać następujące czynności:
– środowisko umożliwiające tworzenie ment Variables);
aplikacji na platformę Android. Udostępnia • dwukrotnie klikamy w zmienną syste- • odnajdujemy plik debug.keystore, w moim
także narzędzie umożliwiające uruchamia- mową Path; przypadku znajduje się on w następują-

118 SDJ Extra 34 Biblia


Google Maps

cej lokalizacji: C:\Documents and Settings\ Po wypełnieniu wszystkich pól klika- example\androidgooglemaps powinien po-
user_name\.android, gdzie user_name to my Finish. Projekt jest gotowy do użycia. jawić się plik AndroidGoogleMaps.java, w
nazwa użytkownika systemowego. W katalogu AndroidGoogleMaps\src\com\ którym powinna pojawić się deklaracja kla-
• dla wygody kopiujemy ten plik np. do
katalogu c:\
• uruchamiamy wiersz poleceń (cmd) i prze-
chodzimy do katalogu, w którym znajdu-
je się narzędzie keytool.exe (w folderze
z zainstalowanym Java SDK), w moim
przypadku jest to następująca lokalizacja:
C:\Program Files\Java\jdk1.6.0_13\bin
• wykonujemy następujące polecenie:

keytool.exe -list -alias androiddebugkey


-keystore "C:\debug.keystore"
-storepass android -keypass android

• Na ekranie powinien zostać wyświetlo-


ny klucz MD5, który kopiujemy.
• Przechodzimy na stronę
http://code.google.com/intl/pl/android/maps-
api-signup.html, gdzie zaznaczeniem po-
twierdzamy zapoznanie się z regulami-
nem, a następnie wklejamy nasz klucz
MD5 i klikamy Generate API key
• W nowym oknie pojawi się klucz, który
wykorzystamy w dalszej części artykułu.

Jeśli wszystko przebiegło bezbłędnie, to w


tym momencie mamy poprawnie skonfigu- Rysunek 1. Konfiguracja Android SDK w Eclipse
rowane środowisko i możemy rozpocząć pi-
sanie aplikacji na platformę Android.

Tworzenie projektu
W celu utworzenia nowego projektu Andro-
id w środowisku Eclipse wybieramy File>Ne-
w>Project. W nowym oknie wybieramy An-
droid Project i klikamy Next. Pojawi się okno
New Android Project, w którym musimy wy-
pełnić następujące pola (Rysunek 2):

• Project Name – nazwa projektu w Eclip-


se, a także nazwa katalogu, w którym
projekt będzie zapisany;
• Contents – lokalizacja katalogu projektu;
• Build Target – docelowa wersja platfor-
my, dla której nasza aplikacja będzie
kompilowana. W związku z tym, że bę-
dziemy oprogramowywać Google Maps,
wybieramy Google APIs (zauważmy, że
automatycznie w polu Min SDK Version
pojawia się wartość 3).
• Application Name – dowolna nazwa aplika-
cji, która będzie wyświetlana w telefonie;
• Package Name – nazwa pakietu dla na-
szej aplikacji. Nazwa pakietu musi być
unikalna dla całej platformy Android.
W naszym testowym przypadku wy-
korzystamy umowną nazwę pakietu
com.example.adroidgooglemaps;
• Create Activity – deklarujemy utworze-
nie aktywności, czyli podstawowej klasy,
która wykona dla nas zakładane funkcjo-
nalności. Rysunek 2. Tworzenie projektu Android

www.sdjournal.org 119
Programowanie Android

sy AndroidGoogleMaps bazującej na kla-


Listing 1. Zawartość pliku AndroidManifest.xml sie Activity. Aplikacja może posiadać wie-
<?xml version="1.0" encoding="utf-8"?> le różnych aktywności (klasy bazujących
<manifest xmlns:android="http://schemas.android.com/apk/res/android" na klasie Activity), ale w tym samym cza-
package="com.example.androidgooglemaps" sie użytkownicy wykorzystują tylko jedną z
android:versionCode="1" nich. Oprócz definicji klasy powinna poja-
android:versionName="1.0"> wić się także definicja metody onCreate(),
<application android:icon="@drawable/icon" android:label="@string/app_name"> która jest wołana przez system Android pod-
<uses-library android:name="com.google.android.maps" /> czas uruchamiania aktywności.

<activity android:name=".AndroidGoogleMaps" Konfiguracja plików XML


android:label="@string/app_name"> W celu poprawnego wyświetlania Google
<intent-filter> Maps w naszej aplikacji musimy skonfiguro-
<action android:name="android.intent.action.MAIN" /> wać plik AndroidManifest.xml znajdujący się
<category android:name="android.intent.category.LAUNCHER" /> w głównym katalogu naszej aplikacji. Musi-
</intent-filter> my umieścić w nim odwołanie do biblioteki
</activity> com.google.android.maps, w tym celu dodaje-
</application> my element <uses-library>, a także zezwolić
<uses-permission android:name="android.permission.INTERNET" /> na łączenie z Internetem i pobieranie danych
<uses-permission android:name="android.permission.ACCESS_COARSE_ o lokalizacji android.permission.ACCESS_FI-
LOCATION" /> NE_LOCATION, w tym celu dodajemy ele-
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" ment <uses-permission>. Plik AndroidMani-
/> fest.xml możemy edytować ręcznie np. w No-
</manifest> tatniku, lub też w środowisku Eclipse. W tym
celu klikamy dwukrotnie na niego w oknie
Listing 2. Zawartość pliku main.xml Package Explorer, a następnie definiujemy od-
<?xml version="1.0" encoding="utf-8"?> powiednie elementy w zakładkach Permissions
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" i Application (obszar Application Nodes). Po
android:layout_width="fill_parent" zmianach plik AndroidManifest.xml powinien
android:layout_height="fill_parent"> wyglądać tak jak na Listingu 1.
Drugim plikiem XML, który musimy
<com.google.android.maps.MapView zmienić w celu poprawnego wyświetlenia
android:id="@+id/mapView" Google Maps w naszej aplikacji, jest plik ma-
android:layout_width="fill_parent" in.xml znajdujący się w katalogu res/layout.
android:layout_height="fill_parent" Ponownie możemy edytować go ręcznie lub
android:enabled="true" otworzyć w Eclipse. W pliku tym musimy za-
android:clickable="true" gnieździć element <com.google.android.ma
android:apiKey="0TmVl5DkCEFnK2wWYo6YE_ygBU6NqcN53US5ocg" ps.MapView> odpowiadający za wyświetlenie
/> mapy wewnątrz elementu <RelativeLayout>
odpowiadającego za rozmieszczenie elemen-
</RelativeLayout> tów w aplikacji. Plik ten powinien wyglądać
tak jak na Listingu 2. Zwróćmy uwagę, że dla
atrybutu android:apiKey należy wkleić otrzy-
many ze strony Google klucz.

Wyświetlanie mapy
W pierwszym kroku musimy zmienić defi-
nicję naszej klasy AndroidGoogleMaps. Mu-
si ona bazować na klasie MapsActivity, a
nie na domyślnej Activity. Jeśli dziedziczy-
my po klasie MapsActivity, musimy nadpi-
sać metodę isRouteDisplayed(), w której je-
dynie zwracamy wartość false. W metodzie
onCreate() instrukcją setContentView(R.l
ayout.main) definiujemy wyświetlenie mo-
dyfikowanego wcześniej przez nas layoutu z
pliku main.xml. W tym momencie nasza apli-
kacja nadaje się już do uruchomienia.

Uruchamianie
aplikacji na platformie Android
Zanim jednak uruchomimy naszą, na razie
bardzo prostą, aplikację, musimy najpierw
Rysunek 3. Google Maps w telefonie Android zdefiniować Android Virtual Device (AVD).

120 SDJ Extra 34 Biblia


Google Maps

Jest to odpowiednik emulatora, na którym Automatyczne tej lokalizacji mapa ustawiła się automatycznie
będziemy testować działanie naszej aplikacji. wyświetlenie określonej lokalizacji na odpowiednim powiększeniu, umożliwiają-
Możemy zdefiniować wiele obiektów AVD Kolejną funkcjonalnością, którą dodamy do cym dostrzeżenie nazw ulic.
dla poszczególnych platform docelowych. W naszej aplikacji, jest automatyczne przejście W tym celu musimy zadeklarować obiekt
celu utworzenia AVD z menu Window wy- do określonej lokalizacji. Domyślnie Google typu MapController, który będzie nam słu-
bieramy Android AVD Manager. W nowym Maps wyświetla mapę Stanów Zjednoczo- żył do wykonywania operacji na mapie. Aby
oknie, w obszarze Create AVD wpisujemy na- nych. Załóżmy, że w naszym przypadku bę- uzyskać taki obiekt, możemy wykonać na zde-
zwę (Name), np. MapsAVD, wybieramy plat- dzie to centrum Warszawy, któremu odpo- finiowanym wcześniej obiekcie mapView me-
formę docelową (Target Platform), w naszym wiadają współrzędne 52.227625, 21.004682. todę getController(). Następnie definiuje-
przypadku jest to Google APIs 1.5 i klika- Załóżmy także, że chcemy, aby po przejściu do my dwuelementową tablicę tekstową, do któ-
my Finish. Teraz możemy już uruchomić na-
szą aplikację. W tym celu z menu Run wybie- Listing 3. Automatyczne wyświetlenie określonej lokalizacji
ramy opcję Run lub klikamy [CTRL + F11], mc = mapView.getController();
a następnie wybieramy tryb: Android Appli- String coordinates[] = {"52.227625", "21.004682"};
cation. W moim przypadku zanim emulator double lat = Double.parseDouble(coordinates[0]);
wystartował zajęło to trochę czasu, więc trze- double lng = Double.parseDouble(coordinates[1]);
ba uzbroić się w cierpliwość.
W chwili obecnej nasza aplikacja poza wy- p = new GeoPoint((int) (lat * 1E6), (int) (lng * 1E6));
świetleniem mapy nie pozwala na nic więcej. W
dalszej części artykułu zajmiemy się funkcjonal- mc.animateTo(p);
nością zoom'u i zmianą trybu wyświetlania. mc.setZoom(14);
mapView.invalidate();
Zoom i zmiana
trybu wyświetlania Listing 4. Definicja klasy MapOverlay
W najnowszej wersji Android SDK 1.5 w ce- class MapOverlay extends com.google.android.maps.Overlay
lu uzyskania funkcjonalności zoom'u, czy- {
li przybliżania (powiększania) i oddalania public boolean onTouchEvent(MotionEvent event, MapView mapView)
(zmniejszania) mapy, nie trzeba już pisać {
wielu linii kodu (jak to było we wcześniejszej if (event.getAction() == 1) {
wersji SDK), które obsługiwałyby kliknięcia GeoPoint p = mapView.getProjection().fromPixels(
odpowiednich przycisków. Wystarczy jedynie (int) event.getX(),
na obiekcie typu MapView wywołać odpo- (int) event.getY());
wiednią metodę setBuiltInZoomControl():
Geocoder geoCoder = new Geocoder(getApplicationContext(),
mapView = (MapView) findViewById(R.id.map Locale.getDefault());
View); try {
mapView.setBuiltInZoomControls(true); List<Address> addresses = geoCoder.getFromLocation(
p.getLatitudeE6() / 1E6, p.getLongitudeE6() / 1E6, 1);
Obiekt typu MapView deklarujemy poza funk-
cją onCreate(). Tutaj jedynie go definiujemy. String add = "";
Załóżmy, że nasza aplikacja ma działać if (addresses.size() > 0)
w ten sposób, że po kliknięciu przycisku 9 {
mapa ma włączać/wyłączać tryb satelity, for (int i=0; i<addresses.get(0).getMaxAddressLineIndex();
zaś przycisk 8 będzie służył do włączania/ i++)
wyłączania trybu ulic. W tym celu musi- add += addresses.get(0).getAddressLine(i) + "\n";
my zdefiniować metodę onKeyDown(int add += p.getLatitudeE6() / 1E6 + "; " + p.getLongitudeE6()
keyCode, KeyEvent event),w której / 1E6;
sprawdzimy numer wciśniętego przycisku. }
Do włączania trybu satelity służy funkcja else
setSatellite(boolean), zaś do trybu ulic add = "Sorry. No information";
– funkcja setStreetView(boolean). W na-
szym rozwiązaniu deklarujemy dwie zmien- Toast.makeText(getBaseContext(), add, Toast.LENGTH_
ne globalne: isSatellite i isStreetView ty- SHORT).show();
pu boolean, które będą odpowiedzialne za }
rodzaj operacji włączenia/wyłączenia. Treść catch (IOException e) {
metody onKeyDown przedstawiłem na Listin- e.printStackTrace();
gu 3.Możemy już teraz sprawdzić nasze no- }
we dwie funkcjonalności. Po uruchomieniu return true;
emulatora (Run) klikamy przycisk 9 i widzi- }
my, że mapa wyświetlana jest w trybie sate- else
lity. Możemy także włączyć tryb ulic, a także return false;
przybliżyć i oddalić mapę dzięki przyciskom }
pojawiającym się na dole ekranu emulatora }
(Rysunek 3).

www.sdjournal.org 121
Programowanie Android

rej wpisujemy interesujące nas współrzędne. niec przeładowujemy mapę przy pomocy meto- Aby umieścić tak przygotowaną warstwę
Są one w kolejnym kroku konwertowane do ty- dy invalidate() obiektu mapView. Treść tych na mapie, należy odwołać się do niej w głów-
pu double. Operacja przejścia do określonej lo- operacji przedstawiłem na Listingu 3. nej funkcji onCreate(). W tym celu dekla-
kalizacji polega tak naprawdę na tym, aby wska- rujemy obiekt naszej klasy MapOverlay. Na-
zać mapie punkt, który ma być wyświetlony. Wyświetlanie stępnie na obiekcie mapView wołamy metodę
Taki punkt to obiekt typu GeoPoint, do które- adresu klikniętej lokalizacji GetOverlays(), która zwraca nam wszystkie
go przekazujemy nasze współrzędne. Kolejnym Ostatnią funkcjonalnością, którą chcemy do- warstwy, które następnie usuwamy (metoda
krokiem jest wywołanie metody animateTo() dać do naszej aplikacji, jest wyświetlanie komu- clear()). Na koniec dodajemy nasz obiekt
z parametrem GeoPoint, która przeniesie nas nikatu z adresem klikniętej lokalizacji. W tym typu MapOverlay:
do określonej lokalizacji. Na koniec wywołu- celu napiszemy własną klasę MapOverlay, któ-
jemy metodę setZoom z wartością 14 – bo ta- ra będzie rozszerzała klasę com.google.andro MapOverlay mapOverlay = new MapOverlay();
kie powiększenie nas interesuje. Na sam ko- id.maps.Overlay. Wewnątrz tej klasy dekla- List<Overlay> listOfOverlays =
rujemy metodę onTouchEvent(MotionEvent mapView.getOverlays();

Listing 5. Lista importowanych pakietów event, MapView mapView). Pierwszą czyn- listOfOverlays.clear();
nością wewnątrz tej metody jest sprawdzenie listOfOverlays.add(mapOverlay);
import java.io.IOException; typu operacji, który miał miejsce. Musimy wy-
import java.util.List; konać nasze zadanie w momencie, kiedy użyt- Definicja klasy MapOverlay została przed-
import java.util.Locale; kownik dotknął ekranu telefonu. Takie spraw- stawiona na Listingu 4. Niestety, opisana po-
dzenie zapewni nam warunek następujący kod wyżej funkcjonalność nie działa prawidłowo
import com.google.android.maps.GeoP event.getAction() == 1. Następnie musi- przy polskich ustawieniach regionalnych –
oint; my zdefiniować obiekt znanego już nam typu wynika to z innego separatora dziesiętnego
import com.google.android.maps.MapAct GeoPoint, do którego przypisujemy współrzęd- w sczytywanych współrzędnych (wymagana
ivity; ne klikniętego/dotkniętego miejsca. Całą opera- jest kropka, a w polskich ustawieniach jest
import com.google.android.maps.MapCon cję pobrania/wyszukania informacji o punkcie to przecinek). Musimy niestety zmienić na-
troller; zapewni nam obiekt typu Geocode. Odpowia- sze ustawienia regionalne na amerykańskie.
import com.google.android.maps.MapV da on za proces zwany Geocoding'iem – czy- Na koniec ważna informacja o pakietach,
iew; li ustalaniem współrzędnych na podstawie na- które muszą zostać zaimportowane w celu
import com.google.android.maps.Over zwy lokalizacji, np. ulicy, placu. Umożliwia on poprawnego skompilowania naszej aplika-
lay; także wykonywanie operacji odwrotnej – czyli cji. Lista pakietów, która powinna zostać za-
ustalenie nazwy na podstawie współrzędnych. importowana, przedstawiona została na Li-
import android.location.Address; Po pobraniu tych informacji budujemy z nich stingu 5.
import android.location.Geocoder; odpowiedni komunikat, który wyświetlany jest
import android.os.Bundle; na mapie przy pomocy obiektu Toast, służące- Podsumowanie
import android.view.MotionEvent; go do wyświetlania krótkich komunikatów dla Android to bijąca obecnie wszelkie rekordy
import android.widget.Toast; użytkowników (Rysunek 4). Jeśli żadna infor- popularności platforma służąca do pisania za-
import android.view.KeyEvent; macja nie zostanie odnaleziona, wówczas wy- awansowanych aplikacji na telefony komór-
świetlany jest komunikat Sorry. No information. kowe. Zapewne niezmiernie ważny jest fakt,
że także na rynku polskim od pewnego cza-
su dostępny jest telefon Era G1 działający w
oparciu o tę platformę. Napisane przez nas
programy można zatem śmiało testować na
tym telefonie. Artykuł ten miał na celu za-
prezentowanie czytelnikom sposobu, w jaki
można rozpocząć programowanie na platfor-
mie Android, a także wykorzystania w swo-
ich aplikacjach usługi Google Maps. Oczy-
wiście zaprezentowane w artykule funkcjo-
nalności nie obejmują całego obszaru możli-
wości, które daje nam połączenie tych dwóch
technologii. Mam nadzieję, że zachęciłem
czytelników do własnych prób i eksperymen-
tów z platformą Android.

IGOR KRUK
Igor Kruk jest z wykształcenia informatykiem.
Obecnie pracuje na stanowisku Business Intelli-
gence Consultant i zajmuje się wdrażaniem sys-
temów klasy BI. Jest również współautorem ksią-
żek „Oracle 10g i Delphi. Programowanie baz da-
nych” oraz „SQL Server 2005. Zaawansowane roz-
wiązania biznesowe”.
Kontakt z autorem: igorkruk@gmail.com,
Rysunek 4. Informacje o adresie zaznaczonej lokalizacji http://www.igorkruk.pl

122 SDJ Extra 34 Biblia


Programowanie Android

Google Android
Programowanie interfejsu użytkownika pod Android OS

Artykuł jest wprowadzeniem do programowania interfejsu użytkownika


(UI – User Interface) na platformie Google Android. Omawia podstawowe
komponenty interfejsu i sposoby rozmieszczania ich na ekranie.
Przedstawia również tworzenie formularzy, przenoszenie danych między
formularzami oraz interakcję między różnymi ekranami użytkownika.
Android technicznie
Dowiesz się: Powinieneś wiedzieć: Google Android od strony technicznej jest
• Jak poprawnie rozmieścić komponenty • Podstawowa znajomość języka Java platformą, w której wyróżnić można czte-
komponenty użytkownika na ekranie; • Znajomość budowy dokumentów XML ry warstwy. Rysunek 1 jest ich ilustracją.
• Jak tworzyć poszczególne widgety; • Znajomość Eclipse IDE Najniżej położona jest warstwa core'owa
• Jak ekrany użytkownika współpracują ze sobą. (Linux Kernel), oparta na jądrze Linuksa
(ver. 2.6), odpowiedzialna za niskopozio-
mowe usługi systemowe takie jak: bezpie-
tów systemu została stworzona przy uży- czeństwo, zarządzanie pamięcią i procesa-
ciu tej technologii. mi, obsługa sterowników.
Poziom trudności Dobry start pozwala sądzić, że system Wyżej jest warstwa bibliotek i środowi-
utrzyma się na rynku systemów opera- ska uruchomieniowego (Libraries i Andro-
cyjnych i platform programistycznych na id Runtime). Zawiera ona zestaw biblio-
urządzenia mobilne mimo konkurencji i tek C/C++ używanych przez różne kompo-

G
oogle Android jest systemem doświadczenia takich marek jak Windows nenty systemu i wystawionych do użytku
operacyjnym na urządzenia mo- ME czy Symbian OS. Fakt ten może być przez developerów poprzez warstwę wyż-
bilne i jednocześnie platformą dobrym przyczynkiem do zainteresowa- szą (Application Framework). Warstwa
do tworzenia oprogramowania (Andro- nia się platformą i poczynienia pierwszych druga zawiera również środowisko uru-
id SDK). Pierwsza wersja systemu zosta- kroków w zakresie tworzenia na nią pro- chomieniowe, czyli napisaną przez Go-
ła wydana przez firmę Google już prawie stych aplikacji. ogle'a maszynę wirtualną (DVM – Dalvik
dwa lata temu (5 listopada 2007 r.). Od te-
go czasu Android zyskuje sobie coraz więk-
szą popularność zarówno wśród develope-
rów urządzeń mobilnych jak i producen-
tów takich urządzeń. Świadczy o tym sta-
le rosnąca ilość stron www i portali zwią-
zanych z tematyką Google Android. Ofi-
cjalny sklep internetowy Google'a (Andro-
id Market) każdego dnia zapełnia się nowy-
mi aplikacjami tworzonymi przez progra-
mistów z całego świata. Z drugiej strony,
producenci telefonów komórkowych (np.
Samsung, Htc) powoli przekonują się do
instalowania systemu operacyjnego spod
znaku Google'a w coraz to większej licz-
bie telefonów.
Wydaje się, że jedną z przyczyn szybkie-
go wzrostu popularności systemu jest do-
starczenie wraz z Androidem kompletne-
go API do tworzenia aplikacji w języku
programowania Java (tzw. Android SDK),
czyli technologii będącej dziś prawdziwym
standardem w świecie oprogramowania. Rysunek 1. Architektura systemu operacyjnego Google Android (źródło: http://
Również znaczna część core'owych elemen- developer.android.com)

124 SDJ Extra 34 Biblia


Programowanie UI

Virtual Machine) dostosowaną do pracy na nych ze stacjonarnymi systemami operacyj- Hierarchia klas
urządzeniach mobilnych. nymi. Ponadto Android OS daje możliwość Wszystkie widgety Android OS dziedzi-
Warstwa kolejna (Application Fra- programiście rozszerzania komponentów czą po abstrakcyjnej klasie View i znajdują
mework) zawiera framework do tworze- czy też tworzenia zupełnie nowych, bazu- się w pakiecie android.widget. Hierarchia
nia aplikacji pod Androida czyli stanowi jąc w tym zakresie na klasycznych mechani- klas komponentów użytych w przykłado-
wspomniane wcześniej API programistycz- zmach dziedziczenia dostępnych w Javie. wej aplikacji, na Rysunku 2.
ne. Przy użyciu tego API napisane zosta-
ły wbudowane i dostarczone wraz z syste-
���� �������� ��������
mem Android aplikacje z warstwy najwyż-
szej (Applications), czyli standardowe i do-
stępne w każdym telefonie aplikacje typu:
wysyłanie smsów, kalendarz, książka tele-
foniczna itp. ��������� ������ ��������������������
Dokładnie to samo API jest, jak już
wspomnieliśmy, dostępne każdemu pro-
gramiście. W artykule tym skupimy się
głównie na jednym elemencie z warstwy ����������� ����������� ������������ ��������������
API (Application Framework), na elemen-
cie View System (vide: Rysunek 1), który
odpowiedzialny jest za dostarczenie API
do tworzenia interfejsu użytkownika. ���������� ���������� ����������� ��������

Programowanie
interfejsu użytkownika
W inżynierii oprogramowania projekto- �������
wanie i tworzenie interfejsów użytkowni-
ka uważane jest za odrębną dziedzinę wie-
dzy. Poprzez interfejs następuje komunika- Rysunek 2. Hierarchia klas komponentów użytych w przykładzie.
cja użytkownika ze wszystkimi warstwami
systemu. Celem projektowania jest więc Listing 1. Tworzenie pola tekstowego w kodzie Javy
uczynienie tej komunikacji jak najbardziej
prostą i efektywną. Często mówiąc o pożą- public class HelloAndroid extends Activity {
danych cechach interfejsu stosuje się okre- @Override
ślenie „przyjazny w stosowaniu dla użyt- public void onCreate(Bundle savedInstanceState) {
kownika” (ang. user friendly). Oznacza to super.onCreate(savedInstanceState);
osiągnięcie jakiegoś efektu w aplikacji w TextView tv = new TextView(this);
możliwie najprostszy i najszybszy sposób tv.setText("Hello, Android");
bez angażowania dużej ilości komponen- setContentView(tv);
tów, formularzy czy też ekranów użyt- }
kownika. Nie bez znaczenia jest tu tak- }
że wizualna strona systemu, czyli tzw. lo-
ok and feel. Listing 2. Tworzenie pola tekstowego w xml
Programowanie na urządzeniach mobil- <?xml version="1.0" encoding="utf-8"?>
nych ma swoje dodatkowe wymagania. Do- <TextView xmlns:android="http://schemas.android.com/apk/res/android"
chodzi w tym przypadku mały ekran oraz android:layout_width="fill_parent"
okrojone możliwości interakcji osoby pra- android:layout_height="fill_parent"
cującej z aplikacją mobilną przejawiają- android:text="Hello, Android"/>
cą się np. w braku kursora myszki. Choć
z drugiej strony, tą niedogodność nadra- Listing 3. Plik AndroidManifest.xml
biają w ostatnim czasie ekrany dotykowe, <manifest xmlns:android="http://schemas.android.com/apk/res/android"
znacznie poszerzające wachlarz sposobów package="pl.example.biorithm.activity"
komunikowania się z systemem. android:versionCode="1"
android:versionName="1.0">
Komponenty <application android:icon="@drawable/icon" android:label="@string/app_name">
interfejsu użytkownika <activity android:name=".InputDataForm" android:label="@string/app_name">
System Google Android, jak każdy system <intent-filter>
operacyjny, zawiera zestaw komponentów <action android:name="android.intent.action.MAIN" />
do tworzenia graficznego interfejsu użyt- <category android:name="android.intent.category.LAUNCHER" />
kownika. Komponenty te, zwane widgeta- </intent-filter>
mi, pozwalają konstruować zaawansowa- </activity>
ne ekrany umożliwiające w wygodny spo- </application>
sób realizację wyszukanych funkcji aplika- <uses-sdk android:minSdkVersion="3" />
cji. Na pewno ich funkcjonalność nie od- </manifest>
biega od kontrolek systemowych dostarcza-

www.sdjournal.org 125
Programowanie Android

Widgety
Obiekty dziedziczące wprost po kla-
sie View stanowią typowe widgety (Wid-
gets). Zaliczyć do nich można komponen-
ty typu: TextView (pole wypisujące tekst),
EditView (pole do wprowadzania tekstu),
Button (przycisk), Checkbox itd. Andro-
id dostarcza także bardziej skomplikowa-
ne komponenty jak np. date picker (kom-
ponent do pobierania daty – czyli odpo-
wiednik kalendarzyka), clock (zegar) czy
zoom. Można rozszerzać istniejące kom-
ponenty dodając nową funkcjonalność lub
tworzyć zupełnie nowe dziedzicząc wprost
po klasie View.

Layouty
Obiekty rozszerzające klasę ViewGroup
stanowią w znacznej mierze tzw. layouty
(Layouts). Są to komponenty służące do
rozmieszczania i pozycjonowania innych
komponentów na ekranie. Mogą zawierać
inne obiekty klasy ViewGroup lub obiekty
View. Możliwość zagnieżdżania layoutów
w sobie pozwala na budowanie dowol-
nie złożonych ekranów. Podczas projek-
towania można kierować się doświadcze-
niem nabytym podczas pracy z biblioteką
Swing, znaną zapewne większości progra-
mistów Javy. Idea konstruowania ekranów
użytkownika jest bardzo podobna.

Sposoby tworzenia
interfejsów użytkownika

Programowalnie
Komponenty interfejsu użytkownika pro-
Rysunek 3. Konfigurowanie projektu Android gramista może tworzyć wprost w kodzie,
korzystając z określonych klas i interfej-
sów reprezentujących widgety i ich zacho-
Przykładowe komponenty wania. Jest to sposób przypominający po-
Lista komponentów dostarczonych wraz z systemem jest dość długa. Poniższe punkty dejście do budowania znane ze wspomnia-
przedstawiają najważniejsze z nich: nej już biblioteki Swing. Przykład jak mo-
że wyglądać kod pola tekstowego jest na Li-
• LinearLayout – podstawowy layout do rozmieszczania elementów horyzontalnie lub stingu 1).
wertykalnie
Dla wielu programistów taka droga jest
• RelativeLayout – pozwala na relatywne rozmieszczanie komponentów, zalecany
przy skomplikowanych interfejsach użytkownika pewnie bardziej naturalna i prosta. Gorzej
• TableLayout – umożliwia umieszczanie widgetów w formie tabeli sytuacja wygląda, gdy zaistnieje koniecz-
• DatePicker – pozwala na wygodny wybór daty (kalendarzyk) ność całościowego spojrzenia na kod inter-
• TimePicker – komponent umożliwiający wybór godziny fejsu użytkownika. Również utrzymanie i
• Button, Checkbox, TextView, EditView – standardowe elementy służące do budowy rozwój takiego kodu jest trudny.
formularzy
• Spinner – odpowiednik listy rozwijanej
• AutoCompleteTextView – komponent do pobierania tekstu z automatyczną podpo- Deklaratywnie
wiedzią wyboru Drugim sposobem tworzenia interfej-
• ListView – widget pozwalający na tworzenie przewijanej, pionowej listy z możliwo- sów pod Android OS jest deklaratywne
ścią filtrowania elementów umieszczanie komponentów w dokumen-
• Gallery – komponent najczęściej używany do tworzenia galerii zdjęć – umożliwia tach XML. W tym przypadku elementy
tworzenie poziomej, przewijanej listy elementów, wybrany element jest umieszcza-
ny na środku listy mają postać tagów, które odpowiadają po-
• TabWidget – widget implementujący funkcjonalność zakładek (tabs) szczególnym klasom widgetów. Ten spo-
• MapView – umożliwia tworzenie ekranów użytkownika z włączoną usługą Google- sób z kolei, przypomina budowanie inter-
Maps fejsu znane z aplikacji internetowych. Źró-
• WebView – pozwala na programowanie ekranów użytkownika z możliwością prze- dło tworzonego interfejsu ma postać za-
glądania zasobów internetu
gnieżdżonych między sobą tagów doku-
mentu XML. Sposób ten przypomina więc

126 SDJ Extra 34 Biblia


Programowanie UI

tworzenie stron www w technologii html. Listing 4. Klasa InputDataForm zaraz po wygenerowaniu
Na Listingu 2 pokazaliśmy jak w pliku xml
zdefiniować pole tekstowe podobne do te- public class InputDataForm extends Activity {
go z Listingu 1.
Należy jednak zaznaczyć, że nie wszyst- /** Metoda wywoływana, gdy obiekt jest tworzony */
ko da się deklaratywnie umieścić w pli- @Override
kach xml. Część funkcjonalności danego public void onCreate(Bundle savedInstanceState) {
komponentu niekiedy trzeba oprogramo- super.onCreate(savedInstanceState);
wać w kodzie klasy. Z racji jednak rozdzie- setContentView(R.layout.main);
lenia (przynajmniej w dużej mierze) czę- }
ści aplikacji odpowiedzialnej za wygląd od
części implementującej logikę (zgodność z }
klasycznym wzorcem MVC!), ten sposób
tworzenia jest zalecany przez twórców An- Listing 5. Plik input_data_form.xml z definicjami elementów formularza
droid OS. <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
Struktura android:orientation="vertical" ...>
aplikacji pod Android OS
Zanim przejdziemy do tworzenia inter- <LinearLayout android:orientation="vertical" ...>
fejsu użytkownika na bazie przykładowej <TextView android:text="@string/birthdayText" .../>
aplikacji, przedstawimy w kilku zdaniach <TableLayout ...>
jak wygląda podstawowa struktura aplika- <TableRow>
cji działającej pod Androidem. Wiemy już, <TextView android:text="@string/day" .../>
że jednym z budulców aplikacji są kompo-
nenty dziedziczące po klasie View. Repre- <TextView android:text="@string/month">
zentują one elementy interfejsu użytkow- <TextView android:text="@string/year"/>
nika. Jednakże podstawowym budulcem </TableRow>
aplikacji pod Android OS są klasy dzie- <TableRow>
dziczące po klasie Activity. Zgodnie z do- <EditText android:id="@+id/birthDay"/>
kumentacją klasy Activity stanowią poje- <Spinner android:id="@+id/birthMonth" .../>
dyńczą jednostkę aplikacji, która jest za- <AutoCompleteTextView android:id="@+id/birthYear" .../>
projektowana do wykonywania akcji użyt- </TableRow>
kownika. Aplikacja może składać się z wie- </TableLayout>
lu klas Activity lecz użytkownik zawsze </LinearLayout>
wchodzi w interakcję tylko z jedną z nich. <LinearLayout android:orientation="vertical" ...>
Klasy Activity mają metodę public void
onCreate(Bundle savedInstanceState) <TextView android:text="@string/biorithmText" .../>
wywoływaną przez Android OS w czasie <TableLayout ...>
startu danej akcji. Tu właśnie należy inicjo- <TableRow>
wać komponenty interfejsu użytkownika. <TextView android:text="@string/dateFromText" .../>

Przykładowa aplikacja <TextView android:id="@+id/dateFromContent" .../>


Najlepiej jest zrozumieć tworzenie i dzia- <TextView android:text="@string/dateToText" .../>
łanie komponentów interfejsu użytkow- <TextView android:id="@+id/dateToContent" .../>
nika wykonując praktyczne przykłady. W </TableRow>
tym miejscu opiszemy budowanie prostej <TableRow>
aplikacji działającej pod Android OS. Bę- <Button android:id="@+id/pickDateFrom" android:text="Zmień" .../>
dzie to popularna aplikacja pokazująca wy- <Button android:id="@+id/pickDateTo" android:text="Zmień" .../>
kresy biorytmów na konkretny dzień lub </TableRow>
przedział dni. </TableLayout>
Po podaniu odpowiednich danych (daty </LinearLayout>
urodzenia użytkownika, przedziału czaso- <LinearLayout android:orientation="vertical" ...>
wego i rodzaju wykresu biorytmu) aplika- <TextView android:text="@string/graphText" .../>
cja pokaże na nowym ekranie wygenerowa- <LinearLayout android:orientation="vertical" ...>
ny biorytm. Wezmą więc udział w tym pro- <CheckBox android:id="@+id/physical" android:text="Fizyczny" .../>
cesie dwa ekrany użytkownika i jedna akcja. <CheckBox android:id="@+id/mental" android:text="Psychiczny" .../>
Do tworzenia projektu będziemy potrze- <CheckBox android:id="@+id/emotional" android:text="Intelektualny" .../>
bować odpowiedniego środowiska. Będzie </LinearLayout>
nim: poprawnie zainstalowany Android </LinearLayout>
SDK w dowolnej wersji (sugerowana wer- <LinearLayout android:orientation="horizontal" ...>
sja: 1.5), środowisko programistyczne Ec- <Button android:id="@+id/generate" android:text="@string/generateText" .../>
lipse, plugin do Eclipse'a ADT (Andro- </LinearLayout>
id Development Toolkit) z wbudowanym </LinearLayout>
emulatorem telefonu komórkowego.

www.sdjournal.org 127
Programowanie Android

Listing 6. Komponent Spinner w input_data_form.xml W dalszej części opisu zakładamy, że wy-


mienione składniki zostały poprawnie za-
<Spinner android:id="@+id/birthMonth" instalowane.
android:layout_width="wrap_content"
Tworzenie projektu
android:layout_height="wrap_content" android:drawSelectorOnTop="true" Pierwszym krokiem jest utworzenie w Ec-
android:width="20pt" lipse IDE projektu Android. Z menu File
android:prompt="@string/months_prompt" /> / New / Project / Android / Android Project
wybieramy opcję projektu (zakładamy, że
Listing 7. Kod obsługi klasy Spinner w klasie InputDataForm. wtyczka jest poprawnie zainstalowana).
birthMonth = (Spinner) findViewById(R.id.birthMonth); Uzupełniamy wymagane pola, po wykona-
niu tych czynności powinniśmy mieć okno
ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.birthMonth, konfiguracji podobne do tego z rysunku 3:
android.R.layout.simple_spinner_item); W polu Create Activity zdefiniowalismy
pierwszą klasę typu Activity o nazwie
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); InputDataForm. Klasa ta będzie zawierać
birthMonth.setAdapter(adapter); prosty formularz do pobrania danych. W
wygenerowanej strukturze projektu może-
Listing 8. Plik day_values.xml z miesiącami wyświetlanymi w komponencie Spinner my od razu zajrzeć do pliku AndroidMani-
<resources> fest.xml. Znajdują się tam podstawowe da-
<string-array name="birthMonth"> ne konfiguracyjne dla całej aplikacji. Tu
<item>styczeń</item> właśnie należy definiować klasy Activity
<item>luty</item> (Listing 3).
Z Listingu 3 widzimy, że do definicji
<item>marzec</item> klasy InputDataForm podłączony został
<item>kwiecień</item> filtr decydujący o uruchomianiu właśnie
<item>maj</item> tej Activity zaraz po starcie aplikacji.
Sama klasa InputDataForm wygląda tak,
<item>czerwiec</item> jak na Listingu 4.
<item>lipiec</item> Jak widać, na Listingu 4 w meto-
<item>sierpień</item> dzie public void onCreate(Bundle
<item>wrzesień</item> savedInstanceState) ustawiana jest kla-
<item>październik</item> sa View zawierająca komponenty: setCon
<item>listopad</item> tentView(R.layout.main). Przedstawio-
<item>grudzień</item> na forma oznacza, że widok będzie gene-
</string-array> rowany z pliku xml. Aby się o tym prze-
</resources> konać, spójrzmy na plik main.xml znajdu-
jący się w katalogu res/layout. Przy okazji
Listing 9. Definicja komponentów Button, pod które podpięty został komponent możemy zmienić jego nazwę na input_da-
DatePickerDialog ta_form.xml. Tutaj właśnie, korzystając
<Button android:id="@+id/pickDateFrom" z tagów odpowiadających poszczególnym
android:layout_width="wrap_content" klasom komponentów zdefiniujemy naj-
android:layout_height="wrap_content" pierw: odpowiedni layout dla formularza,
android:layout_column="1" później umieścimy na nim komponenty.
android:text="Zmień"/>
<Button android:id="@+id/pickDateTo" Definiowanie layoutów
android:layout_width="wrap_content" Jak już wspomnieliśmy, formularz będzie
android:layout_height="wrap_content" posiadał elementy: pola do podania daty
android:layout_column="3" urodzenia, zakresu czasu na jaki ma być
android:text="Zmień"/> wygenerowany biorytm oraz do wyboru
rodzaju wykresu. Wydaje się, że najodpo-
Listing 10. Obiekty nasłuchujące podpięte pod buttony. wiedniejszym rozwiązaniem będzie w tym
pickDateFrom.setOnClickListener(new View.OnClickListener() { przypadku wybór LinearLayout umiesz-
public void onClick(View v) { czającego komponenty w porządku piono-
showDialog(DATE_FROM_DIALOG_ID); wym. To będzie zewnętrzny, główny layout.
} Każde z tych elementów będzie zdefinio-
}); wane dodatkowo w layoutcie poziomym, a
konkretnie w TableLayout. Pamiętajmy, że
pickDateTo.setOnClickListener(new View.OnClickListener() { np. element formularza do wprowadzenia
public void onClick(View v) { daty urodzenia, będzie się składał z 3 pól
showDialog(DATE_TO_DIALOG_ID); (dzień, miesiąc, rok urodzenia) i to właśnie
} te pola umieścimy w TableLayout.
}); Podobnie drugi element – pola do wy-
boru zakresu dat. Pola te zrealizujemy za

128 SDJ Extra 34 Biblia


Programowanie UI

pomocą komponentu DatePickerDialog. Jak już wspomnieliśmy, nie wszystko da się kły komponent Button została podpięta
Aby pola te i opisy tekstowe do nich były zdefiniować w samych plikach xml. Kompo- akcja pokazująca stosowne okno dialogo-
również efektywnie rozmieszczone, zasto- nent Spinner, aby działał poprawnie musi mieć we. Fragment w input_form_data.xml wy-
sujemy także TableLayout. jeszcze w klasie InputDataForm, w metodzie gląda tak (Listing 9).
Trzeci element, czyli pola wyboru rodza- onCreate(Bundle savedInstanceState) pod- Na Listingu 9 widzimy dwa buttony, je-
ju wykresu, zrealizujemy za pomocą kom- łączony adapter. Wygląda to tak (Listing 7). den odpowiedzialny za przyjęcie daty od
ponentów Checkbox. Jednak te elemen- Całość listy rozwijanej na Rysunku 5. której wykres biorytmu ma być generowa-
ty umieścimy w kolejnym LinearLayout, Przy okazji możemy zdradzić jak zdefi- ny, drugi – służący do przyjęcia daty koń-
również zorientowanym na pionowe niowaliśmy miesiące znajdujące się na li- cowej.
(ang. vertical) rozmieszczanie zawartości. ście rozwijanej. Rozwiązaniem jest umiesz- W kodzie Javy obsługa DatePickerDialog
Spójrzmy jak może wyglądać plik input_ czenie danych, które chce się wyświetlić na wygląda następująco (Listing 10). W metodzie
data_form.xml po zmianach wprowadzo- liście, w pliku xml znajdującym się w kata- onCreate(Bundle savedInstanceState)
nych zgodnie z powyższymi uwagami (Li- logu res aplikacji (w tym katalogu, znajdują pod buttony podpięte są nasłuchiwacze zda-
sting 5). Listing ten ma za zadanie głównie się wszystkie statyczne składniki aplikacji). rzeń (ang. listenery).
pokazać rozmieszczenie layoutów stąd też Dokładnie chodzi tu o katalog res/values za- Metoda showDialog(int id) z kla-
niepotrzebne atrybuty zostały usunięte. wierający statyczne i stałe łańcuchy zna- sy Activity jest sprzężona z metodą
Uważny czytelnik Listingu 5 z pewnością ków. W katalogu tym zdefiniowaliśmy plik onCreateDialog(int id), w której two-
zauważył, że tak naprawdę TableLayouty za- day_values.xml (Listing 8). rzone są komponenty kalendarza, w zależ-
gnieździliśmy jeszcze w kolejnych elemen- Pole do wpisania roku urodzenia za- ności od wyboru – pobierającego datę Od
tach LinearLayout. Było to konieczne ze implementowaliśmy korzystając z kom- lub datę Do (Listing 11).
względu na elementy opisowe poszczegól- ponentu AutoCompleteTextView. Jest to Ostatnią sekcją formularza są kompo-
nych sekcji formularza (czyli swego rodzaju również dość popularny widget (zwłasz- nenty Checkbox, określające jakiego rodza-
labele), które zaimplementowane są za po- cza w aplikacjach webowych) wyświetla- ju wykres biorytmu ma być wygenerowany
mocą komponentu TextView. Dokładnie są jący użytkownikowi podpowiedzi dosto- i wyświetlony na ekranie użytkownika. W
to napisy: Data urodzenia, Biorytm, Wykres. sowane do tego, co już użytkownik wpi- input_data_form.xml mają one postać na-
Oto jak może wyglądać ekran użytkow- sał w pole. Nadmienimy tylko, że kompo- stępującą (Listing 12).
nika zdefiniowany w omawianym pliku in- nent ten również wymaga podłączenia ada-
put_data_form.xml (Rysunek 4): ptera (w klasie InputDataForm w metodzie Komunikacja
Pole do wprowadzania dnia urodzenia jest onCreate(Bundle savedInstanceState), między ekranami użytkownika
typowym komponentem służącym do po- oraz że zbiór podpowiedzi (czyli w tym Dane wprowadzone przez użytkowni-
bierania tekstu. Realizuje je klasa EditView. przypadku lata od 1900 do 2000) zde- ka na powyżej opisanym interfejsie mu-
Ciekawsze rozwiązanie przyjęliśmy przy po- finiowany jest w tablicy years w klasie simy w końcu przetransportować do kla-
lu do wprowadzania miesiąca urodzenia. InputDataForm. sy Graph (drugi ekran użytkownika) od-
Użyty jest tam komponent Spinner będą- Do zaimplementowania sekcji do wy- powiedzialnej za generowanie wykresu. W
cy klasyczną listą rozwijaną. Oto fragment znaczania zakresu dat biorytmu użyliśmy Android OS stosuje się w takich przypad-
pliku input_form_data.xml definiujący ten komponentów kalendarzyka, czyli w An- kach obiekty klasy Intent, która jest, mó-
komponent (Listing 6). droid OS – DatePickerDialog. Pod zwy- wiąc w skrócie, zwykła mapą do przeno-

Rysunek 4. Ekran z formularzem do Rysunek 5. Komponent Spinner (lista rozwijana) Rysunek 6. Końcowy efekt – ekran pokazujący
wprowadzania danych. w działaniu wygenerowany wykres

www.sdjournal.org 129
Programowanie Android

szenia danych między klasami Activites. dzie jest to data urodzenia) i na ich podsta- mów. Klasa ta również korzysta z obiektu
Przykładowo, tworzenie obiektu Intent wie wygenerować wykres. GraphView (nasz własny komponent dzie-
ma postać konstruktora: Intent intent Samo uruchomienie przejścia pomiędzy dziczący po klasie View) do budowy inter-
= new Intent(this, Graph.class). Od dwoma ekranami użytkownika wykonuje fejsu użytkownika. Konkretnie rzecz uj-
razu można zauważyć klasę Activity, metoda klasy Activity : startActivityFo mując, klasa GraphView „rysuje” wykresy.
do której przeniesione zostanie sterowa- rResult(intent, SHOW_GRAPH_OK). Definicja ekranu użytkownika odpowie-
nie. Ustawienie wartości do przeniesie- dzialnego za pokazanie wykresów znajdu-
nia czyli parametru wygląda tak: intent Wygenerowany biorytm je się tam, gdzie znajdują się pliki xml ze
.putExtra(Constants.DATE_OF_BIRTH, Za pokazanie na interfejsie użytkowni- zdefiniowanymi layoutami dla poszczegól-
dateOfBirth). W ten sposób w metodzie ka wygenerowanych biorytmów odpowia- nych ekranów użytkownika, czyli w katalo-
onCreate(Bundle savedInstanceState) da klasa Graph. To w niej następuje odwo- gu res/layout, w pliku graph.xml. Plik ten
w klasie Graph możemy już odebrać prze- łanie do obiektu BiorithmCalculator wy- wygląda, tak jak na Listingu 13.
kazane parametry (w podanym przykła- konującego całą logikę obliczania bioryt- Układ ekranu zgodny z plikiem
graph.xml oraz oczywiście sam wykres,
Listing 11. Metoda onCreateDialog(int id) zgodny z danymi wejściowymi pokazany-
mi na Rysunku 3, przedstawia Rysunek 6.
protected Dialog onCreateDialog(int id) {
switch (id) { Podsumowanie
case DATE_FROM_DIALOG_ID: Tworzenie interfejsu użytkownika pod An-
return new DatePickerDialog(this, dateFromSetListener, yearFrom, droid OS jest proste, choć z początku może
monthFrom, dayFrom); wydawać się nieprzyjemne, z racji operowa-
case DATE_TO_DIALOG_ID: nia tagami w xml przy definiowaniu kom-
return new DatePickerDialog(this, dateToSetListener, yearTo, monthTo, ponentów. Dla zatwardziałych zwolenni-
dayTo); ków programowalnego tworzenia kompo-
} nentów zawsze pozostaje możliwość pisa-
return null; nia kodu w klasach Activity w metodach
} onCreate(Bundle savedInstanceState).
Zapewniamy jednak, że przestawienie się
Listing 12. Komponenty checkbox w pliku input_data_form.xml na myślenie o interfejsie użytkownika w
<CheckBox android:id="@+id/physical" kategoriach tagów xml'a jest dość szybkie.
android:layout_width="wrap_content" Później już dość trudno znów zacząć pisać
kod obsługujący komponenty w klasach.
android:layout_height="wrap_content" Po prostu myśli się już trochę tak jak przy
android:text="Fizyczny" /> projektowaniu dokumentów html.
Android OS charakteryzuje się również
Listing 13. Plik graph.xml bogatym zestawem komponentów pozwa-
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" lającym na realizację nawet najbardziej
android:orientation="vertical" skomplikowanych interfejsów. W przypad-
android:layout_width="fill_parent" ku developerów niezbyt zadowolonych ze
android:layout_height="wrap_content" standardowej biblioteki, zawsze pozosta-
android:paddingBottom="50px" je możliwość rozszerzania istniejących już
android:gravity="right"> komponentów.
<pl.com.jcode.biorithm.view.GraphView Z racji tego, że Android tworzony jest
android:id="@+id/graphView" przez Google'a, istnieje łatwy dostęp do
takich komponentów jak MapView czy
android:layout_width="fill_parent" WebView, umożliwiających korzystanie w
android:layout_height="wrap_content" /> tworzonych aplikacjach z Google Maps
<Button oraz pozwalających na łatwe przeglądanie
android:id="@+id/back" zawartości Internetu. To znacznie posze-
android:layout_width="wrap_content" rza krąg pomysłów na aplikacje, już na star-
android:layout_height="wrap_content" cie pozwala uczynić je bardziej ciekawymi
android:text="@string/backText" /> i konkurencyjnymi.
</LinearLayout> Można sądzić, że powyższe zalety (pro-
stota tworzenia interfejsów, duży wybór
komponentów, łatwe wykorzystanie Go-
ogle Maps i Internetu) spowodują, że An-
W Sieci
droid OS umocni swoją pozycję w świecie
• Android SDK do ściagnięcia – http://developer.android.com/sdk/1.5_r2/index.html platform programistycznych na urządze-
• Instalacja Android SDK i wtyczki ADT do Eclipse'a – http://developer.android.com/sdk/1.5_ nia mobilne.
r2/installing.html
• Opis komponentów użytkownika – http://developer.android.com/guide/tutorials/views/
index.html
• Dokumentacja Android API (JavaDoc) – http://developer.android.com/reference/android/ TOMASZ MILCZAREK
app/package-summary.html Konsultant w firmie BNS IT.
Kontakt z autorem: tomasz.milczarek@gmail.com

130 SDJ Extra 34 Biblia


Programowanie iPhone OS

Poznajemy iPhone SDK


Pierwsze kroki

Nikt nie zaprzeczy, że jednym z najpopularniejszych urządzeń mobilnych


ostatnich 12 miesięcy jest iPhone. Doskonały wygląd zewnętrzny,
przejrzysty graficzny interfejs użytkownika oraz bogata funkcjonalność
to tylko niektóre jego cechy. W niniejszym artykule przedstawię to
urządzenie z punktu widzenia programisty.
ją strukturę warstwową, przedstawioną na
Dowiesz się: Powinieneś wiedzieć: Rysunku 1. Warstwy Core OS oraz Core Se-
• Podstawowych informacji na temat iPhone OS; • Jak programować w języku Objective-C. rvices zawierają fundamentalny interfejs
• Co zawiera iPhone SDK oraz jak się z nim systemu operacyjnego. Wykorzystuje się je
programuje. w celu uzyskania dostępu do plików oraz
gniazd sieciowych (ang. network sockets).
W tej warstwie definiowane są również ni-
skopoziomowe typy danych. Poszczególne
się zderzyć z pewnymi niedogodnościami. struktury wchodzące w skład warstwy Co-
Trzeba tu wymienić dwie najistotniejsze re Services to:
Poziom trudności sprawy: mało popularny język programo-
wania Objective-C, którego używanie jest • Address Book: jest to interfejs umożliwia-
wymagane przy tworzeniu aplikacji korzy- jący przeglądanie oraz edycję poszcze-
stających z natywnego interfejsu użytkow- gólnych rekordów bazy danych kontak-

T
ak jak inne produkty Apple, tak i nika, oraz wymóg posiadania systemu ope- tów.
iPhone zachwyca użytkowników racyjnego Mac OS, który jest niezbędny do • Core Foundation: odpowiada między in-
swoim wyglądem, estetyką i jako- instalacji iPhone SDK. nymi za: zarządzanie danymi oraz ich
ścią wykonania. Pierwszy moment styczno- kolekcjami, operacje na ciągach znaków,
ści, pierwsze uruchomienia wbudowanych Architektura iPhone OS przetwarzanie preferencji użytkownika
aplikacji i już widać, co jest tu najważniej- Architektura systemu operacyjnego iPho- oraz zarządzanie wątkami.
sze: prosta i intuicyjna obsługa. Jeden kla- ne jest bardzo podobna do architektu- • Core Location: umożliwia odczyt współ-
wisz oraz wielodotykowy ekran (ang. multi- ry systemu Mac OS X. Obydwa te rozwią- rzędnych geograficznych urządzenia po-
touch screen) to wszystko, czego potrzebuje- zania oparte są na podobnym jądrze i ma- branych na podstawie danych z modułu
my do sterowania. Nie można również po-
minąć wszechobecnej przejrzystości GUI.
Doskonałym potwierdzeniem wysokiej ja-
kości tego urządzenia jest liczba dostęp- �����
����� ����������
nych dla niego aplikacji, oferowanych w �����
sklepie Apple. Już pierwsze oficjalne wy-
danie SDK przyciągnęło rzeszę programi- ���� ���� �����
����� �������� ������ �������
stów, którzy w niezwykle krótkim czasie �������� ����� ������
zaprezentowali owoce swojej pracy. Warto
dodatkowo zwrócić uwagę, iż aplikacje te ���� ������� ���� ���� ���
��������� ������
wydano po niezwykle atrakcyjnych cenach. �������� ���� ���������� �������� �������
Wytłumaczenie tego faktu jest proste: wraz
z malejącym nakładem pracy maleje ce- ����������������
na końcowego produktu, a z nią – wprost �������
proporcjonalnie – rośnie jego sprzedaż. Po- ������������
twierdzeniem tej reguły są statystyki udo-
stępniane przez Apple Store: tysiące do-
stępnych aplikacji oraz miliony ich pobrań
to nic innego jak wymowna kropka nad i w
tym temacie. Niestety, tak jak nie ma róży
bez kolców, tak i programista iPhone musi Rysunek 1. Architektura iPhone OS

132 SDJ Extra 34 Biblia


iPhone SDK

GPS, operatora sieci komórkowej lub po- cyjnego (http://developer.apple.com/iphone/ my plik iPhone SDK, który jest programem
łączenia WIFI. program/start/register/). Po zalogowaniu się instalacyjnym.
• CFNetwork: jest strukturą odpowiada- na wyżej wymienionej stronie pobieramy Do kolejnego kroku przechodzimy, na-
jącą za obsługę połączeń sieciowych. SDK oraz przeprowadzamy proces instala- ciskając przycisk Continue. Kolejny ekran
Interfejs ten pozwala na tworzenie po- cji. Pik z SDK w wersji 2.2.1 zajmuje oko- daje możliwość zapoznania się z licencją
łączeń z wykorzystaniem gniazd BSD, ło 1.7 GB (jest to obraz dysku). Podwój- SDK, którą akceptujemy, naciskając Conti-
tworzenie szyfrowanych połączeń ne kliknięcie rozpoczyna proces podłącze- nue. Dalej pojawia się ekran wyboru doce-
zgodnych z SSL oraz TLS. Wspiera- nia dysku oraz uruchamia menadżer pli- lowej partycji, na której SDK będzie zain-
ne protokoły to m.in. HTTP, HTTPS ków. Podwójnym kliknięciem uruchamia- stalowane. Aby zainstalować iPhone SDK
oraz FTP.
• SQLite: odpowiada za dostęp do wbudo- Listing 1. Implementacja metody applicationDidFinishLaunching
wanej bazy danych typu SQL.
• XML Support: umożliwia parsowanie - (void)applicationDidFinishLaunching:(UIApplication *)application
dokumentów XML. {
[ window addSubview:[ navigationController view ]];
Kolejna warstwa – Media, wykorzysty- [ window makeKeyAndVisible ];
wana jest przy dostępie do grafiki 2D, }
3D, audio oraz video. Warstwa ta składa
się z takich technologii jak: OpenGL ES, Listing 2. Implementacja metody viewDidLoad
Quartz, oraz Audio Core. Media to rów- - (void)viewDidLoad
nież Animation Core, czyli zaawansowa- {
ny silnik animacji oparty na języku Ob- [ super viewDidLoad ];
jective-C. self.title = @"First View";
Najbardziej istotną warstwą w systemie }
iPhone OS jest warstwa Cococa Touch. Za-
wiera ona między innymi takie infrastruk- Listing 3. Implementacja metody numberOfSectionsInTableView
tury jak: - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
• UIKit Framework: jest jedną z pod- return 1;
stawowych bibliotek wykorzystywa- }
nych przy tworzeniu aplikacji w iPho-
ne SDK. Pełni ona rolę obsługi interfej- Listing 4. Implementacja metody numberOfRowsInSection
su graficznego, zdarzeń, zarządza apli- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection
kacją, jej oknami oraz interakcją z użyt- (NSInteger)section
kownikiem. {
• Foundation Framework: jest swoistego return 6;
rodzaju opakowaniem (ang. wrapper) }
dla danych dostępnych w warstwie Listing 5. Implementacja metody cellForRowAtIndexPath
Core Services. Interfejs ten odpowia- -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
da za kolekcje danych, operacje na cią- (NSIndexPath *)indexPath
gach znaków oraz między innymi za {
zarządzanie datą i czasem, preferen- static NSString *CellIdentifier = @"Cell";
cjami użytkownika czy wątkami i pę- UITableViewCell *cell = [ tableView dequeueReusableCellWithIdentifier:
tlami. CellIdentifier ];
• Address Book UI Framework: umożli-
wia integrację zewnętrznych aplika- if (cell == nil)
cji z natywną bazą danych kontaktów. {
Aplikacje mogą uruchamiać poszcze- cell = [[[ UITableViewCell alloc ] initWithFrame:CGRectZero reuseIdentifier:
gólne widoki niezbędne przy dodawa- CellIdentifier ] autorelease ];
niu kontaktów, ich edycji oraz prze- }
glądaniu.
NSString* label = [ NSString stringWithFormat:@"cell %d", indexPath.row ];
iPhone SDK [ cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator ];
Najbardziej aktualną wersję pakietu iPho- [ cell setText:label ];
ne SDK można ściągnąć ze strony http:
//developer.apple.com/iphone. Wersja, z któ- return cell;
rej korzystałem podczas pisania niniejsze- }
go artykułu, to 2.2.1. Aby pobrać SDK,
niezbędne jest posiadanie swojego Apple Listing 6. Definicja funkcji didSelectRowAtIndexPath
ID. Jest to identyfikator, który reprezen- -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
tuje poszczególną osobę korzystającą z pro- *)indexPath
duktów Apple. W przypadku, gdy jeszcze {
nie posiadasz tego identyfikatora, zapra- }
szam do wypełnienia formularza rejestra-

www.sdjournal.org 133
Programowanie iPhone OS

w wersji 2.2.1, wymagane jest około 5GB kacji, ich testów oraz instalacji na urządze- cy XCode jest niezwykle proste. Do dys-
wolnej przestrzeni dyskowej. Aby konty- niu z platformą iPhone OS. Są to XCode, pozycji mamy wygodny generator szablo-
nuować proces instalacji, zaznaczamy od- Interface Builder oraz Instrumenty. XCo- nów aplikacji oraz klas. Pomocny okazu-
powiednią partycję, a następnie naciska- de to IDE (ang. Integrated Development Envi- je się również tryb debugowania w edyto-
my przycisk Continue. Ostatnim etapem ronment) wykorzystywane przy tworzeniu rze kodu, który umożliwia między innymi
konfiguracji instalatora jest wybór narzę- aplikacji zarówno dla systemu Mac OS X, podgląd wartości poszczególnych zmien-
dzi, które mają zostać zainstalowane. Wy- jak i dla iPhone OS. Narzędzie to umożli- nych bez przełączania się w tryb pełnego
braną konfigurację należy zaakceptować wia proste i wygodne zarządzanie projek- debuggera. XCode radzi sobie bez proble-
przyciskiem Continue. Rozpoczyna się pro- tami, składanie paczek wykonywalnych mu z dopełnianiem, kolorowaniem skład-
ces instalacji. (zarówno w wersjach pod symulator oraz ni, ukrywaniem bloków kodu oraz - na co
Pokrótce postaram się omówić zawartość urządzenie), debugowanie aplikacji oraz warto zwrócić uwagę - pozwala definiować
zainstalowanej paczki. W skład SDK wcho- ich instalację na urządzeniu końcowym. oraz stosować własne makra. Wspomniane
dzą narzędzia niezbędne do produkcji apli- Stworzenie pierwszej aplikacji przy pomo- makra umożliwiają wstawianie wcześniej
zdefiniowanych bloków kodu za pomocą
poszczególnych słów kluczowych. Wraz z
XCode dostarczony jest symulator, który
do pewnego stopnia udaje fizyczne urzą-
dzenie (iPhone/iPod Touch).
Interface Builder służy do tworzenia in-
terfejsów graficznych użytkownika. Po-
zwala on na przegląd dostępnych kompo-
nentów, umiejscowienie ich na ekranie
oraz połączenie z kodem źródłowym apli-
kacji. Efektem końcowym działania pro-
gramu jest plik NIB, który następnie mo-
że zostać zaimportowany w projekcie. Mu-
szę przyznać, iż narzędzie to zrobiło na
mnie bardzo pozytywne wrażenie przede
wszystkim dlatego, iż w prosty sposób po-
zwala ono na modyfikacje poszczególnych
komponentów oraz ich integrację z kodem
źródłowym projektu.
Instrumenty to narzędzia, które pod róż-
nym kątem pozwalają sprawdzić tworzo-
ną aplikację. Pozwalają one między inny-
mi przetestować program pod względem
szybkości jego działania, obciążenia proce-
sora, zużycia zasobów czy wycieków pamię-
ci. Możemy również sprawdzić intensyw-
Rysunek 2. Wybór szablonu aplikacji ność połączeń sieciowych czy historię do-

Co nowego w iPhone SDK dla iPhone OS 3.0


iPhone OS 3.0 jest nowym systemem operacyjnym przeznaczonym dla urządzeń iPhone oraz iPod Touch, który wraz z nowym urządzeniem
(iPhone 3.0 S) ujrzy światło dzienne w czerwcu tego roku. Telefon ten reklamowany jest jako szybszy, bardziej funkcjonalny oraz pracujący dłu-
żej od swojego poprzednika. Nowości, na które z niecierpliwością czekają użytkownicy poprzednich wersji iPhone, to na pewno możliwość wy-
syłania wiadomości typu MMS oraz dostępność modułu Bluetooth. Oprogramowanie 3.0 dostarcza również ciekawą funkcję przeszukiwania
pamięci urządzenia, znaną ze środowiska Mac OS - SpootLight Search. Mechanizm ten umożliwia automatyczne wyszukiwanie określonej frazy
m.in. w wiadomościach e-mail, w książce adresowej oraz w kalendarzu. Oprócz kilku nowych aplikacji pojawiają się również udoskonalone wer-
sje kalendarza, oraz programu umożliwiającego monitorowanie notowań Giełdy Papierów Wartościowych. Niewielkie zmiany widoczne są rów-
nież w ustawieniach użytkownika.
Wraz z nowym oprogramowaniem pojawia się iPhone SDK 3.0. Zawiera on między innymi API do obsługi Open GL ES 2.0. Programiści otrzy-
mują również możliwość korzystania z wbudowanej kamery, zarówno do robienia zdjęć, jak i kręcenia filmów. Jednym z dodanych interfej-
sów jest kompas, który uzupełnia funkcjonalność modułu GPS. Oprócz wskazania dokładnego kierunku geograficznego urządzenia progra-
mista może sprawdzić, czy urządzenie znajduje się w orientacji panoramicznej bądź portretowej. Firma Apple przygotowała również mecha-
nizm dostępu do sklepu Apple z poziomu aplikacji oraz notyfikacje Apple Push. System notyfikacji jest to brakująca we wcześniejszej wersji
możliwość komunikowania się zewnętrznego serwera z użytkownikiem bądź aplikacją - nawet aktualnie nieuruchomioną. Dostarczanie wia-
domości polega na przesyłaniu odpowiednich komunikatów z serwera do konkretnego urządzenia. Połączenie odbywa się poprzez zadany
adres IP urządzenia. Notyfikacja może zostać przedstawiona użytkownikowi na ekranie bądź może być przechwycona przez konkretną apli-
kację. iPhone SDK 3.0 dostarcza również możliwość korzystania z mechanizmu połączeń Peer to Peer pomiędzy urządzeniami. API z pewno-
ścią zostanie wykorzystane przez programistów gier, których produkty będą mogły zawierać tryb gry dla dwóch graczy. W ramach obsługi
mechanizmów komunikacji pojawia się również interfejs obsługi urządzeń peryferyjnych. Każde urządzenie dedykowane dla systemu iPho-
ne, podłączone zarówno przez kabel, Bluetooth, jak i sieć bezprzewodową, może być sterowane z poziomu aplikacji. Ostatnią nowością, na
którą warto zwrócić uwagę, jest interfejs Map Kit. Wprowadza on możliwość integracji aplikacji z serwisem Google Mobile Maps. Zewnętrz-
ne programy mogą wyświetlać widok mapy, przeszukiwać bazę danych lokalizacji, obliczać trasy pomiędzy zadanymi punktami oraz ozna-
czać dowolne miejsca pinezkami Google. Uzupełnieniem w stosunku do poprzedniej wersji SDK jest również dostęp do biblioteki muzycznej
użytkownika.

134 SDJ Extra 34 Biblia


iPhone SDK

stępów do systemu plików. Wyniki działa- niuje pustą tabelę, która była widoczna na ekran. Tytuł tego widoku zmienimy na
nia narzędzi przedstawione są w przejrzy- wcześniej w oknie symulatora. Uzupeł- First View.
stej formie graficznej. Ciekawostką jest to, niając kolejne metody, zmienimy tytuł ta- Listing 3 przedstawia metodę, która wy-
iż wspomniane testy można przeprowadzić beli oraz wypełnimy jej komórki przykła- woływana jest przez system po to, by spraw-
zarówno na symulatorze, jak i na urządze- dowymi tekstami. Metoda przedstawiona dzić, ile jest sekcji w tabeli. Tabela może zo-
niu końcowym. na Listingu 2 zostanie wywołana w mo- stać podzielona na wiele sekcji, które są póź-
mencie, gdy widok zostanie załadowany niej grupowane. W naszym przypadku po-
Pierwszy program
Przy tworzeniu pierwszego projektu sko- Listing 7. Nagłówek klasy CustomCell
rzystam z przygotowanych w XCodzie sza-
blonów aplikacji. Po uruchomieniu IDE z @interface CustomCell : UITableViewCell
menu File wybieram opcję New Project. Na- {
stępnie w oknie przedstawionym na Ry- UILabel* upperText;
sunku 2 wybieram szablon Navigation-Ba- UILabel* lowerText;
sed Application, po czym wprowadzam na- }
zwę projektu. W ten sposób XCode wyge-
nerował pierwszy projekt definiujący pro- -(void) setUpper:( NSString* )text;
stą aplikację. Przed przystąpieniem do edy- -(void) setLower:( NSString* )text;
cji wygenerowanych klas warto sprawdzić
aktualny wygląd aplikacji. Po naciśnięciu @end
kombinacji klawiszy command+R program
kompiluje się oraz uruchamia w symulato- Listing 8. Inicjalizacja obiektu DetailsTableViewCotroller
rze.Zawiera ona jedynie pustą tabelę oraz - (id) initWithFrame:(CGRect)frame
pusty pasek nawigacji. reuseIdentifier:( NSString *) reuseIdentifier
Pora rozpocząć implementację. Pra- {
wa strona IDE prezentuje drzewo projek- if ( self = [super initWithFrame:frame
tu. W katalogu Classes znajdują się aktu- reuseIdentifier:reuseIdentifier ])
alnie tylko dwie klasy: AppDelegate oraz {
RootViewController. Pierwsza klasa jest // Initialization
delegatem aplikacji – główną klasą projek- UIView *view = self.contentView;
tu, zaś druga klasa definiuje widok tabeli.
Listing 1 przedstawia metodę, która wy- lowerText= [[ UILabel alloc ] init ];
woływana jest w momencie, gdy aplikacja UIFont* font = [ UIFont boldSystemFontOfSize:20.0 ];
zostaje uruchomiona. Zadaniem tej meto- lowerText.font = font;
dy jest przygotowanie interfejsu graficzne- [ view addSubview:lowerText ];
go użytkownika. Poszczególne widoki zo- [ lowerText release ];
stają tu dodane do okna aplikacji; w przy-
padku omawianego programu jest to wi- upperText = [[ UILabel alloc ] init ];
dok nawigacji. font = [ UIFont systemFontOfSize:20.0 ];
Rzućmy teraz okiem na zawartość kla- upperText.font = font;
sy RootViewController. Klasa ta defi- [ view addSubview:upperText ];
[ upperText release ];

}
return self;
}

Listing 9. Implementacja metody layoutSubviews


- (void)layoutSubviews {
[super layoutSubviews];
// getting the cell size
CGRect contentRect = self.contentView.bounds;

CGFloat boundsX = contentRect.origin.x;


CGRect frame;

frame = CGRectMake(boundsX + 10, 3, contentRect.size.width - 15, 20);


upperText.frame = frame;

frame = CGRectMake(boundsX + 10, 23, contentRect.size.width - 15, 35);


lowerText.frame = frame;
}
}
Rysunek 3. Widok pierwszej tabeli aplikacji

www.sdjournal.org 135
Programowanie iPhone OS

zostawimy tu domyślną wartość 1. Następ- sekcji. Jeśli tabela ma wiele sekcji, to wspo- ny jest jako parametr section. W naszym
nie (patrz: Listing 4) system sprawdza, ile mniana metoda jest wywoływana dla każdej przypadku zwracamy przykładową wartość
rzędów w tabeli jest dostępnych dla danej sekcji oddzielnie. Numer sekcji przekazywa- 6: chcemy, aby tabela miała wypełnionych
sześć komórek.
Listing 10. Metody służące do ustawiania zawartości etykiet komórki Dla każdej z komórek wywoływana jest
metoda przedstawiona na Listingu 5. W
-(void) setLower:(NSString*)text tej metodzie tworzymy i inicjalizujemy
{ poszczególne wiersze tabeli. Numer aktu-
lowerText.text = text; alnie tworzonej komórki przekazany jest
} w parametrze indexPath. W pierwszej li-
-(void) setUpper:(NSString*)text nii tego listingu definiujemy identyfika-
{ tor danego obiektu. Mechanizm ten umoż-
upperText.text = text; liwia powtórne korzystanie z wcześniej
} skonstruowanego obiektu. Ponowne uży-
cie obiektu jest o wiele szybsze niż two-
Listing 11. Implementacja metody viewDidLoad w klasie DetailsTableViewCotroller rzenie go na nowo. Po tych kilku krokach
- (void)viewDidLoad komórka tabeli jest gotowa do wypełnie-
{ nia danymi.
[ super viewDidLoad ]; Dalej tworzymy ciąg znaków przecho-
self.title = @"second view"; wujący słowo komórka (ang. cell), do któ-
moreButton = [[ UIBarButtonItem alloc ] initWithTitle:@"More" rego dopiszę numer wiersza danej komór-
style:UIBarButtonItemStylePlain ki. Za pomocą następnej linii [ cell
target:self setAccessoryType: UITableViewCellAcc
action:@selector(onMore)]; essoryDisclosureIndicator ]; określa-
self.navigationItem.rightBarButtonItem = moreButton; my typ elementu graficznego, który zosta-
} nie wyświetlony po prawej stronie każde-
go wiersza.
Listing 12. Metody wykorzystywane przy konstrukcji tabeli w klasie DetailsTableViewCotroller Typ tej grafiki powinien odpowiadać ak-
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView cji, jaka może zostać wykonana dla danej
{ komórki. W przedstawionym przypadku
return 1; element ten ma być zachętą do kliknięcia.
} Kolejna linia listingu przedstawia umiesz-
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section czenie przygotowanego wcześniej ciągu
{ znaków w utworzonej komórce. Po uru-
return 2; chomieniu aplikacji na symulatorze po-
} jawia się widok przedstawiony na Rysun-
ku 3.
Listing 6 przedstawia metodę, która zosta-
nie wywołana po kliknięciu w daną komórkę
tabeli. Implementacja tej metody zostanie do-
dana później.
W kolejnym kroku przygotujemy widok,
który będzie uruchamiany po kliknięciu w
daną komórkę. Będzie to również tabela. Za-
czniemy od stworzenia nowej klasy. Z me-
nu XCode wybieramy opcję File, a następnie
New File. Uruchomione okno (Rysunek 4)
wyraźnie przypomina to, z którego wybra-
łem wcześniej szablon aplikacji.
W tym przypadku możemy wybrać
typ klasy, który chcemy stworzyć. Wy-
bieramy opcję UITableViewControl-
ler subclass, ponieważ widok, który bę-
dę tworzył, będzie tabelą. Klasa bazowa
UITableViewController definiuje tabelę.
Nową klasę nazwę DetailsTableViewCotr
oller. Implementacja nowo wygenerowa-
nego pliku jest nieomal identyczna z im-
plementacją tabeli RootViewController. Z
racji tego, iż tabela ta ma zawierać szczegó-
łowe informacje, chcielibyśmy, aby poszcze-
gólne komórki umieszczone w tej tabeli za-
wierały po dwa teksty, umieszczone jeden
Rysunek 4. Okno wyboru podklasy nowo tworzonego obiektu nad drugim. Pozwoli to na przedstawienie

136 SDJ Extra 34 Biblia


iPhone SDK

przykładowych atrybutów oraz wartości im


przypisanych. Do tego celu niezbędne bę- Listing 13. Metoda przedstawiająca tworzenie komórek typu CustomCell
dzie stworzenie kolejnej klasy, która będzie - (UITableViewCell *)tableView:(UITableView *)tableView
dziedziczyć po klasie UITableViewCell. Po- cellForRowAtIndexPath:(NSIndexPath *)indexPath
wtarzamy w tym celu kroki opisane w przy- {
padku tworzenia DetailsTableViewCotr static NSString *CellIdentifier = @"DetailsCell";
oller. Zamiast UITableViewController z
ekranu przedstawionego na Rysunku 4 wy- CustomCell *cell =
bieramy UITableViewCell jako bazę two- (CustomCell*)[ tableView dequeueReusableCellWithIdentifier:CellIdentifier ];
rzonej klasy.
Nowa klasa nazywać się będzie if (cell == nil)
CustomCell. Listing 7 przedstawia jej na- {
główek. cell = [[[ CustomCell alloc] initWithFrame:CGRectZero
Aby wyświetlić dwie linie tekstu w da- reuseIdentifier:CellIdentifier ]
nej komórce, skorzystamy z dwóch kompo- autorelease ];
nentów typu UILabel. Kolejne linie wier- }
sza nazwiemy upperText oraz lowerText.
Listing 7 przedstawia również dwie meto- switch (indexPath.row) {
dy, które wykorzystamy do ustawienia war- case 0:
tości poszczególnych etykiet. Pora zajrzeć [cell setUpper:@"First row upper text"];
do implementacji klasy. Do wygenerowanej [cell setLower:@"First row lower text"];
przez XCode metody widocznej na Listin- break;
gu 8 należy dodać tworzenie obiektów ty-
pu UILabel. Etykieta ( ang. label ) to kom- case 1:
ponent, który wyświetla tekst. W pierwszej [cell setUpper:@"Second row upper text"];
linii listingu uruchamiamy metodę klasy [cell setLower:@"Second row lower text"];
bazowej. break;
W kolejnych liniach listingu tworzę
pierwszą etykietę. Następnie przypisuję default:
jej pogrubioną czcionkę systemową o wiel- break;
kości 20 pikseli. Komponent ten następ- }
nie dodajemy do widoku danej klasy. W return cell;
ten sam sposób zostaje stworzony kolejny }
wiersz komórki.
Aby poprawnie umiejscowić komponen- Listing 14. Nagłówek klasy DetailsTableViewController
ty na ekranie, dodamy metodę przedsta- @interface DetailsTableViewCotroller :
wioną na Listingu 9. Najpierw wywołuje- UITableViewController <UITableViewDelegate>
my metodę z klasy bazowej, po czym pobie- {
ramy rozmiar aktualnego widoku. W kolej- UIBarButtonItem* moreButton;
nych liniach tworzymy ramy o poszczegól- }
nych parametrach, które następnie zostają
przypisane do utworzonych wcześniej kom- Listing 15. Metoda, która określa wysokość pojedynczej komórki w tabeli
ponentów.
Listing 10 przedstawia wspomniane - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath
wcześniej metody setLower i setUpper, *)indexPath
które będą wykorzystane do ustawienia {
wartości poszczególnych etykiet komórki. return 60.0;
Przekazane w parametrach teksty zosta- }
ją zapisane w poszczególnych etykietach
wiersza. Listing 16. Metoda wyświetlająca przykładowy dialog
Powróćmy teraz do implementacji Deta
ilsTableViewCotroller. Listing 11 przed- -(void) onMore
stawia metodę, która wywoływana jest po {
załadowaniu widoku. Oprócz ustawienia UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"MessageTitleText"
tytułu widoku tworzymy przycisk, któ- message:@"More information"
ry widoczny będzie w pasku nawigacji. delegate:nil
Kolejne parametry używane przy inicja- cancelButtonTitle:@"Hide me"
lizacji tego obiektu określają: tekst, któ- otherButtonTitles:nil];
ry będzie zawierał dany przycisk oraz styl [alert show];
przycisku, klasę, która ma zostać powia- [alert release];
damiana o użyciu danego przycisku, oraz }
metodę (ang. selector), która będzie wy-
woływana po jego przyciśnięciu. Zmien-
ną moreButton dodaję do nagłówka klasy

www.sdjournal.org 137
Programowanie iPhone OS

DetailsTableViewCotroller w postaci: Listing 12 przedstawia omówione wcze- eView oraz numberOfRowsInSection. Po-
UIBarButtonItem* moreButton; śniej metody numberOfSectionsInTabl zostało jedynie stworzenie poszczegól-

Listing 17. Tworzenie obiektu DetailsTableViewController

- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailsTableViewCotroller* secondTable =
[[ DetailsTableViewCotroller alloc ]
initWithStyle:UITableViewStyleGrouped ];

sdjAppDelegate* appDelegate =
[ UIApplication sharedApplication ].delegate;

[[ appDelegate navigationController]
pushViewController:secondTable
animated:YES];

[secondTable release];
}

Listing 18. Inicjalizacja połączenia asynchronicznego – iPhone OS


- (void)sendHTTPMessage:(NSData*)body toURL:(NSString*)url
{
NSURL* url2 = [NSURL URLWithString:url];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url2];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:body];
[request setValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
NSHTTPURLResponse* response = [[[NSHTTPURLResponse alloc] init] autorelease];
}

Listing 19. Inicjalizacja połączenia synchronicznego – iPhone OS


NSHTTPURLResponse* response = [[[NSHTTPURLResponse alloc] init] autorelease]; NSData* resp = [NSURLConnection sendSynchronousRequest:
requestreturningResponse:&response error:err];

Listing 20. Implementacja metody didReceiveResponse


- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.receivedData setLength:0];
}

Listing 21. Implementacja metody didReceiveData


- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.receivedData appendData:data];
}

Listing 22. Obsługa nieudanego połączenia


- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[self.delegate connectionDidFail:error];
[connection release];
}

Listing 23. Metoda wywoływana w momencie zakończonej transakcji


- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
//(...)
}

138 SDJ Extra 34 Biblia


iPhone SDK

nych elementów tabeli. Listing 13 przed- tabeli detali. Na pasku nawigacyjnym auto- tu NSURLConnection. W ten sposób rozpo-
stawia sposób, w jaki konstruujemy kolej- matycznie pojawia się przycisk z tytułem wi- czyna się proces asynchronicznego przesy-
ne komórki. Tym razem tworzone wier- doku. Przycisk ten umożliwia powrót do po- łania danych. Aby skorzystać z trybu syn-
sze są zdefiniowanego wcześniej typu przedniej tabeli. chronicznego, wystarczy zamienić ostat-
CustomCell. Następnie sprawdzamy in- Aby mieć możliwość instalacji aplikacji nią linię Listingu 18 oraz dodać dwie linie
deks aktualnej komórki, by wypełnić ją na urządzeniu, należy dołączyć do iPho- przedstawione na Listingu 19. W momen-
odpowiednim tekstem, po czym zwracam ne Developer Program. Uczestnictwo w cie, gdy połączenie zostanie nawiązane,
stworzony obiekt. tym programie jest niestety płatne. Pod- system prześle odpowiedź z serwera, wy-
Aby wszystko prezentowało się poprawnie stawowe członkostwo kosztuje 99$. Wraz wołując metodę z Listingu 20. Następnie,
na ekranie, konieczne jest ustawienie wyso- z jego wykupieniem otrzymujemy certy- gdy serwer prześle porcję danych, urucho-
kości pojedynczej komórki tabeli. Modyfiku- fikat, który jest niezbędny w celu podpi- miona zostanie metoda z Listingu 21.
ję nagłówek klasy DetailsTableViewControl sania aplikacji przed jej instalacją. Certy- Zakończoną sukcesem transakcję po-
ler, dodając protokół UITableViewDelegate fikat ten jest również niezbędny przy wy- twierdza metoda connectionDidFinish
( Listing 14 ), oraz implementuję metodę syłaniu własnych aplikacji do sklepu Ap- Loading ( Listing 23 ), zaś wszelkie błę-
heightForRowAtIndexPath przedstawioną ple. Osoby zainteresowane instalacją apli- dy połączenia powinny zostać obsłużone
na Listingu 15. kacji bez podpisywania odsyłam do wyszu- w metodzie przedstawionej na Listingu
Implementację tabeli detali kończymy, de- kiwarki Google ;) 22. Niestety, realizacja podobnego zada-
finiując metodę onMore. Tworzymy w niej nia na platformie Symbian jest o wiele bar-
dialog, który będzie wyświetlał przykładowy Porównanie: dziej skomplikowana. Aby przeanalizować
tekst. Inicjalizacja tego dialogu polega na po- iPhone OS vs. Symbian OS ten proces, zapraszam do przejrzenia przy-
daniu jego tytułu, treści oraz tekstu, jaki ma Jako że w ciągu ostatnich lat przyszło mi kładu znajdującego się na stronie http://
się pojawić na przycisku zamykającym dany pracować zarówno nad aplikacjami pod wiki.forum.nokia.com/index.php/How_
dialog. Jak to zrobić w praktyce, przedstawia iPhone, jak i pod Symbian OS, dlatego po- to_Make_an_HTTP_Connection_Using_
Listing 16. kusiłem się o porównanie tych dwóch plat- TCP/IP_with_RSocket. Wygląda na skom-
Pozostaje jeszcze tworzenie obiek- form z programistycznego punktu widze- plikowane, prawda? I niestety – takie jest
tu nowej tabeli, które znajdzie się w me- nia. W tym celu postanowiłem przygoto- w rzeczywistości...
todzie didSelectRowAtIndexPath, w kla- wać dwie aplikacje: jedną pod Symbia- Do tego dochodzą nieco pokraczne idio-
sie RootViewController. Jest ona wy- na, drugą zaś pod iPhone OS, realizujące my narzucane przez Symbiana (np. stos
wołana po kliknięciu komórki tabeli identyczne zadanie: przesyłanie danych czyszczenia czy deskryptory). Realizacja te-
RootViewController. Listing 17 pokazuje, metodą POST poprzez protokół HTTP. go zadania pod iPhone jest o niebo prostsza
jak tworzony jest obiekt typu DetailsTab W tym miejscu nie będę przytaczał peł- i bardziej intuicyjna (zakładając oczywiście
leViewController oraz w jaki sposób mo- nych źródeł tych aplikacji, skupię się je- dobrą znajomość języka Objective-C). Pod-
żemy dodać go do widoku nawigacji. War- dynie na fragmentach dotyczących re- sumowując to szybkie porównanie: w mo-
to zauważyć, że przy inicjalizacji obiektu alizacji wspomnianego zadania. Listing jej subiektywnej ocenie – zwycięża zdecy-
tabeli podajemy jej typ. W tym przypadku 18 obrazuje inicjację połączenia w języ- dowanie iPhone OS!
jest to typ UITableViewStyleGrouped. Styl ku Objective-C. Kilka pierwszych wywo-
ten charakteryzuje się grupowym wyświe- łań metod to nic innego jak przygotowa- Podsumowanie
tlaniem poszczególnych sekcji. Nowo utwo- nie adresu URL, ustawienie odpowied- W powyższym artykule przedstawiłem
rzony obiekt dodajemy do widoku nawiga- nich wartości nagłówka oraz rozpoczę- wstęp do programowania aplikacji pod
cji, co powoduje animowane wyświetlenie cie transakcji poprzez inicjalizację obiek- iPhone przy pomocy standardowego SDK.
Starałem się zawrzeć informacje przydatne
do rozpoczęcia pracy z tą platformą. Wy-
daje mi się, iż rozbudowane możliwości
nadchodzącej wersji SDK oraz urządzenia
iPhone 3.0 S powinny być kuszące dla pro-
gramistów aplikacji. Zapraszam serdecznie
do zapoznania się z dostępnym API, IDE
oraz innymi narzędziami dostarczonymi
przez Apple. Za dodatkową motywację do
rozpoczęcia nauki może posłużyć świado-
mość, że dziesiątki i setki tysięcy użytkow-
ników czekają na nowe aplikacje pod iPho-
ne – być może na Twoje aplikacje!

TOMASZ DUBIK
Pracuje na stanowisku Programista Aplikacji Mo-
bilnych w firmie BLStream. Tworzeniem aplikacji
dla urządzeń przenośnych zajmuje się od 3 lat.
Przez ten czas miał okazję poznać takie platfor-
Rysunek 5. Tabela typu DetailsTableViewCont Rysunek 6. Wygląd prostej aplikacji my jak iPhone OS, Symbian OS/S60 oraz Palm OS.
roller wygenerowanej przez XCode Kontakt z autorem: tomasz.dubik@blstream.com

www.sdjournal.org 139
Programowanie iPhone OS

Objective-C
kontra Java i C++
Wprowadzenie do języka
Java i C++ panują niepodzielnie w działce technologii mobilnych. Język
Objective-C jest stosunkowo nowym graczem na tym rynku, stoi jednak
za nim potężna marketingowa siła platformy Apple iPhone/iTouch.
Niniejszy artykuł zawiera szybkie wprowadzenie do Objective-C oraz
porównanie jego możliwości z językami Java i C++.
cować zaskakująco dużą liczbą ciekawych
Dowiesz się: Powinieneś wiedzieć: wniosków.
• Jak wyglądają podstawowe konstrukcje ję- • Jak programować w języku Java bądź C++. Do dzieła więc! Na Listingach 1, 2 oraz 3
zyka Objective-C; zaprezentowane są źródła programów Wi-
• Jak Objective-C ma się do takich języków jak taj Świecie! zapisanych w C++, Java i Objecti-
Java i C++. ve-C. Na dobry początek sugeruję przejrzenie
tych Listingów.
Przy pisaniu niniejszego artykułu zało-
(tudzież, Twój szef stwierdził za Ciebie), iż żyłem sobie, iż przeznaczony on będzie dla
nadszedł czas na rozpoczęcie nauki języka osób znających stosunkowo dobrze języki
Objective-C, to zapraszam do lektury ni- C++ oraz Java (patrz sekcja Powinieneś wie-
Poziom trudności niejszego artykułu. Postaram się przedsta- dzieć). Wnioskuję zatem, iż programy przed-
wić ten temat z perspektywy znajomych stawione na Listingach 1 i 2 nie wymagają
Tobie języków i przekonać Ciebie, że – jak dogłębnych wyjaśnień. W przypadku Witaj

J
eśli Twoja praca zawodowa wiąże się mówi stare przysłowie – nie taki diabeł Świecie w C++ wita nas wysłużona funkcja
z programowaniem urządzeń mobil- straszny jak go malują. main, stanowiąca dziedzictwo języka C. Dy-
nych, to zapewne masz Drogi Czytel- W tym miejscu pozwolę sobie jedynie rektywa using namespace świadczy o tym,
niku doświadczenia bądź to z językiem Ja- dodać, iż poniższy artykuł nie pretenduje że język zaprojektowany przez Bjarne Stro-
va, tudzież z C++. Prymat tych języków w do miana podręcznika Objective-C. Nale- ustrupa wspiera przestrzenie nazw, zaś dość
mobilnym sektorze rynku wydawał się nie- ży go raczej uznać za mocno skondensowa- nietypowa składnia:
zachwiany od kilku dobrych lat. A tu nagle ny przegląd możliwości wspomnianego ję-
niespodzianka! zyka, połączony z szeregiem odniesień do cout << "Hello, World!" << endl;
Nagle na horyzoncie pojawiał się nieco C++ i Java, oraz rozszerzony o garść wska-
ezoteryczny język Objective-C, napędzany zówek i drogowskazów dla tych, którzy przypomina o tym, że C++ pozwala przeła-
potężną marketingowo-biznesową machiną chcieliby na poważnie kontynuować naukę dowywać operatory. Zawartość Listingu 2 po-
stojącą za nową platformą Apple: iPhone/ Objective-C. twierdza smutną prawdę, że aby zmusić pro-
iTouch. Prawdę mówiąc, gdyby jakieś dwa gram pisany w Javie (tj. w języku czysto obiekto-
lata temu ktoś oznajmił mi, że niedługo Po trzykroć: Witaj Świecie! wym) do wyświetlenia prostego napisu, trzeba
przyjdzie mi bliżej obcować z Objective- Znane chińskie powiedzenie mówi, iż jeden stworzyć osobną klasę i wyposażyć ją w publicz-
C, uśmiechnąłbym się zapewne lekcewa- fragment kodu źródłowego wart jest tysiąca ną, statyczną metodę main.
żąco. Dziś o tym języku słychać sporo; wy- słów (hmm... chyba coś pokręciłem...). Z tego Listing 3 przynosi za to szereg niespodzia-
nika to z prostego faktu: chcąc programo- względu zdecydowałem, iż zanim przejdę do nek. Na pierwszy rzut oka wygląda trochę zna-
wać natywne aplikacje pod iPhone/iTouch, omówienia poszczególnych właściwości oma- jomo. W pierwszej linii wita nas znajoma dy-
nie mamy praktycznie żadnej alternatywy. wianego języka, chciałbym zaproponować Ci rektywa preprocesora #include. Dalej mamy
I tak oto język, używany dotąd przez wą- ciekawy eksperyment w postaci prezentacji definicję funkcji main. W pierwszej linii tej-
ską grupę programistów natywnych apli- i analizy trzech aplikacji typu Witaj Świecie! że funkcji pojawia się coś jakby wskaźnik do
kacji dla MacOS, bazujących na Cocoa, zaprogramowanych kolejno w C++, Java oraz obiektu NSAutoreleasePool, a potem... No
stał się nagle niespodziewanie ważny, zaś Objective-C. właśnie – co potem!? Nagle ni stąd, ni zowąd
na wzmiankę o konieczności jego nauki ra- Jak wskazuje Paul Graham w jednym ze pojawia się przedziwna składnia:
czej nikt się już nie uśmiecha. Tak więc dro- swoich esejów (patrz ramka W sieci), anali-
gi Programisto Java/C++, jeżeli stwierdziłeś za takich małych programów może zaowo- [[NSAutoreleasePool alloc] init];

140 SDJ Extra 34 Biblia


Wprowadzenie do języka Objective-C

Czyżby nowa odmiana Lispa, tyle że z nawi- tyczność oraz pełną kontrolę typów, co w namizm, co miało zaowocować większą ela-
sami kwadratowymi zamiast okrągłych...? rezultacie miało zaowocować dużą wydaj- stycznością języka za cenę (nie)znaczne-
W kolejnej linii wywołanie funkcji NSLog, nością pisanych w nim programów. Objec- go spadku wydajności. Ta fundamentalna
która – wnioskując po przekazywanym do tive-C poszedł za to w zupełnie inną stro- różnica w założeniach projektowych spra-
niej argumencie – zdaje się być odpowiedni- nę: jego projektanci główny nacisk położy- wiała, iż pomimo wspólnego korzenia w
kiem funkcji printf ze standardowej biblio- li na charakterystyczny dla Smalltalk'a dy- postaci języka C, C++ i Objective-C w wie-
teki języka C.
Tylko czemu przed literałem napisowym Listing 1. Program Witaj Świecie! napisany w języku C++
występuje znak @? W kolejnej linii znajduje-
my po raz wtóry dziwne kwadratowe nawia- #include <iostream>
sy, zaś na końcu żegna nas znajoma instruk-
cja return 0;. using namespace std;
Takie mniej więcej myśli pojawiały się w
mojej głowie, kiedy pierwszy raz czytałem int main()
program typu Witaj Świecie! napisany w Ob- {
jective-C. Pora na wnioski. Patrząc z punk- cout << "Hello, World!" << endl;
tu widzenia osoby, która nie zna tego języ-
ka, możemy stwierdzić prawie na pewno, że return 0;
Objective-C: }

• jest na pewno bliższy językom C\C++ Listing 2. Program Witaj Świecie! napisany w języku Java
niż Javie; public class HelloWorld
• nie jest językiem czysto obiektowym {
i zapewne, podobnie jak C++, wspiera public static void main( String[] args )
wiele paradygmatów programowania; {
• nie posiada mechanizmu przestrzeni System.out.println("Hello, World!");
nazw; }
• jest wyposażony w preprocesor; }
• szykuje dla nieobeznanych z nim progra-
mistów semantyczne niespodzianki! Listing 3. Program Witaj, Świecie! napisany w języku Objective-C
#import <Foundation/Foundation.h>
Mam nadzieję, że mój nietypowy ekspery-
ment z potrójnym Witaj Świecie! rozbudził int main (int argc, const char * argv[])
Czytelniku Twoją ciekawość. Jeśli chciał- {
byś dowiedzieć się, jakie jeszcze niespo- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
dzianki szykuje dla Ciebie język Objecti- NSLog (@"Hello, World!");
ve-C, to zapraszam do dalszej lektury. Na
początek... [pool drain];
return 0;
...krótki rys historyczny }
Historia Objective-C zaczyna się we wcze-
snych latach 80 dwudziestego wieku. Spró- Listing 4. Język C++: plik nagłówkowy klasy Point (Point.hpp)
bujmy wczuć się na chwilę w atmosferę #ifndef __POINT_HPP_INCLUDED__
tamtego okresu. W środowisku programi- #define __POINT_HPP_INCLUDED__
stów systemowych niepodzielnie króluje ję-
zyk C (oczywiście nie opisany jeszcze jako class Point
standard ANSI). Obiektowość (dość ezote- {
ryczny paradygmat programowania stoso- public:
wany przez wąską grupę specjalistów) koja- Point( int x = 0, int y = 0 );
rzy się przede wszystkim ze... Smalltalk'iem
80 (będącym następnikiem Smalltalk'a 72). int X() const;
Pojawiają się pierwsze próby pożenienia wy- int Y() const;
dajności języka C z rewolucyjnymi mecha- void SetX( int x );
nizmami budowania abstrakcji rodem z ję- void SetY( int y );
zyków obiektowych. Jest rok 1983. Bjarne
Stroustrup zaczyna pracować nad językiem private:
C++. Z kolei Brad Cox oraz Tom Love, za- int m_X;
łożyciele firmy Stepstone, opracowują język int m_Y;
Objective-C. };
Aby zrozumieć fundamentalną różni-
cę pomiędzy tymi dwoma językami, nale- std::ostream& operator<<( std::ostream& os, const Point& p );
ży zrozumieć założenia projektowe przy-
jęte przez ich autorów. Mówiąc w dużym #endif // __POINT_HPP_INCLUDED__
uproszczeniu, Język C++ postawił na sta-

www.sdjournal.org 141
Programowanie iPhone OS

lu kwestiach różnią się diametralnie. Java, (ang. templates). Java – początkowo święcą- Niewątpliwie najbardziej rzucającym się
która pojawiła się na rynku ponad 10 lat ca wielkie tryumfy, powoli traci na znacze- w oczy (niektórzy powiedzieliby: najbar-
później niż C++ i Objective-C, miała stano- niu jako język wysokiego poziomu, wypie- dziej dziwacznym) elementem języka Ob-
wić kolejny krok ewolucyjny w stosunku do rana przez bardziej dynamiczne języki (wi- jective-C jest składnia wywoływania me-
C++. Ten czysto obiektowy język, również dać złoty środek nie zawsze jest... złoty). tod obiektów. Jeśli przez całe swoje zawo-
inspirowany pomysłami ze Smalltalk'a, w Objective-C doczekał się w 2007 roku no- dowe życie programowałeś w C++ lub w Ja-
wielu aspektach zbliża się do Objective-C wej odsłony (2.0) i zyskuje na popularności vie (tudzież w jednym z wielu innych, po-
(widać to chociażby na przykładzie mecha- na fali sukcesów firmy Apple. Jak potoczy pularnych języków wspierających paradyg-
nizmu refleksji); można by się wręcz poku- się dalej ta historia, pokaże czas. Dość hi- mat programowania obiektowego, np. C#,
sić o tezę, że w zamyśle swoich twórców Ja- storii! W kolejnych podpunktach zajrzymy Python, Ruby, JavaScript itd.), to najbar-
va miała stanowić złoty środek pomiędzy pod maskę Objective-C i zobaczymy cóż się dziej naturalną wydaje Ci się następująca
C++ a Objective-C. tam kryje. A jest co oglądać. konstrukcja:
Od początku lat 80-tych zarówno C++,
Java, jak i Objective-C przeszły długą drogę. Przegląd składni obiekt.metoda();
C++ doczekał się standardu ANSI (C++98), Podobnie jak C++, tak samo Objective-C jest
kolejna odsłona standardu tego języka jest nadzbiorem języka C. Oznacza to, że każdy, W Objective-C konstrukcja ta wygląda zgo-
właśnie opracowywana (C++0x), zaś sam poprawnie skonstruowany program, napisa- ła odmiennie:
język nadal cieszy się olbrzymim poważa- ny w języku C będzie poprawnie przetwo-
niem jako narzędzie programistów syste- rzony przez kompilator Objective-C. W tym [obiekt metoda];
mowych, głównie dzięki możliwości stoso- ujęciu Objective-C jest zdecydowanie bliż-
wania potężnych i wysoce wydajnych me- szy C++, niż Javie, która nadzbiorem języ- Warto w tym miejscu zauważyć, iż taka,
chanizmów abstrakcji w postaci szablonów ka C nie jest. nieco egzotyczna, składnia tyczy się je-
Podstawowy element nowości w skład- dynie wywołań metod; wywołania glo-
Listing 5. Język C++: plik z implementacją ni Objective-C wiąże się z mechanizmami balnych funkcji mają taką samą składnię
klasy Point (Point.cpp) programowania obiektowego oraz z tymi, jak język C czy C++. Co więcej, przed-
które wiążą się z zestawem nowych słów stawiona wyżej forma składniowa wią-
#include "Point.hpp" kluczowych. Objective-C stosuje tutaj dość że się z fundamentalną różnicą w znacze-
oryginalną konwencję (np. w porówna- niu stwierdzenia wywołanie metody, któ-
Point::Point( int x, int y ) niu do C++): słowa kluczowe specyficzne ra stanowi jeden z fundamentów modelu
: m_X( x ), dla tego języka oznaczone są prefiksem @. obiektowości w języku Objective-C. Mó-
m_Y( y ) Wspomniane słowa kluczowe to: @class, wiąc w dużym skrócie, wywołanie meto-
{ @interface, @implementation, @public, dy w tym języku to w rzeczywistości wy-
} @private, @protected, @try, @catch, słanie komunikatu do obiektu.
@throw, @finally, @end, @protocol, Na koniec tego szybkiego przeglądu skład-
int Point::X() const @selector, @synchronized, @defs oraz ni Objective-C warto zauważyć, iż komenta-
{ @encode. Znaczenie większości z wymie- rze w tym języku są zaznaczane identycznie
return m_X; nionych słów poznamy w dalszej części ni- jak w Javie i C++: dowolna jest zarówno for-
} niejszego artylułu. ma blokowa: /* ... */ , jak i komentarze dla
Objective-C w stosunku do czystego C pojedynczej linii: // ....
int Point::Y() const wprowadza również nowe typy i wartości.
{ Pierwszym z nich jest typ BOOL, służący do Programowanie
return m_Y; reprezentacji wartości boolowskich (odpo- obiektowe odkryte na nowo
} wiednikami tego typu w Javie i C++ są od- Jak mogłeś się przekonać czytając poprzedni
powiednio boolean i bool). Co ciekawe, podpunkt, po szybkim zapoznaniu się z róż-
void Point::SetX( int x ) Objective-C stosuje dość rzadko stosowaną nicami w składni Objective-C, okazuje się, iż
{ konwencję, jeśli chodzi o nazwy stałych bo- zmian wcale nie ma tak dużo jak by się mogło
m_X = x; olowskich: zmienne typu BOOL przyjmują wydawać. W rzeczywistości fundamentalna
} wartości YES i NO. Zarówno Java, jak i C++
stosują w tym przypadku słowa kluczowe Listing 6. Język C++: tworzenie obiektu
void Point::SetY( int y ) true i false. klasy Point
{ Kolejnym elementem nowości są słowa
m_Y = y; kluczowe nil, Nil oraz id. nil jest odpowied- #include "Point.hpp"
} nikiem wartości NULL w C\C++ i służy do
oznaczania pustego wskaźnika na zmienną/ #include <iostream>
std::ostream& operator<<( std:: obiekt. Nil jest odpowiednikiem nil, słu-
ostream& os, żącym do oznaczania pustego wskaźnika na using namespace std;
const Point& obiekt klasy (w Objective-C klasa jest pełno-
p ) prawnym obiektem). int main()
{ Specyficznym rozwiązaniem stosowanym {
return os << "x=" << p.X() << "; w Objective-C jest typ SEL. Wartości tego Point p( 5, 10 );
y=" << p.Y(); typu mogą przechowywać tzw. selektory, tj. cout << p << endl;
} identyfikatory metod. SEL jest w pewnym return 0;
sensie odpowiednikiem wskaźników na me- }
tody w języku C++.

142 SDJ Extra 34 Biblia


Wprowadzenie do języka Objective-C

różnica pomiędzy Objective-C a C++ i Javą C++ wraz ze swoim RTTI (ang. Run-Time cej punkt w dyskretnej przestrzeni dwu-
leży w konstrukcjach programowania obiek- Type Information) wypada bardzo blado. Ja- wymiarowej. Na Listingach 4, 5 oraz 6,
towego. Z tego względu spora część niniejsze- va znajduje się mniej więcej pośrodku tej przedstawiona jest prosta implementacja
go artykułu skupi się właśnie na opisywaniu układanki, wbudowany w nią mechanizm takiej klasy, zapisana w języku C++. Na Li-
tych różnic. refleksji (ang. reflection) możliwościami sting 7 pokazana jest implementacja takiej
Chcąc scharakteryzować jednym słowem jest zbliżony do Objective-C, aczkolwiek samej klasy w języku Java.
model obiektowości w Objective-C, można nieco bardziej restrykcyjny (prawdopodob- Zakładam, iż Czytelnicy tego artykułu zna-
by napisać, iż jest on zupełny (ang. strict). nie ze względu na fakt, iż język ten bazuje ją bądź to C++, bądź Javę, dlatego nie będę w
Model taki stanowi niewątpliwie dziedzic- mocno na C++). tym miejscu opisywał szczegółów implemen-
two języka Scheme i stoi w silnej opozycji W kolejnych podpunktach rozważymy na tacji klasy Point w tych językach. Będę się za
do bardziej ograniczonego (czytaj: statycz- przykładach możliwości wszystkich trzech to odnosił do nich przy opisie definicji bliź-
nego) modelu, który został wbudowany w języków w kontekście oferowanych przez nie niaczej klasy w języku Objective-C. Szkielet
język C++. Język Objective-C pozwala za- mechanizmów wsparcia dla programowania takiej implementacji przedstawiony jest na
rządzać zarówno obiektami, jak i klasami w obiektowego. Listingach 8 i 9.
trakcie wykonania programu (klasy są peł- Rozważmy poszczególne fragmenty tych
noprawnymi obiektami). Przy takim mode- Pokaż mi swoją klasę dwóch Listingów. Pierwsza rzecz, która
lu możliwe jest tworzenie nowych klas, do- Naszą podróż poprzez meandry obiekto- rzuca się w oczy, to zdecydowane rozdzie-
dawanie do nich metod czy pobieranie listy wości w Objective-C rozpoczniemy od pro- lenie interfejsu oraz implementacji klasy.
składowych – wszystko to w trakcie wyko- stego, praktycznego przykładu. Rozważmy Podział ten jest podkreślony przez dobór
nania programu! W tym kontekście język implementację prostek klasy reprezentują- nazw słów kluczowych: @interface oraz
@implementation. Podobnie jak w przypad-

Listing 7. Język Java: definicja klasy Point ku C++, definicja (interfejs) klasy oraz jej
implementacja mogą być (i zazwyczaj są)
import java.util.*; umieszczone w odrębnych plikach. Pro-
gramując w Objective-C, interfejsy klasy
public class Point { umieszcza się w plikach nagłówkowych (z
private int x; rozszerzeniem .h; patrz: Listing 8), zaś de-
private int y; finicje metod w pliku implementacji (z roz-
szerzeniem .m; patrz: Listing 9). Podejście
public Point( int x, int y ) { to stoi w opozycji do konwencji javowskiej,
this.x = x; wedle której cała definicja klasy, tj. zarówno
this.y = y; jej interfejs, jak i implementacja, umiesz-
} czone są w jednym pliku źródłowym. War-
to zauważyć, że w języku Objective-C atry-
public int getX() { buty i metody nie mogą być pomieszane.
return x; Atrybuty definiowane są w sekcji interfej-
} su, oznaczonej nawiasami klamrowymi (Li-
sting 8). Co więcej, o ile atrybuty klasy mo-
public int getY() { gą być oznaczone jako publiczne (@public),
return y; chronione (@protected) bądź prywatne
} (@private), o tyle wszystkie metody zade-
klarowane w jej interfejsie są domyślnie pu-
public void setX( int x ) { bliczne. Metody niepubliczne (jeśli takowe
this.x = x; są potrzebne) ukryte są w pliku implemen-
} tacji. Podejście to stanowi istotną różnicę,
przede wszystkim w stosunku do C++, w
public void setY( int y ) { którym publiczne, chronione i prywatne
this.y = y; metody mogą być swobodnie pomieszane
} w definicji klasy. Java, ze swoim mecha-
nizmem interfejsów, znajduje się pośrod-
@Override public String toString() { ku obydwu rozwiązań. Warto zauważyć,
StringBuilder ret = new iż Objective-C posiada również słowo klu-
StringBuilder(); czowe @class. Jednakże w przypadku te-
ret.append( "x=" + x + "; " ); go języka słowo to wykorzystywane jest je-
ret.append( "y=" + y ); dynie do zapisywania poprzedzających de-
return ret.toString(); klaracji klas, co ma służyć do przerywania
} łańcuchów cyklicznych zależności w pli-
kach nagłówkowych (problem bardzo do-
public static void main( String args[] ) { brze znany programistom C++). Na koniec
Point p = new Point( 5, 10 ); warto wspomnieć, iż w przypadku Objecti-
System.out.println(p); ve-C domyślny poziomem dostępu do skła-
} dowych jest @protected oraz że język ten
} nie wspiera statycznych atrybutów klasy,
oznaczanych słowem kluczowym static

www.sdjournal.org 143
Programowanie iPhone OS

w językach Java i C++ (przerażonych Czy- employees) wyglądałoby w Objective-C na- znaczoną na urządzenie mobilne. Aplika-
telników chciałbym szybko uspokoić: Ob- stępująco: cja ta konstruowana jest z widoków. Otóż
jective-C pozwala uzyskać efekt składowej chcielibyśmy, aby nasz widok reagował na
statycznej w nieco odmienny sposób: po- [employees insert:newEmployee: dwa rodzaje zdarzeń: zmiany orientacji
przez definicję globalnej zmiennej w pli- newEmployeeIndex] urządzenia rejestrowane przez akcelero-
ku implementacji oraz dodanie w interfej- metr oraz kontakt stylusa z ekranem do-
sie klasy odpowiednich metod dostępu do Poniższa lista przedstawia podstawowe fak- tykowym. W tym celu, definiowany przez
tej zmiennej). ty związane z programowaniem metod w nas widok musi zarejestrować się jako ob-
Objective-C; po jej przestudiowaniu przed- serwator w serwisach oferowanych przez
Metoda na metodę stawionym powyżej przykład stał się jasny: system. Aby jednak to się zadziało, musi
Jako że pomyślnie udało się nam przebrnąć on dziedziczyć po określonych interfejsach.
przez ogólny opis syntaktyki deklaracji kla- • nazwa metody w Objective-C posiada pre- Scenariusz taki jest dość powszechnie sto-
sy w Objective-C, rozważmy teraz poszcze- fiks: znak minus (–), jeśli jest ona metodą sowany w przypadku programowania urzą-
gólne zagadnienia zawiązane z tym tema- instancji, lub plus (+), jeśli mamy do czy- dzeń mobilnych. Na Listingu 11 przedsta-
tem bardziej szczegółowo. W niniejszym nienia z metodą klasy (tj. odpowiednikiem wiony jest fragment implementacji takiego
podpunkcie przeanalizujemy szczegóły de- metody statycznej w C++ lub w Javie); scenariusza w języku C++.
finicji metod klasy. • nazwy typów występujących w dekla- W tym przypadku AccelerometerObserver
Rzeczą, którą daje się zauważyć na przysło- racji metody (tyczy się to zarówno ty- oraz StylusObserver jako klasy abstrakcyjne
wiowy pierwszy rzut oka, jest fakt, iż składnia pu wartości zwracanej, jak i typów argu- pełnią rolę interfejsów. Warto zauważyć, iż
służąca do definicji metod w Objective-C jest mentów) występują w nawiasach; C++ nie udostępnia dedykowanego mecha-
mocno odmienna od tej, do której przyzwycza- • kolejne argumenty metody oddzielone nizmu służącego do definiowania interfejsów
iły nas języki pokroju C++ bądź Java. są od siebie znakiem dwukropka (:);
Rozważmy hipotetyczną klasę List, repre- • nazwa metody może być identyczna jak Listing 8. Język Objective-C: interfejs klasy
zentującą jednokierunkową listę z dowiąza- nazwa atrybutu klasy; jest to bardzo przy- Point (plik Point.h)
niami w języku C++. Załóżmy sobie, iż kla- datne przy pisaniu akcesorów (ang. getters).
sa ta oferuje nam następującą, publiczną me- @interface Point : NSObject
todę: Dziedziczenie, {
wirtualność i protokoły @private:
void List::Insert(void* object, std:: Moc paradygmatu obiektowego leży w możli- int x;
size_t at); wości tworzenia hierarchii przy pomocy me- int y;
chanizmu dziedziczenia. Objective-C wspie- }
Na podstawie powyższej deklaracji nie- ra oczywiście ten mechanizm, aczkolwiek
trudno się domyśleć, że obiekty typu List – w odróżnieniu od języka C++ - nie wspie- -(int) getX;
miałyby przechowywać elementy dowol- ra dziedziczenia wielokrotnego. W zamian za -(int) getY;
nego typu (void*), zaś zadaniem rozwa- to, podobnie jak język Java, oferuje narzędzia, -(void) setX:(int)x;
żanej metody byłaby alokacja nowego wę- których zadaniem jest zrekompensowanie te- -(void) setY:(int)y;
zła przechowującego zadany obiekt (argu- go braku. Narzędzia te (protokoły oraz kate- @end
ment object) i wstawienie go do listy w gorie klas), oraz sam mechanizm dziedzicze-
określonym miejscu (parametr at). Nagłó- nia w Objective-C, przedstawię pokrótce w Listing 9. Język Objective-C:
implementacja klasy Point (plik Point.m)
wek podobnej metody w języku Java wy- niniejszym podpunkcie.
glądałby tak: Podstawowa składnia dziedziczenia w ję- #import "Point.h"
zyku Objective-C przedstawiona jest na Li-
public void insert(Object object, int stingu 10. @implementation Point
at) { Objective-C, podobnie jak Java, nie oferu- -(int) getX
... je wielorakich wariantów dziedziczenia (tak {
jak na przykład C++, który pozwala dzie- return x;
Wywołanie takiej metody byłoby podobne dziczyć nieprywatne składniki klasy bazo- }
zarówno w Javie, jak i w C++ ,i mogłoby wy- wej w trzech trybach: public, private i
glądać na przykład tak: protected). Dostęp do klasy nadrzędnej od- -(int) getY
bywa się za pośrednictwem znajomego pro- {
employees.insert(newEmployee, gramistom Javy słowa kluczowego super. W return y;
newEmployeeIndex); Objective-C wszystkie metody są domyśl- }
nie wirtualne, w związku z tym język ten
Przekonajmy się, jak podobną metodę moż- nie posiada odpowiednika słowa kluczowe- -(void) setX:(int)x
na by zadeklarować w Objective-C. Gdyby- go virtual, stosowanego w C++. To samo {
śmy zechcieli przetłumaczyć powyższe de- tyczy się mechanizmu dziedziczenia wirtu- return self->x = x;
klaracje w sposób bezpośredni, to w wyniku alnego z C++. }
otrzymalibyśmy, co następuje: Brak wsparcia dla dziedziczenia wielo-
krotnego jest w Objective-C rozwiązany -(void) setY:(int)y
-(void) insert:(id)anObject:(unsigned podobnie jak w Javie: przy pomocy inter- {
int)at fejsów, które w terminologii pierwszego z self->y = y
wymienionych języków zwane są protoko- }
Wywołanie takiej metody (a faktycznie: łami. Rozważmy następujący przykład. Za- @end
wysłanie wiadomości insert do obiektu łóżmy, iż piszemy aplikację użytkową prze-

144 SDJ Extra 34 Biblia


Wprowadzenie do języka Objective-C

(tj. abstrakcyjnych klas nie posiadających żad- Employee* employee = new Employee; sę, zaś init – do obiektu będącego instancją
nych składowych, a jedynie czysto wirtualne tej klasy. Na Listingu 14 przedstawiony jest
metody). Z tego względu programowanie in- można by zapisać w Objective-C tak: przykład klasy Point rozszerzony o metodę
terfejsów w C++ to kwestia przyjęcia pewnej inicjalizującą. Pisząc taką metodę, należy pa-
konwencji. Inaczej jest w Javie. Język ten ofe- Employee* employee = [[Employee alloc] init] miętać o tym, iż:
ruje nam dedykowane słowo kluczowe (in-
terface) służące do definiowania takich kon- Warto zauważyć, iż wiadomość alloc jest • jej nazwa musi się zaczynać od przed-
strukcji. Na Listingu 12 pokazane jest, jak wysyłana do obiektu reprezentującego kla- rostka init (jest to dość interesująca kon-
opisany wyżej scenariusz mógłby być zaim-
plementowany w języku Java. Listing 10. Język Objective-C: podstawowa składnia dziedziczenia
To, co na samym początku rzuca się w
oczy, to większe skondensowanie kodu w @interface Derived : Base
stosunku do C++. Zastosowanie słowa klu- {
czowego interface wiąże się ze specyficz- }
nymi konsekwencjami (np. metody inter- @end
fejsu z założenia są publiczne), co pozwala
uniknąć nadmiarowych słów kluczowych, Listing 11. Język C++: zastosowanie interfejsów
aczkolwiek wiąże się z częściową utratą ela- class AccelerometerObserver
styczności. Listing 13 pokazuje z kolei, jak z {
zagadnieniem obsługi interfejsów radzi so- public:
bie Objective-C. virtual void OnAccelerometerEvent(
Analizując przedstawiony fragment ko- int dx, int dy, int dz ) = 0;
du, można odnieść wrażenie, iż jest on };
znacznie bliższy koncepcji interfejsów w
Javie niż abstrakcyjnych klas bazowych w class TouchScreenObserver
C++. {
Jednakże mechanizm protokołów w Ob- public:
jective-C idzie znacznie dalej; już sama na- virtual void OnStylusEvent(
zwa wskazuje na fundamentalną różnicę: int x, int y ) = 0;
cały czas trzeba pamiętać bowiem o tym, };
iż wywołanie metody w Objective-C jest
w rzeczywistości wysłaniem komunikatu class MyAppView : public AccelerometerObserver,
do obiektu. Co ciekawe, Objective-C ofe- public StylusObserver
ruje specjalną metodę o następującym pro- {
totypie: public:
MyAppView();
-(BOOL) conformsToProtocol: // ...
(Protocol*)protocol virtual void OnAccelerometerEvent(
int dx, int dy, int dz );
która pozwala w czasie wykonania progra- virtual void OnStylusEvent(
mu sprawdzić, czy dany obiekt jest zgodny z int x, int y );
zadanym protokołem. // ...
};
Kilka słów o tworzeniu obiektów
Mieć definicję klasy to jedno. Aby jednak MyAppView::MyAppView()
zmusić nasz program do wykonania kon- {
kretnego zadania, musimy zaludnić go obiek- // ...
tami. W przypadku języka C++ tworzenie accelerometerManager.register(this);
obiektów to dość skomplikowany temat. C++ touchScreenManager.register(this);
pozwala programiście kontrolować ten proces // ...
bardzo dokładnie (chociażby oferując mu ta- }
kie konstrukcje języka jak umieszczający ope-
rator new). Objective-C jest w tym względzie void MyAppView::OnAccelerometerEvent(
znacznie bardziej zbliżony do Javy, w której int dx, int dy, int dz )
pamięć na obiekty przydzielana jest zawsze {
dynamicznie – w trakcie działania programu. // ...
Jest to niejako implikacja przyjętego modelu }
obiektowości, który cechuje się bardzo du-
żym poziomem dynamiki. void MyAppView::OnStylusEvent(
Konstrukcja obiektów w Objective-C jest o int x, int y )
tyle specyficzna, iż składa się z dwóch faz: alo- {
kacji oraz inicjalizacji. // ...
Dla przykładu, następujący fragment kodu }
w języku C++:

www.sdjournal.org 145
Programowanie iPhone OS

wencja w stosunku do Javy i C++, które tody dealloc. Metoda ta jest wywoływa- nie występuje. Na Listingu 15 przedstawio-
narzucają konkretny schemat nazewnic- na automatycznie w momencie dealokacji ny jest fragment kodu Objective-C obsługu-
twa konstruktorów); obiektu. jący wyjątek.
• powinna ona zwracać nowy obiekt; Zarówno programiści języka C++, jak i Ja-
• powinna również wywołać metodę init Wyjątki a obsługa błędów vy nie powinni mieć problemu ze zrozumie-
dla swojej nad-klasy. Obsługa wyjątków w języku Objective-C niem tego fragmentu kodu. W bloku @try
jest znacznie bliższa temu, co oferuje Java wykonywana jest operacja, która może rzu-
Podobnie jak C++, Objective-C pozwala de- niż C++. Dzieje się tak z racji występowa- cić wyjątek (action), kolejne bloki @catch
finiować dla klasy destruktor w postaci me- nia słowa kluczowego finally, które w C++ obsługują różne typy wyjątków – od bardziej
szczegółowych (UserException) do bardziej
Listing 12. Język Java: zastosowanie interfejsów ogólnych (NSException). Podobnie jak Java i
C++, język Objective-C oferuje mechanizm
public interface AccelerometerListener { ponownego rzucania wyjątku (w tym celu
void onAccelerometerEvent(int dx, int dy, int dz); stosuje się w sekcji @catch słowo kluczowe
} @throw bez argumentów). Jeśli zachodzi ta-
ka potrzeba, to po serii sekcji @catch moż-
public interface TouchScreenListener { na umieścić sekcję @finally, której zawar-
void onStylusEvent(int x, int y); tość będzie wykonana zawsze przy wyjściu
} z bloku @try.

public class MyAppView RTTI: poznaj swoje obiekty


implements AccelerometerListener, TouchScreenListener { Na koniec naszej krótkiej podróży po labi-
ryncie możliwości języka Objective-C war-
public MyAppView() { to wspomnieć kilka słów na temat mechani-
AccelerometerManager.register(this); zmu RTTI (ang. Run-Time Type Information).
TouchScreenManager.register(this); W przypadku C++, RTTI sprowadza się do
} następujących elementów:

public void onAccelerometerEvent( • klasa type_info oraz operator typeid


int dx, int dy, int dz) { (dość ograniczone rozwiązanie służące
// ... do porównywania typów);
} • operator dynamic _ cast, służący do
bezpiecznego rzutowania obiektów w
public void onStylusEvent(int x, int y) { dół hierarchii klas.
// ...
} Java, w odróżnieniu od C++ ,oferuje znacz-
} nie bogatszy wachlarz możliwości w ramach
mechanizmu refleksji, pozwalającego two-
Listing 13. Język Objective-C: protokoły rzyć obiekty klasy na podstawie jej nazwy
@protocol AccelerometerListener oraz zdobywać informacje o klasach w trak-
-(void) onAccelerometerEvent:(int)dx:(int)dy:(int)dz cie wykonania programu.
@end Objective-C oferuje szeroki wachlarz me-
chanizmów RTTI, na które składają się:
@protocol TouchScreenListener
-(void) onStylusEvent:(int)x:(int)y • metoda isMemberOfClass: określa, czy
@end dany obiekt jest instancją określonej kla-
sy;
@interface MyAppView : NSObject <AccelerometerListener, • metoda isKindOfClass: określa, czy da-
TouchScreenListener> ny obiekt jest instancją zadanej klasy
{ bądź jednej z podklas tej klasy;
// ... • conformsToProtocol: określa, czy dany
} obiekt implementuje wszystkie metody
zadanego protokołu;
@implementation MyAppView • respondsToSelector, instancesRespon
-(void) onAccelerometerEvent:(int)dx:(int)dy:(int)dz dToSelector: określają, czy dany obiekt
{ implementuje zadaną metodę.
//...
} Teoria teorią, a praktyka?
-(void) onStylusEvent:(int)x:(int)y Mój znajomy programista, który Objective-
{ C używa od kilku lat (głównie do programo-
//... wania natywnych aplikacji pod MacOS), za-
} pytany o to, co sądzi o wspomnianym języ-
@end ku, odpowiedział krótko: w tym, do czego go
używam, sprawdza się świetnie. Po kilkuna-

146 SDJ Extra 34 Biblia


Wprowadzenie do języka Objective-C

stu minutach rozmowy okazało się, że wspo- przy tym, iż mając do dyspozycji Objecti- misty, który przez kilka lat tworzył aplika-
mniany programista wykorzystywał Objec- ve-C o wiele łatwiej przetłumaczyć mu kon- cje pod Symbian OS, zaś niedawno zaczął
tive-C do programowania interfejsów użyt- cepcje powstałe w głowie na język zrozumia- pisać programy pod iPhone'a. Jego komen-
kownika pod system spod znaku Jabłusz- ły dla maszyny w porównaniu do języków tarze na temat Objective-C były zadziwia-
ka. Jako główne zalety języka wymienił je- Java czy C++. Co ciekawe, podobne opinie jąco podobne do przedstawionych powyżej.
go elastyczność i dynamiczność. Stwierdził usłyszałem od innego znajomego: progra- Wniosek nasuwa się sam: Objective-C, a w
zasadzie jego elastyczny model obiektowo-
ści, sprawiają, iż język ten cechuje się bardzo
Listing 14. Język Objective-C: definicja klasy Point z metodą inicjalizującą
dużą siłą wyrazu, szczególnie przy tworze-
@interface Point : NSObject niu aplikacji użytkowych (np. programowa-
{ nie interfejsów użytkownika czy obsługa ko-
int x; munikacji sieciowej). C++ wygrywa niewąt-
int y; pliwie w kategorii aplikacji niskopoziomo-
} wych, narzędzi czy złożonych programów
wymagających optymalizacji w celu uzyska-
-(id) initWithX:(int)anX andY:(int)anY; nia ultra-wysokiej wydajności. Wychodzi na
@end to, że najbardziej poszkodowana z tego poje-
dynku wychodzi Java: w tym przypadku zło-
@implementation Point ty środek wcale nie jest taki złoty. Java nad-
-(id) initWithX:(int)anX andY:(int)anY rabia jednak zaległości olbrzymią bazą kodu,
{ dostępem do niezliczonej liczby świetnej ja-
if (![super init]) kości narzędzi oraz świetną maszyną wirtu-
return nil; alną, którą można oprogramować również w
x = anX; bardziej elastycznych językach. W tej sytu-
y = anY; acji oczywiste jest, iż warto zapoznać się z
return self; Objective-C. Chociażby w celu rozszerzenia
} horyzontów własnej wiedzy.
@end
// ... Podsumowanie
Point* p1 = [[Point alloc] initWithX:5 andY:10]; W powyższym artykule przedstawiłem naj-
ważniejsze konstrukcje języka Objective-C,
Listing 15. Język Objective-C: obsługa wyjątku porównując je przy tym do rozwiązań dostęp-
@try nych w językach Java oraz C++. Jeśli uważnie
{ przestudiowałeś niniejszy tekst, to nie powi-
action(); nieneś mieć kłopotów z rozumieniem progra-
} mów pisanych w tym języku. Ze względu na
@catch (UserException* e) ograniczenie objętości artykułu wiele istot-
{ nych zagadnień (np. zarządzenia pamięcią)
handleUserException(); zostało pominiętych. Jeśli jesteś zaintereso-
} wany pogłębieniem swojej wiedzy z zakresu
@catch (NSException* e) Objective-C, to zapraszam do lektury ramki
{ zatytułowanej Nauka Objective-C. Ze swojej
handleNSException(); strony żywię nadzieję, iż zaprezentowany ma-
@throw teriał zachęci Cię do zapoznania się z tym cie-
} kawym językiem, a być może również będzie
@finally impulsem do rozpoczęcia eksperymentów z
{ programowaniem aplikacji pod iPhone.
cleanup();
}
RAFAŁ KOCISZ
Pracuje na stanowisku Dyrektora Techniczne-
go w firmie Gamelion, wchodzącej w skład Gru-
Nauka Objective-C py BLStream. Rafał specjalizuje się w technolo-
Oczywistym jest, iż w krótkim artykule nie da się przekazać pełnej wiedzy na temat tak zło- giach związanych z produkcją oprogramowa-
żonego języka, jak Objective-C. Jeśli zaprezentowany temat wzbudził Twoje zainteresowa- nia na platformy mobilne, ze szczególnym na-
nie, to chciałbym Ci zaproponować konkretne materiały, które pomogą Ci pogłębić wiedzę z
ciskiem na tworzenie gier. Grupa BLStream po-
zakresu Objective-C. Jeśli posiadasz już doświadczenia w programowaniu w innych językach
(głównie C++ lub Java), to polecam świetny dokument autorstwa Pierre Chatelier'a, zatytuło- wstała, by efektywniej wykorzystywać potencjał
wany From C++ to Objective-C. Angielskojęzyczna, elektroniczna i całkowicie darmowa wer- dwóch szybko rozwijających się producentów
sja tego dokumentu znajduje się pod adresem: http://ktd.club.fr/programmation/fichiers/cpp- oprogramowania – BLStream i Gamelion. Firmy
objc-en.pdf. Notabene, materiał tam zaprezentowany stał się dla mnie impulsem do napisa- wchodzące w skład grupy specjalizują się w wy-
nia niniejszego artykułu. Gdybyś z kolei zechciał uczyć się Objective-C od podstaw w sposób twarzaniu oprogramowania dla klientów korpo-
bardziej systematyczny, to polecam książkę Programming in Objective-C 2.0 autorstwa Ste-
racyjnych, w rozwiązaniach mobilnych oraz pro-
phen'a G. Kochan'a. Oprócz tego, w Internecie znajdziesz moc dokumentacji oraz tutoriali na
temat programowania w tym języku. dukcji i testowaniu gier.
Kontakt z autorem: rafal.kocisz@game-lion.com

www.sdjournal.org 147
Programowanie JavaME

JME na przykładzie
Przelicznik walut na podstawie kursów NBP

Jeszcze kilka lat temu obsługa technologii Java była informacją, która
miała zachęcić do zakupu telefonu komórkowego. Przez tych kilka lat
nastąpił znaczny rozwój„komórek” i choć obecnie większość z nich posiada
własny system operacyjny i nie służą już tylko do wykonywania rozmów
i przesyłania wiadomości, to technologia JME jest nadal popularna.
wcześniej zasoby, w pamięci stałej powinny
Dowiesz się: Powinieneś wiedzieć: zostać zapisane informacje, które mają być
• Co to jest J2ME; • Podstawy języka Java; dostępne przy ponownym uruchomieniu.
• Jak tworzyć i uruchamiać własne programy • Podstawy języka HTML.
na komórki. Co chcemy osiągnąć
i czego do tego potrzebujemy
Naszym celem jest utworzenie własnego progra-
czenie. Na jego bazie tworzone są aplikacje zwa- mu na komórkę – MIDletu służącego do wymia-
ne MIDletami, wśród których wyróżnić może- ny walut. Najważniejszym wymaganiem dla na-
Poziom trudności my gry lub inne aplikacje wykorzystywane przy- szej aplikacji jest pobieranie aktualnych kursów
kładowo w biznesie. Gotowy MIDlet może być walut ze strony Narodowego Banku Polskiego
uruchamiany na wszystkich urządzeniach, któ- (NBP). Zakładamy, że nasz MIDlet będzie umoż-
re mają zaimplementowaną maszynę wirtualną liwiał dokonywanie następujących konwersji:

W
chwili obecnej technologia Java jest dla konfiguracji CLDC i profilu MIDP.
dzielona przez firmę Sun na trzy Bardzo istotnym zagadnieniem jest cykl ży- • PLN (polski złoty) → EUR (euro);
wersje: Standard (SE), Enterprise cia MIDletu, który definiuje trzy stany, w któ- • EUR → PLN;
(EE) i Micro (ME) Edition. Każda z tych wersji rych MIDlet może się znaleźć (Rysunek 1): • PLN → USD (dolar amerykański);
jest przeznaczona dla urządzeń o różnych para- • USD → PLN.
metrach sprzętowych. Pierwsza (SE) dystrybu- • zatrzymany (paused) – MIDlet jest zaini-
cja Javy jest przeznaczona dla komputerów oso- cjowany i oczekuje; Oczywiście nasza aplikacja musi obsługiwać
bistych, druga (EE) przeznaczona jest dla du- • aktywny (active) – MIDlet funkcjonuje sytuacje wyjątkowe i błędne (np. wpisywanie
żych, wydajnych serwerów, trzecia zaś (ME), normalnie. Wejście w stan następuje po liter zamiast cyfr w kwocie do przeliczenia).
najmniejsza, została zaprojektowana z myślą wywołaniu metody startApp(); Na stronie NBP w sekcji Informacja o termi-
o urządzeniach o bardzo ograniczonych zaso- • zakończony (destroyed) – MIDlet zwalnia nach publikacji kursów walut NBP znajdują się
bach, takich jak telefony komórkowe lub palm- wszystkie swoje zasoby i zostaje zakoń- informacje o zawartości poszczególnych do-
topy. Ze względu na ograniczenia techniczne ta- czony. Wejście w stan następuje: kiedy stępnych tabel kursów. Dla ułatwienia przyj-
kich urządzeń, tj. wolniejsze procesory, mniejszą metoda destroyApp() zwróci wyjątek miemy, że interesują nas średnie kursy walut
pamięć, Java ME posiada swój własny, okrojony błędnego argumentu i zrzucony zosta- zapisanych w tabeli A dostępnej pod adresem
w stosunku do większych dystrybucji zbiór klas nie wyjątek MIDletStateChangeExcept http://nbp.pl/kursy/kursya.html, która jest aktu-
zwanych konfiguracją (ang. configuration). Spe- ion, kiedy metoda notifyDestroyed() alizowana w każdy dzień roboczy w godzinach
cyfikacja JME określa obecnie dwie konfigura- wykona się pomyślnie. MIDlet musi naj- 11:45–12:15.
cje: CDC (Connected Device Configuration) oraz pierw wykonać metodę destroyApp() , W związku z tym pojawia się dodatkowe wy-
CLDC (Connected Limited Device Configura- a dopiero po niej notifyDestroyed(). maganie polegające na informowaniu użytkow-
tion). Konfiguracja jest z kolei podstawą do two- nika dokonującego przeliczenia o dacie, z której
rzenia profili (Profile). Profile, bazując na specy- Każdy MIDlet powinien posiadać implemen- pochodzą kursy walut. Aby przystąpić do pracy,
fikacji konfiguracji, rozszerzają jej możliwości. tację trzech poniższych metod: musimy mieć zainstalowane na naszym kom-
Jednym z profili dostępnych dla konfiguracji puterze następujące oprogramowanie:
CLCD jest MIDP (Mobile Information Device Pro- • startApp() – wywoływana po inicjali-
file), który udostępnia funkcje sieciowe, kompo- zacji obiektów; • JAVA Software Development Kit (SDK) –
nenty interfejsu użytkownika i lokalną pamięć • pauseApp() – wstrzymuje działanie jest to środowisko przeznaczone dla każ-
stałą. Pozwala stworzyć prosty interfejs użyt- aplikacji, przygotowuje MIDlet do stanu dego, kto chciałby rozpocząć programowa-
kownika i podstawowe funkcje sieciowe, korzy- zatrzymania i zwalnia zasoby; nie w języku Java. Zawiera w sobie pakiet
stając z HTTP 1.1. Kombinacja CLDC wraz z • destroyApp() – przygotowuje MIDlet do Java Runtime Environment, a także dodat-
profilem MIDP to najczęściej spotykane połą- zamknięcia. Zwolnione są zablokowane kowe narzędzia takie jak kompilator i de-

148 SDJ Extra 34 Biblia


JME na przykładzie

bugger. Darmowe środowisko pobierze- • Lib – dodatkowe biblioteki; w J2SE pakietów graficznych, tj. Swing czy
my z tej strony: http://java.sun.com/javase/ • Res – zasoby, np. pliki graficzne, dźwię- AWT. Mamy zaś dostęp do klas:
(z sekcji Download wybieramy update 6). kowe;
• JME Wireless Toolkit – zbiór narzędzi, któ- • Src – kody źródłowe MIDletu (*.java). • java.io – obsługa strumieni wejścia/wyjścia;
re dostarcza programistom środowisko • java.lang – podstawowe klasy typu
emulujące, dokumentacje i przykłady po- Finalna postać MIDletu składa się z co naj- String (tak jak w J2SE);
zwalające na budowanie aplikacji w tech- mniej dwóch plików, tj. z pliku samego • java.util – klasy użytkowe dla specjal-
nologii JME. Darmowy pakiet pobierze- programu (PrzelicznikWalut.jar) i pliku re- nych struktur danych (tak jak w J2SE).
my ze strony: http://java.sun.com/javame/ pozytorium (PrzelicznikWalut.jad), któ-
index.jsp (z sekcji Download wybieramy ry zawiera informacje dot. wersji MIDle- Ponadto w JME dostępne są dodatkowe pa-
Sun Java Wireless Toolkit 2.5.2 for CLDC). tu, autora, lokalizacji programu, jego zaso- kiety:
• Dowolny edytor tekstowy, w którym bę- by i nazwę.
dziemy pisać kod naszej aplikacji. Dla uła- Kolejnym krokiem, jaki musimy zrobić, • javax.microedition.midlet – zawiera jed-
twienia dobrze byłoby, gdyby edytor pod- jest utworzenie w katalogu Src pliku o nazwie ną klasę o nazwie MIDlet, która definiu-
świetlał składnię języka Java. Przykłado- PrzelicznikWalut.java, w którym zapiszemy je interakcje między aplikacjami a śro-
wym edytorem może być JCreator LE kod źródłowy naszego programu. dowiskiem uruchomieniowym, klasa ta
(do pobrania ze strony producenta: http:/ musi rozszerzać klasę główną (extends
/www.jcreator.com/) Budowa interfejsu MIDlet) w każdym programie, co ozna-
W pierwszym kroku musimy zaimportować cza przejęcie jej interfejsu;
Wireless Toolkit umożliwia użytkownikom odpowiednie biblioteki. Bardzo ważne jest, • javax.microedition.lcdui – zawiera klasy
tworzenie aplikacji z wykorzystaniem czte- że w JME nie mamy dostępu do istniejących UI API (User Interface API), wykorzysty-
rech podstawowych szablonów telefonów
komórkowych:
���������
• Default Color Phone – domyślny telefon ���
z kolorowym wyświetlaczem;
• Default Gray Phone – z czarno-białym;
������������
• Media Control Skin – zawiera dodatko- ������
we opcje i możliwości związane z odtwa-
rzaniem multimediów;
• QWERTY Device – telefon z klawiaturą ����������
w układzie QWERTY.
���������� ���������
Po zainstalowaniu Wireless Toolkit w menu Start
pojawia się nowa grupa: Sun Java (TM) Wireless ������
Toolkit 2.5.2_01 for CLDC, w której znajdują ������������
się odwołania do zainstalowanego oprogramo-
wania i dokumentacji. Nas najbardziej intere- Rysunek 1. Cykl życia midletu
suje Wireless Toolkit 2.5.2 – właściwy program,
w którym będziemy tworzyć i uruchamiać na-
szą aplikację. Szczególnie interesujące mogą być Listing 1. Szkielet klasy PrzelicznikWalut
dla czytelników domyślnie dostępne przykłado- import java.io.*;
we MIDlety, na przykładzie których można za- import javax.microedition.midlet.*;
obserwować możliwość tej technologii, a tak- import javax.microedition.lcdui.*;
że znajdować ciekawe rozwiązania i zdobywać import javax.microedition.io.*;
wiedzę z tego obszaru. public class PrzelicznikWalut extends MIDlet implements CommandListener{
public PrzelicznikWalut()
Nowy Midlet {
W głównym oknie Wireless Toolkit klikamy // konstruktor
przycisk New project, a następnie wpisujemy na- }
zwę tworzonego MIDletu i nazwę głównej je- public void startApp()
go klasy, np. PrzelicznikWalut. W oknie Set- {
tings for project, z menu Target platform, wybie- // kod wykonywany przy starcie oraz przy wznowieniu
ramy JTWI, a następnie wybieramy konfigura- }
cję CLDC 1.1. Pozostałe opcje zostawiamy bez public void pauseApp()
zmian i klikamy OK. W tym momencie w loka- {
lizacji C:\Documents and Settings\user_name\ // kod wykonywany przy wstrzymaniu
j2mewtk\2.5.2\apps (gdzie user_name to nazwa }
naszego użytkownika systemowego) utworzo- public void destroyApp (boolean unconditional)
ny został katalog z taką samą nazwą jak nasz MI- {
Dlet zwierający następujące podkatalogi: // kod wykonywany przy zamykaniu
}
• Bin – spakowane pliki binarne (*.jar), pli- }
ki repozytorium (*.mdf, *.jad);

www.sdjournal.org 149
Programowanie JavaME

wane do budowania interfejsu użytkow- • javax.microedition.rms – dostarcza aplika- tem (RMS) (nie będziemy wykorzystywać
nika tworzonych MIDletów; cjom możliwość zapisywania informacji tego pakietu w naszej aplikacji).
• javax.microedition.io – zawiera klasy przez użytkownika, ich przechowywania
wspomagające sieć oparte na konfigura- i odtwarzania. Ten prosty system bazoda- Po zaimportowaniu odpowiednich pakietów
cji z ograniczeniami (CLDC); nowy nazywamy Record Managment Sys- przyszedł czas na deklarację klasy i obowiąz-
kowych metod. Pamiętajmy, że nazwa głów-
Listing 2. Definicja obiektów, zmiennych nej klasy musi być taka sama jak nazwa pli-
ku z kodem źródłowym programu. W tym
/**Ekran komorki**/ momencie kod naszego programu jest taki
private Display mDisplay; jak na Listingu 1. Zauważmy, że w defini-
/**Formularz do wprowadzania danych**/ cji klasy PrzelicznikWalut dodaliśmy obo-
private Form wartosc_form; wiązkowy parametr extends MIDlet, a tak-
/**Formularz do wyświetlania wyników**/ że informację o własnej implementacji inter-
private Form wynik_form; fejsu CommandListener, co pozwoli nam na
/**Formularz oczekiwania na wynik**/ obsługę dodawanych elementów, np. reak-
private Form waitForm = new Form("Waiting..."); cję na przyciśnięcie przycisków funkcyjnych
w programie.
/**Przewijana nazwa programu**/ Kolejnym krokiem jest zdefiniowanie wy-
private static final String TICKER_TEXT = "Przelicznik walut - igorkruk.pl"; glądu naszego programu. Definiujemy nastę-
private Ticker t = new Ticker(TICKER_TEXT); pujące elementy i zmienne:
/**Pole tekstowe do wpisywania przeliczanej wartosci**/
private final TextField wartosc = new TextField("Wpisz kwotę: ", "", • zmienna typu Display – umożliwi odwo-
10,TextField.DECIMAL); łanie do ekranu telefonu komórkowego, co
/**Lista mozliwych przelicznikow**/ da nam możliwość wyświetlania na nim
private final ChoiceGroup cg = new ChoiceGroup("", ChoiceGroup.POPUP, przygotowanych przez nas formularzy;
new String[] {"EUR->PLN", "PLN->EUR", "USD->PLN", "PLN->USD"}, null); • trzy zmienne typu Form – formularze wy-
/**Polecenie wyjscia z aplikacji**/ świetlane na ekranie telefonu; pierwszy
private final static Command CMD_EXIT = new Command("Exit", Command.EXIT, 1); będzie zawierał elementy do wprowadza-
/**Polecenie przejscia do wczesniejszego formularza**/ nia kwoty do przeliczenia oraz typu prze-
private final static Command CMD_BACK = new Command("Back", Command.BACK, 1); liczenia, drugi formularz będzie służył do
/**Polecenie do pobrania aktualnego kursu walut**/ wyświetlenia wyniku przeliczenia, trzeci
private final static Command CMD_CONNECT = new Command("Connect", Command.SCREEN, zaś do wyświetlenia komunikatu oczeki-
1); wania na przeliczenie kwoty;
/**Jaka zamiana walut**/ • zmienna typu Ticker – umożliwi wy-
int pozycja = 0; świetlenie na ekranie przewijającego się
/**Warotsc, ktora przeliczamy**/ napisu z nazwą programu (zmienna typu
private double ile_przeliczamy; String);
/**Kod waluty, ktora przeliczamy: EUR, USD**/ • pole tekstowe TextField – służące do
private String wybrana_waluta = "";//EUR czy USD wpisywania przeliczanej kwoty. Zwróć-
/**Adres aktualnego kursu **/ my uwagę, że polu temu przypisujemy
private String url = "http://nbp.pl/kursy/kursya.html"; właściwość TextField.DECIMAL, dzięki
czemu zagwarantujemy sobie możliwość
Listing 3. Konstruktor i metoda startApp wpisania tylko liczb i znaku kropki;
public PrzelicznikWalut() • zmienna typu ChoiceGrup – stanowić
{ będzie listę dostępnych typów konwersji
// konstruktor walutowych;
wartosc_form = new Form("Przelicznik Walut");
wartosc_form.setTicker(t);
//przygotowujemy forme do wpisywania informacji - pole tekstowe i menu wyboru
wartosc_form.append(wartosc);
wartosc_form.append(cg);
//przyciski exit i connect
wartosc_form.addCommand(CMD_EXIT);
wartosc_form.addCommand(CMD_CONNECT);
//nasluch
wartosc_form.setCommandListener(this);
}
public void startApp()
{
// kod wykonywany przy starcie oraz przy wznowieniu
mDisplay = Display.getDisplay(this);
mDisplay.setCurrent(wartosc_form);
}
Rysunek 2. Uruchomienie i wpisanie wartości

150 SDJ Extra 34 Biblia


JME na przykładzie

• trzy zmienne typu Command – będą od- my na nim formularz wartosc_form (mDi- cję konstruktora oraz metody startApp() za-
powiadały za przyciski funkcyjne na splay.setCurrent(wartosc_form);) Defini- wiera Listing 3.
ekranie komórki; odpowiednio: wyjście
z programu (CMD _ EXIT), przejście do Listing 4. Implementacja interfejsu CommandListener
wcześniejszego formularza (CMD _ BACK),
połączenie ze stroną NBP i przeliczenie public void commandAction(Command c, Displayable d) {
wpisanej kwoty (CMD _ CONNECT); //jesli jestesmy na formie wpisywania informacji i naciskamy connect
• dodatkowe zmienne zawierające m.in. ad- if ((d.equals(wartosc_form)) && (c==CMD_CONNECT))
res tabeli z kursami walut na stronie NBP, {
przechowującej wpisaną wartość do prze- String ile = wartosc.getString();
liczenia. try
{
Definicja wszystkich opisanych wyżej //zamieniamy string na double
zmiennych zawarta została na Listingu 2. ile_przeliczamy = java.lang.Double.parseDouble(ile);
W kolejnym kroku musimy zdefiniować, }
które elementy będą znajdować się na formu- catch (NumberFormatException e) {}
larzach, a następnie podać formularz starto- //ustawiamy nowa forme na czas laczenia i pobierania kursu
wy dla naszego programu. W tym celu w kon-
struktorze naszej klasy PrzelicznikWalut de- mDisplay.setCurrent(waitForm);
finiujemy wartość zmiennej wartosc_form, try
do której przypisujemy także obiekty typu {
Ticker (przewijany tekst). Następnie przy //pobieramy jaki ma byc przelicznik
pomocy metody append umieszczamy na for- switch (cg.getSelectedIndex())
mularzu wartosc_form obiekt do wpisywa- {
nia kwoty (wartosc, TextField) oraz rozwi- case 0: pozycja=1; break;
janą listę z dostępnymi przelicznikami (cg, case 1: pozycja =2; break;
ChoiceGroup). Na tym formularzu powinny case 2: pozycja=3; break;
być dostępne dwa przyciski funkcyjne: pierw- case 3: pozycja =4; break;
szy powinien służyć do wyjścia z programu, default: pozycja =1;
drugi do uruchamiania procedury przelicza- }
nia walut. W związku z tym przy pomocy //sprawdzamy cz interesuje nas kurs EUR czy USD
metody addCommand dodajemy dwa zadekla- if ((pozycja==1) || (pozycja==2))
rowane wcześniej przyciski: CMD_EXIT, CMD_ {
CONNECT. Aby program obsługiwał kliknięcie wybrana_waluta = "EUR";
w te przyciski, musimy przypisać do formula- }
rza nasłuch, który zdefiniujemy w dalszej czę- else
ści artykułu. Wykonujemy to poprzez metodę {
setCommandListener(this). wybrana_waluta = "USD";
Kiedy mamy już przygotowany odpowied- }
ni formularz, możemy wyświetlić go na ekra- }
nie telefonu komórkowego. W tym celu w catch (NumberFormatException e) {return;}
metodzie startApp() definiujemy obiekt //pobieranie kursu zestrony NBP.pl w oddzielnym watku
mDisplay, który będzie stanowić odwołanie Thread t = new Thread()
do głównego ekranu komórki (Display.get- {
Display(this);), a następnie wyświetla- public void run()
{
connect(url);
}
};
t.start();
}
//jesli jestesmy na formie wyniku i nacisnelismy BACK - cofamy sie do formy
wprowadzania informacji
if ((d.equals(wynik_form) && (c == CMD_BACK)))
{
mDisplay.setCurrent(wartosc_form);
}
if (c == CMD_EXIT)
{
destroyApp(false);
notifyDestroyed();
}
Rysunek 3. Łączenie z Internetem i wynik }
przeliczenia

www.sdjournal.org 151
Programowanie JavaME

Implementacja wybrania typu przeliczenia i nastąpi kliknię- wania na przeliczenie (w tym czasie na-
nasłuchu CommandListener cie przycisku CONNECT, wówczas: stąpi połączenie ze stroną NBP i przeli-
Kolejną rzeczą, jaką musimy zrobić, jest za- czenie wartości);
implementowanie interfejsu nasłuchu • program sczytuje wpisaną wartość i par- • sprawdza, jaka waluta obca (euro lub do-
CommandListener. Jeśli jesteśmy aktualnie na suje ją do zmiennej typu Double; lar amerykański) została wybrana;
formularzu służącym do wpisywania kwoty i • wyświetla na ekranie formularz oczeki- • tworzy nowy wątek (obiekt typu
Thread) i w definicji jego metody run
Listing 5. Definicja metody connect wywołuje funkcję connect() z adresem
private void connect(String url) zawierającym tabelę z kursami walut;
{ • uruchamia zadeklarowany wątek.
HttpConnection hc = null;
InputStream in = null; Jeśli jesteśmy na formularzu z wynikiem prze-
try { liczenia i klikniemy przycisk BACK, wówczas
HttpConnection c = null; program powinien przenieść nasz formularz
InputStream is = null; służący do wpisywania danych. Z kolei jeśli
StringBuffer b = new StringBuffer(); klikniemy przycisk EXIT, to program jest za-
try { mykany. Pełna definicja implementacji nasłu-
c = (HttpConnection)Connector.open(url); chu przedstawiona została na Listingu 4.
is = c.openDataInputStream();
int ch; Połączenie do strony NBP
while ((ch = is.read()) != -1) Za pobranie kursów walut ze strony NBP odpo-
{ wiadać będzie funkcja connect(), która przyj-
b.append((char) ch); muje jeden parametr – adres url. Sposób działa-
} nia tej funkcji jest niezwykle prosty. Przy pomo-
} cy obiektu HTTPConnection łączy się ze wska-
finally { zanym adresem, do którego podpina strumień
if(is!= null) { wejściowy, z którego będziemy czytać dane. Na-
is.close(); stępnie w pętli sczytywane są pojedyncze war-
} tości (int), które są konwertowane na typ char
if(c != null) { – znakowy, i dodawane do bufora. Na koniec
c.close(); bufor ten jest przekształcany do zmiennej typu
} String. Można uznać, że w ten sposób wczyta-
} liśmy stronę HTML zawierającą tabelę z kursa-
String wynik_s = b.toString(); mi walut znak po znaku. Na koniec wywoły-
pobierz_kurs (wynik_s,wybrana_waluta); wana jest funkcja pobierz_kurs(), do której
} przekazujemy pobraną stronę HTML i wybraną
catch (IOException ioe) { wcześniej walutę obcą. Treść funkcji connect()
} zawarty został na Listingu 5.

Listing 6. Definicja metody pobierz_kurs Pobranie kursu waluty


private void pobierz_kurs(String strona,String waluta) Aby zrozumieć zasadę działania funkcji
{ pobierz_kurs(), najlepiej jest zacząć od
String data = "";//data kursu otwarcia strony z tabelą kursów w przeglą-
String kurs = ""; darce internetowej. Jeśli zapoznamy się te-
int pozycja_daty = strona.indexOf("z dnia <b>"); raz ze źródłem tej strony, to zauważymy, że
data = strona.substring(pozycja_daty+10,pozycja_daty+20); jej zasadniczą i najbardziej nas interesują-
//pobieramy kurs wybranej waluty cą częścią jest tabela HTML, w której każdy
if (waluta.equals("USD")) wiersz odpowiada oddzielnej walucie obcej.
{ Nas w szczególności będą interesować druga
// wybralismy USD i trzecia kolumna: nazwa waluty i jej aktual-
int pozycja_waluty = strona.indexOf("USD"); ny kurs. Warto zwrócić jeszcze uwagę, że nad
kurs = strona.substring(pozycja_waluty+26,pozycja_waluty+32); tabelą znajduje się informacja, z którego dnia
} pochodzą poniższe kursy walut. Nasza pra-
else ca w zakresie funkcji pobierz_kurs() po-
{ legać będzie zatem na odpowiednim wycię-
//wybralismy EUR ciu dwóch wartości: daty i interesującego nas
int pozycja_waluty = strona.indexOf("EUR"); kursu (w zależności, czy użytkownik wybrał
kurs = strona.substring(pozycja_waluty+26,pozycja_waluty+32); EUR lub USD , ta waluta będzie przez nas szu-
} kana w tabeli). Na koniec wywołujemy funk-
//zamieniamy , na . cję przelicz(), która na podstawie przekaza-
String kurs_2 = kurs.substring(0,1) + "." + kurs.substring(2,kurs.length()); nych wartości: daty i kursu, przeliczy dla nas
przelicz(kurs_2,data); kwotę i wyświetli odpowiednie informacje na
} formularzu wynik_form. Listing 6 zawiera
treść funkcji pobierz_kurs().

152 SDJ Extra 34 Biblia


JME na przykładzie

Przeliczenie użytkownik. W zależności od jego wyboru do- użytkownika. Do formularza wynik_form do-
kwoty i wyświetlenie wyników konywana jest odpowiednia operacja matema- dawany jest element StringItem, który umożli-
W pierwszej kolejności w funkcji przelicz() tyczna – przeliczająca waluty oraz budująca od- wia wyświetlenie dowolnego ciągu znakowego.
sprawdzamy, który typ przeliczenia wybrał powiedni komunikat końcowy wyświetlany dla W naszym przypadku jest to informacja o dacie,
z której pochodzą wykorzystane w przeliczeniu
Listing 7. Definicja metody przelicz kursy oraz wynik przeliczenia. Dodatkowo do
formularza dodawane są przyciski CMD_BACK i
private void przelicz(String kurs,String data) CMD_EXIT. Na koniec formularz ten wyświetla-
{ ny jest na ekranie telefonu komórkowego. Li-
double wynik = 0; sting 7 zawiera treść funkcji przelicz().
String tekst = "\n";
double kurs_d = Double.parseDouble(kurs); Uruchomienie aplikacji
Przyszedł czas na uruchomienie aplikacji. W
if (pozycja==1) tym celu zapisujemy treść naszej klasy i wraca-
{ my do Wireless Toolkit. Z menu wybieramy Bu-
//EUR-PLN ild, aby dowiedzieć się, czy nasza klasa zostanie
wynik = ile_przeliczamy*kurs_d; poprawnie skompilowana. Jeśli nie popełniliśmy
String pom = ""+wynik; żadnego błędu, to powinniśmy otrzymać komu-
pom = pom.substring(0,pom.indexOf(".")+3); nikat Build complete. Teraz możemy urucho-
tekst += ""+ile_przeliczamy+" EUR = "+pom+" PLN"; mić aplikację, wybierając z menu Run. Na Ry-
} sunkach 2 i 3 przedstawiłem zrzuty ekranowe
if (pozycja==2) z naszego MIDletu. Warto zaznaczyć, że w mo-
{ mencie kliknięcia przycisku CONNECT aplika-
//PLN-EUR cja zapyta się nas, czy wyrażamy zgodę na połą-
wynik = ile_przeliczamy/kurs_d; czenie naszej aplikacji z Internetem (stroną NBP
//dodatkowa zmienna aby obciac wynik tylko do 2 miejsc po przecinku z kursami walut). Musimy kliknąć przycisk Yes.
String pom = ""+wynik; Oczywiście należy także upewnić się, czy ewen-
pom = pom.substring(0,pom.indexOf(".")+3); tualnie zainstalowane na naszym komputerze
tekst += ""+ile_przeliczamy+" PLN = "+pom+" EUR"; oprogramowanie Firewall zezwala na połączenie
} aplikacji Wireless Toolkit z Internetem.
if (pozycja==3)
{ Podsumowanie
//USD-PLN W artykule tym przedstawiłem architektu-
wynik = ile_przeliczamy*kurs_d; rę technologii JME i zasady tworzenia MIDle-
String pom = ""+wynik; tów. Dzięki prostej, lecz w pełni funkcjonal-
pom = pom.substring(0,pom.indexOf(".")+3); nej aplikacji czytelnik miał możliwość zapo-
tekst += ""+ile_przeliczamy+" USD = "+pom+" PLN"; znania się z możliwościami, jakie daje tech-
} nologia JME. Oczywiście, część z przedsta-
if (pozycja==4) wionych rozwiązań jest stworzona tylko na
{ potrzeby artykułu i ze względu na ogranicze-
//PLN-USD nia w jego objętości – odpowiednio uprosz-
wynik = ile_przeliczamy/kurs_d; czona i skrócona. Najważniejszą zmianą, do
//dodatkowa zmienna aby obciac wynik tylko do 2 miejsc po przecinku której zachęcam zainteresowanych czytelni-
String pom = ""+wynik; ków, jest napisanie oddzielnego WebService,
pom = pom.substring(0,pom.indexOf(".")+3); który odpowiedzialny byłby za ściąganie kur-
tekst += ""+ile_przeliczamy+" PLN = "+pom+" USD"; sów walut ze strony NBP w formacie XML,
} a nie, jak zaproponowałem, w HTML. Dzięki
//przygotowujemy forme dla wyniku temu moglibyśmy rozszerzyć możliwości MI-
wynik_form = new Form("WYNIK"); Dletu o przeliczanie innych walut. Mam na-
StringItem instrukcje = new StringItem("Wg. KURSU "+wybrana_waluta+" NBP z dnia dzieję, że przedstawione w artykule informa-
"+data+": ",tekst); cje zachęcą czytelników do rozpoczęcia przy-
//dodajemy tekst wyniku gody z pisaniem własnych programów na ko-
wynik_form.append(instrukcje); mórkę.
//przycisk BACK - cofa nas to formy wprowadzania informacji
wynik_form.addCommand(CMD_BACK); IGOR KRUK
//przycisk EXIT - wyjscie z aplikacji Igor Kruk jest z wykształcenia informatykiem.
wynik_form.addCommand(CMD_EXIT); Obecnie pracuje na stanowisku Business Intelli-
//dodanie nasluchu gence Consultant i zajmuje się wdrażaniem sys-
wynik_form.setCommandListener(this); temów klasy BI. Jest również współautorem ksią-
//ustawiamy aktualna forme na forme wyniku żek „Oracle 10g i Delphi. Programowanie baz da-
mDisplay.setCurrent(wynik_form); nych” oraz „SQL Server 2005. Zaawansowane roz-
} wiązania biznesowe”.
Kontakt z autorem: igorkruk@gmail.com, http://
www.igorkruk.pl

www.sdjournal.org 153
Programowanie JavaME

Klasyczna platformówka
na komórkę
Piszemy grę bazującą na platformie Java Micro Edition

Tworzenie każdej gry, nawet najprostszej, łączy w sobie wiele wyzwań,


z którymi musi uporać się programista. Tworzenie aplikacji dla platformy
mobilnej niesie dodatkowe problemy. Jednak satysfakcja z napisania
własnej, nawet bardzo prostej gry na telefon komórkowy, jest bezcenna!
Jeśli chciałbyś poczuć taką satysfakcję, to przeczytaj poniższy artykuł.
W ramach niniejszego artykułu napisze-
Dowiesz się: Powinieneś wiedzieć: my prostą grę platformową na telefony ko-
• W jaki sposób stworzyć prostą grę na tele- • Podstawowa znajomość Javy. mórkowe oferujące wsparcie dla standardu
fon komórkowy; JME. Naszą pracę podzielimy na kilka ko-
• W jaki sposób można budować plansze do gry; lejnych etapów. Zaczniemy od doboru od-
powiednich narzędzi oraz wymyślenia fa-
buły. W kolejnym etapie naszej pracy sku-
ba najważniejszą zaletą platformówek, pa- pimy się na przygotowaniu tzw. poziomu
trząc z punktu widzenia programisty, jest (ang. level), czyli – innymi słowy – na stwo-
Poziom trudności możliwość wykorzystania raz zaimplemen- rzeniu wirtualnej rzeczywistości, w któ-
towanego silnika tego typu gier przy pro- rej będzie egzystował nasz bohater. Kie-
dukcji kolejnych tytułów z tego gatunku. dy będziemy już mogli oglądać stworzony
Tworzenie nowej gry sprowadza się w ta- przez nas świat na ekranie telefonu komór-

N
ie ma chyba użytkownika kompu- kim przypadku jedynie do podmiany zaso- kowego, ożywimy bohatera, tak aby stero-
tera wywodzącego się z tak zwa- bów, tj.: grafiki, animacji, dźwięków, plansz wany przez gracza poruszał się po utwo-
nej starej szkoły (ang. old school), itp. Drobne modyfikacje fabuły, dodanie rzonej wcześniej planszy. Aby życie nasze-
który nie miałby na swoim koncie wielu go- kilku nowych elementów funkcjonalności, go wojownika nie było zbyt proste, doda-
dzin spędzonych na grze w klasyczną plat- i mamy nowy tytuł! Co ciekawe, opisany my w grze przeciwników oraz wzbogaci-
formówkę. Ten gatunek gier przewodził wyżej proces może w wielu przypadkach my ich prymitywną inteligencją. Dodawa-
kiedyś wszystkim produkcjom. Gry plat- odbyć się bez konieczności ingerencji pro- nie bonusów, dodatkowych punktów i cza-
formowe, potocznie nazywane jump & run, gramisty. Niestety, są też negatywne stro- su to nieodzowne elementy każdej platfor-
wciąż mają w sobie coś magicznego, co spra- ny takiego podejścia. W platformówkach mówki, w związku z czym nie może ich za-
wia, że wracamy do nich z wielkim senty- testowanie i znajdowanie błędów jest bar- braknąć w naszej mini-produkcji. W dal-
mentem. Warto zatrzymać się nad tym kla- dzo czasochłonne, zaś reprodukcja wyjąt- szej kolejności dodamy obsługę punktów
sycznym gatunkiem i zajrzeć w proces two- kowych sytuacji w grze trudna. Początko- i upływającego czasu oraz prosty interfejs
rzenia tego typu gier od kuchni. Na ryn- wy etap produkcji wymaga przygotowania użytkownika (ang. user interface). Na ko-
ku dostępna jest bardzo duża ilość plat- silnika do renderowania planszy, logiki gry, niec pozostawimy nieodzowną część pro-
formówek na telefony komórkowe. Two- sztucznej inteligencji przeciwników, inte- cesu produkcji każdej gry: testy, testy, te-
rzenie jump & run'a na urządzenia mobil- rakcji między postaciami w grze. Nieste- sty. Na tym etapie zalecane będzie wspar-
ne, gdzie grę trzeba dostosować do kilku- ty potrzeba na to dużo czasu, a wysokiej ja- cie brata, siostry lub kuzyna; niech grają i
set telefonów i kilkudziesięciu rozdzielczo- kości silnik mamy najczęściej dostępny do- dzielą się wszelkimi spostrzeżeniami. To
ści, jest wbrew pozorom dość prostym pro- piero po wydaniu kilku udanych produkcji. wbrew pozorom jeden z ważniejszych eta-
cesem, przynajmniej w porównaniu do in- Mimo wszystko praca nad takim klasykiem pów tworzenia gry, tzw. testy grywalności
nych typów gier. Przede wszystkim dzięki daje ogromną satysfakcję. Chęć znajdywa- (ang. play testing). Na podstawie wyników
ogromnej skalowalności proces portowania nia kolejnych kluczy, monet, kryształów, tych testów dopracowuje się szczegóły, po-
(patrz: Ramka Portowanie aplikacji JME) diamentów, ukrytych poziomów, a także (a prawia plansze, dobiera lepiej czas i usta-
jest znacząco krótki. Oznacza to oczywi- może przede wszystkim) walka z przeciw- wienia bonusów, kolejnych żyć, a także po-
ście zarówno tańsze koszty produkcji, jak nikami, którzy przeszkadzają osiągnąć cel, zycje odpowiednich przeciwników na plan-
i potencjalne większe zyski. Drugą i chy- wciąga bez pamięci. szy. Nasza gra musi być grywalna, dlatego

154 SDJ Extra 34 Biblia


Gra w Javie na komórkę

każda uwaga wytrawnego gracza jest cenna rzemy pakiety Java SE Development Kit na go bardzo łatwo zintegrować z IDE. Ba-
i warto się zastanowić nad sugestiami i spo- (JDK) w wersji 6 (lub nowszej). Bardzo zowym modelem telefonu komórkowego,
strzeżeniami osób trzecich. Na koniec pod- ważne jest dobranie odpowiednich narzę- na którym będziemy pracować, będzie SE
sumujemy efekty naszej pracy, zastanowi- dzi do pracy. W przypadku naszego projek- w900i. Rozdzielczość ekranu na tym urzą-
my się nad możliwościami dalszego rozwo- tu zalecam wykorzystanie środowiska Net- dzeniu wynosi 240 pikseli szerokości i 320
ju projektu oraz ulepszeniem i optymaliza- Beans lub Eclipse. Ja osobiście stosowałem pikseli wysokości. SE w900i oferuje rów-
cją. Na ten moment nie pozostaje nic inne- NetBeans z wtyczką do obsługi JME. Na- nież sporą ilość pamięci, ponad 3,7 MB he-
go, jak tylko zabrać się do pracy. rzędzie to jest wygodne, ma duże możli- ap'a, z czego ponad 2MB pamięci graficz-
wości, a zarazem jest stosunkowo łatwe w nej, natomiast ograniczeniem dla pliku wy-
Wybieramy narzędzia instalacji i konfiguracji. Do pracy będzie- nikowego jest jedynie dostępna pamięć na
Nasz projekt będziemy rozwijać z wyko- my potrzebować również emulatora telefo- karcie (470 MB).
rzystaniem platformy Java, więc na począ- nu komórkowego, który we wstępnym eta- Kolejnym bardzo ważnym narzędziem
tek odwiedzimy strony firmy Sun i pobie- pie naszych prac będzie zastępował docelo- jest edytor plansz. Oczywiście można po-
we środowisko działania gry. Mamy tu dość kusić się o napisanie własnego narzędzia.
Listing 1. Przykładowa implementacja duży wybór, począwszy od standardowego My jednak nie mamy na to czasu i skorzy-
kodowania RLE sun'owskiego WTK (Sun Java Wireless Tool- stamy z darmowego produktu. Jest spo-
kit), po emulatory od konkretnych produ- ro takich narzędzi; ja osobiście polecam
try { centów telefonów: Nokia, Sony Ericsson, TileStudio albo Mappy. Na nasze potrze-
DataOutputStream out = new Motorola (patrz: Ramka W sieci). Ja wy- by w zupełności wystarczy Mappy: jest on
DataOutputStream( brałem Sony Ericsson'a, jako że pokrywa bardzo prosty w obsłudze i ma podstawo-
new FileOutputStream("level_ on dużą ilość modeli, jest przyjazny i moż- wą funkcjonalność. Umożliwia tworzenie
compressed.dat"));
int i = 0;
while (i < levelMap.length) {
int val = levelMap[i];
int count = 1;
i++;
while (i < levelMap.length &&
count < 127 &&
levelMap[i] ==
val) {
count++;
i++;
}
if (count > 1)
out.write((128 + count));
out.write(val);
}
out.close();
}
catch (IOException e) {
e.printStackTrace();
} Rysunek 1. Widok planszy w edytorze poziomów MappyWin32. Centralna część to obszar roboczy
naszej mapy. Z prawej strony widać użyty zestaw tili

Portowanie aplikacji JME


Portowanie aplikacji na telefony komórkowe oznacza dostosowanie jej do prawidłowego działania na kilkuset modelach telefonów komórko-
wych kilkudziesięciu producentów. Głównym zadaniem specjalistów od portowania gier jest dostosowanie bazowego kodu gry do określonej li-
sty telefonów. Każdy telefon ma swoje charakterystyczne parametry i ograniczenia, takie jak rozdzielczość ekranu, ilość pamięci, format dźwię-
ku. Są modele, które mają problemy ze zwalnianiem pamięci, inne nie radzą sobie z dużymi plikami. Niektóre telefony nie mają wszystkich kla-
wiszy funkcyjnych, inne mają tylko ekran dotykowy. Proces portowania kończy się wygenerowaniem bardzo dużej ilości plików aplikacji na każ-
dy model. Oczywiście zależnie od producenta poszczególne modele przynależą do grup i serii (np. Nokia seria S40v1). Kilka, a nawet kilkadzie-
siąt modeli z tej samej grupy, serii posiada wówczas jedną parę plików wykonywalnych *.jad oraz *.jar.

Game Designer
Projektant gier komputerowych to osoba, która wymyśla fabułę gry, czyli innymi słowy – pisze jej scenariusz. Oprócz tego wymyśla reguły, zasa-
dy oraz strukturę gry. Historia bohatera, jego zadania i cele, a także to, jakich przeciwników spotka on na swojej drodze – wszystko to zależy od
kreatywności projektanta. Osoba pracująca na takim stanowisku musi być dobrze zorientowana w zagadnieniach takich jak teoria grywalności
(ang. playability), interakcja między obiektami w grze, optymalizacja produkcji, wydajność techniczna. Poza tym musi biegle orientować się w
branży gier komputerowych, znać nowoczesne trendy współczesnego rynku gier na różnych platformach, standardy grywalności itd. Kreatyw-
ność, wyobraźnia to bardzo ważne atuty tego zawodu. Game Designer to zwykle maniak gier (ang. hardcode player), często posiadający duże do-
świadczenie jako tester gier. Bardzo często osoba taka pełni również funkcję projektanta poziomów (ang. Level Designer) – czyli układa plansze,
stopniuje poziomy trudności, definiuje w grze misje przy pomocy odpowiednich edytorów i narzędzi pomocniczych.

www.sdjournal.org 155
Programowanie JavaME

kilku warstw, co jest ważne dla naszej gry. nie z sieci (patrz: Ramka W sieci). Teraz, kie- Tworzymy fabułę
Przyda się jeszcze jakiś program do edy- dy mamy już wszystko co potrzebne do pracy, – prosty projekt gry
cji grafiki, np. Irfanview. Wszystkie opisa- czas sięgnąć po kartkę papieru i wymyślić na- Nasza gra ma być przede wszystkim prosta.
ne wyżej narzędzia można pobrać bezpłat- szą platformówkę. Nie będziemy więc tworzyć wyrafinowa-
nego, skomplikowanego scenariusza, choć
Listing 2. Przykładowa implementacja dekodowania RLE z drugiej strony – pokusimy się o odrobinę
fantazji. Proponuję osadzić bohatera (ang.
try { hero) naszej gry w wirtualnym świecie wie-
byte[] decompressedLevel = new byte[levelMap.length]; kowych moczar, zapomnianych lasów, w
DataInputStream in = new DataInputStream( którym, skacząc po platformach, będzie on
new FileInputStream("level_compressed.dat")); musiał odnaleźć magiczne klucze ukryte w
i = 0; różnych miejscach planszy. Szukając klu-
while (true) { czy, będzie on zbierał złote dukaty i zdo-
try { bywał dodatkowe punkty. Jak dobrze po-
int b = in.readUnsignedByte(); szuka, to znajdzie dodatkowe życie albo
int count = 1; dodatkowy czas na odnalezienie brakują-
int val = b; cych kluczy. Przeciwnicy występujący na
int n = (b&0xff); planszy będą starali się utrudnić mu wyko-
if ((n&128) == 128) { nanie zadania. Nasz bohater będzie spoty-
count = n&127; kał ich w różnych miejscach. Zachowanie
val = in.readUnsignedByte(); przeciwników będzie stosunkowo proste.
} Poruszać się będą oni na planszy według
for (; count > 0; count--) { określonych znaczników. Jeśli nasz boha-
decompressedLevel[i] = (byte)val; ter znajdzie się w pobliżu terytorium wro-
i++; ga, czyli w jego zasięgu, to ten będzie pró-
} bował go złapać i zabrać mu cenną ener-
} gię. Utrata zbyt dużej ilości energii pro-
catch (EOFException e) { wadzi do utraty jednego z żyć. Zakładamy
break; na starcie, że nasz bohater ma trzy życia,
} czas na przejście pierwszej planszy ustali-
} my w końcowym etapie play testów. Mo-
żemy pokonać przeciwnika, uderzając go,
//sprawdzamy poprawność danych po dekompresji kiedy jesteśmy odpowiednio blisko, zbyt
for (i = 0; i < decompressed.length; i++) bliski kontakt z przeciwnikiem spowodu-
if (decompressedLevel[i] != levelMap[i]) je utratę energii naszego bohatera. Głów-
System.out.println(„BLAD DANYCH"); nym celem gry jest zdobycie jak najwięk-
szej ilości punktów, czyli zebranie mak-
} symalnej ilości monet, każdy znaleziony
catch (IOException e) { klucz to dodatkowe punkty. Tak więc na-
e.printStackTrace(); sze zadanie to zebrać wszystkie klucze na
} planszy oraz uzbierać jak najwięcej zło-
tych monet. Poruszanie się po planszy mu-
si być maksymalnie uproszczone, dostęp-
Listing 3. Klasa startowa Start.java ne za pomocą d-pada lub joysticka telefo-
public class Start extends MIDlet { nu komórkowego. Alternatywnie wskaza-
private Game game; na jest możliwość grania za pomocą kla-
wiatury numerycznej telefonu, pamiętaj-
public void startApp() { my jednak, że nie każdy telefon posiada ta-
if (game == null) { kową klawiaturę.
game = new Game(this); Poniżej przedstawione są klawisze funkcyj-
} else { ne naszej gry:
game.showNotify();
} • d-pad / joystic – poruszanie się po plan-
} szy w poziomie, skok w górę oraz ude-
rzenie;
public void destroyApp(boolean unconditional) { • klawisz numeryczny 2 – skok w górę;
• klawisz numeryczny 4 – poruszanie
} w lewo;
• klawisz numeryczny 6 – poruszanie
public void pauseApp() { w prawo;
game.hideNotify(); • klawisz numeryczny 5 – uderzenie.
}
} W grze przewidziane będą dwa typy prze-
ciwników:

156 SDJ Extra 34 Biblia


Gra w Javie na komórkę

• TYP1 – czeka aż bohater pojawi się w je- ko odpowiednik angielskojęzycznego poję- Win32. Do szczęścia brakuje nam jeszcze
go pobliżu, wówczas rozpoczyna poru- cia tile stosuje się słowo kafelek), z których zestawu tili. Jeśli ktoś ma ochotę, to zachę-
szanie w jego kierunku; będzie można utworzyć maksymalnie róż- cam do pobawienia się w PhotoShopie, tu-
• TYP2 – porusza się od znacznika do norodne plansze, platformy, elementy do- dzież innym programie graficznym, i przy-
znacznika, zmieniając kierunek swo- datkowe itd. Korzyści z korzystania tilese- gotowanie własnego zestawu. Jest to jed-
jego ruchu na zgodny z kierunkiem tów są ogromne. Trudno jest wyobrazić so- nak dość czasochłonne i wymaga talentu
znacznika. bie sytuację, w której grafik przygotowuje artystycznego. My poszukamy w sieci goto-
gigantyczną planszę, którą wyświetlamy wego, darmowego tilesetu. Mnie udało się
Dodatki występujące w naszej grze: na telefonie. Tym bardziej, że mamy tutaj takowy znaleźć (patrz: Ramka W sieci). Na
sporo ograniczeń, zwykle można wgrać do tej samej stronie autor udostępnia jeszcze
• serduszko – dodatkowe życie; pamięci telefonu grafikę wielkości dwóch, kilka innych przydatnych grafik; amatorów
• moneta – punkty w grze; trzech ekranów. Nawet gdyby zastosować tworzenia gier zachęcam do zapoznania się
• klepsydra – dodatkowy czas na ukoń- takie podejście, to wynikowy poziom był- z tymi materiałami. Tileset do używania
czenie poziomu. by bardzo mały. A gdzie pozostałe elemen- w Mappym musi być zapisany w formacie
ty gry, bohater, przeciwnicy i inne...? Nale- BMP, dlatego też pobraną grafikę (dostęp-
Edytor poziomów ży pamiętać, że po wgraniu np. pliku gra- ną w formacie PNG) przekonwertujemy
– tworzymy plansze ficznego 100x100 pikseli, który na dysku przy pomocy IrfanView do postaci wyma-
Każda dobra platformówka to przede twardym zajmuje załóżmy 2kB, w pamię- ganej przez nasze narzędzie.
wszystkim dobra plansza, innymi sło- ci telefonu zajmuje około 20kB. Co gorsza, Skoro mamy wszystko, co potrzeba, uru-
wy: świetna grafika, odpowiednio ułożo- zdarzają się urządzenia, na których będzie chamiamy Mappy'ego. Program ten jest ła-
ne platformy, niespodziewane tajemne on zajmował około 40kB. Przy dostępnych twy i intuicyjny w obsłudze. Na początek
przejścia, trampoliny, czarne dziury i nie- dzisiaj nośnikach o pojemnościach liczo- wybieramy opcję File>New Map (skrót Ctr-
bezpieczne miejsca. Jak można się domy- nych w gigabajtach czy terabajtach, przy- l+M). Podajemy rozmiar pojedynczego ti-
śleć, przygotowanie dobrego poziomu nie toczone tu liczby wydają się być śmieszne. la 16 pikseli, oraz rozmiar całej mapy w
jest wcale łatwym zadaniem. Plansza bar- Jednak są jeszcze telefony na rynku, któ- tilach. Pierwszy poziom nie powinien być
dzo często to wielki duży obszar, kilkana- re dysponują niewielką pamięcią graficz- zbyt duży, powiedzmy 40 na 30 tili, co da-
ście, a nawet kilkadziesiąt razy większy niż ną. Telefony z tak zwanej listy 64k, czyli je nam w wyniku planszę o rozmiarach 640
ekran naszego telefonu. Świat, w którym te, na których rozmiar uruchamianej pacz- na 480 pikseli. Proponuje włączyć jeszcze
porusza się nasz bohater musi być odpo- ki *.jar nie może przekroczyć 65535 baj- jedną opcję: MapTools>Dividers; w okien-
wiednio duży, aby gra była ciekawa. Ponad- tów, oferują swoim programom jedynie ku zaznaczamy Enable Dividers, wybiera-
to kolejne plansze w grze powinny być co- około 250kB pamięci (ang. heap memory), my kolor siatki na niebieski: Line Colour:
raz trudniejsze, większe, a gracz musi mieć z czego 70kB to pamięć graficzna. W takiej 0x0000ff, oraz ustawiamy rozmiar poje-
poczucie narastającej trudności i ciągle od- sytuacji budowanie planszy z tili jest więc dynczej kratki: Pixel gap X/Y na 16, czyli ta-
krywać coraz to nowe elementy. Pierw- koniecznością. Tworzenie własnej planszy ki jak rozmiar naszego tila. Wgrywamy nasz
sze poziomy zwykle służą nauce porusza- daje też bardzo dużo satysfakcji. Jest wiele tileset z dysku (File>Import). Teraz możemy
nia się, oraz zapoznaniem się z umiejętno- dostępnych narzędzi do przygotowywania rozpocząć projektowanie naszego wirtual-
ściami i możliwościami bohatera. Idea bu- świata z tilesetów, często programiści przy- nego świata. Ja, przyznam uczciwie, posze-
dowania planszy w platformówkach opiera gotowują swoje własne edytory. My sko- dłem na łatwiznę i skorzystałem z przykła-
się na używaniu jednego lub kilku różnych rzystamy z pobranego wcześniej Mappy- dowego poziomu, pobranego wraz z tilese-
tilesetów, z których buduje się poziom. Ti-
leset to zestaw unikalnych elementów gra- Listing 4. Fragment pliku z zasobami Res.java
ficznych, np. 16x16 pikseli, które pozwala-
ją budować plansze. Oczywiście grafik ma public interface Res {
tutaj ogromne pole do popisu, aby przygo-
tować takie tile (w polskiej terminologii ja- public final static int IMG_BG = 0;
public final static int IMG_TILESET = 1;
public final static int IMG_HERO_STAY_LEFT = 2;
public final static int IMG_HERO_STAY_LEFT1 = 3;
public final static int IMG_HERO_STAY_LEFT2 = 4;
public final static int IMG_HERO_STAY_LEFT3 = 5;
...

String imgPath[] = {
"bg",
"tileset",
"hero_06_f",
"hero_07_f",
"hero_08_f",
"hero_09_f",
...
};

}
Rysunek 2. Kolejność renderowanych tili

www.sdjournal.org 157
Programowanie JavaME

tem. Warto w tym momencie wspomnieć o Najprościej wygenerować mapę jako dwu- użycia bezpośrednio w kodzie źródłowym.
jednej z użytecznych funkcjonalności, jakie wymiarową tablicę, a wartości w tej tablicy Gdy mamy jednak do czynienia z większą
daje Mappy; mowa tu o tworzeniu mapy z to indeksy umieszczonych na mapie kafel- liczbą poziomów, a tak zazwyczaj bywa w
obrazka. Możemy wczytać wcześniej przy- ków. Możemy również wyeksportować so- praktyce, zalecane jest, aby przenieść ich
gotowaną dużą mapę do programu, któ- bie nasze tile. Najkorzystniej będzie usta- dane do zewnętrznych plików binarnych,
ry sam podzieli ją na kafelki o zdefiniowa- wić je w pojedynczą, długą kolumnę. Pozo- które będą kolejno wczytywane w zależ-
nej wielkości oraz stworzy listę niepowta- staje nam jeszcze ustalić, które tile z nasze- ności od potrzeby. W takiej sytuacji warto
rzalnych tili, które można wyeksportować. go tilesetu będziemy traktować jako kolizyj- również pokusić się o prostą kompresję. Po-
Istotne w tym przypadku jest, aby wielkość ne, to znaczy takie, po których nasz boha- mimo tego, że pliki binarne z danymi po-
wczytywanej grafiki pokrywała się z wiel- ter będzie mógł się poruszać (będą one sta- ziomów bardzo dobrze się kompresują i w
kością naszej mapy, oraz aby była przygoto- nowiły dla niego podłoże). W naszym ze- wynikowym rozmiarze pliku *.jar nie za-
wana tak, że da się podzielić na powtarzal- stawie są to wszystkie kafelki posiadają- uważymy dużej różnicy, to po rozpakowa-
ne elementy. ce elementy trawy – (indeksy 24-27, 32, niu naszej gry spora ilość nieskompresowa-
Gdy już mamy mapę, czas wyeksporto- 37, 46) oraz te, z których tworzymy po- nych plików będzie zajmować dużo miej-
wać ją w takiej postaci, aby dało się ją od- ziome odgałęzienia od pni drzew (29-31, sca. Pisząc grę w JME, należy pamiętać, że
tworzyć na telefonie komórkowym. W tym 34-35, 42). warto walczyć o każdy kilobajt! Przygląda-
celu wybieramy File>Export as text... W Poziom wyeksportowany w postaci dwu- jąc się naszemu plikowi binarnemu, łatwo
okienku ustawiamy parametry eksportu. wymiarowej tablicy bajtów gotowy jest do zauważyć, że jest tam bardzo dużo powta-
rzających się sekwencji. Bardzo prosta do
Listing 5. Wyznaczenie obszaru renderowania implementacji jest kompresja RLE (ang.
Run-Length Encoding), czyli kodowanie dłu-
public void initLayer() gości serii. Co więcej, RLE będzie bardzo
{ efektywna w przypadku naszych danych.
levelWidthT = LEVEL_WIDTH; Kompresja ta polega na opisywaniu ciągu
levelHeightT = LEVEL_HEIGHT; tych samych wartości za pomocą licznika
powtórzeń. W ten sposób powstają pary
//wielkosc planszy w pikselach (licznik, wartość).
levelWidth = levelWidthT<<TILE_SHIFT; Na przykład, zamiast sekwencji: 1, 1, 1, 1,
levelHeight = levelHeightT<<TILE_SHIFT; 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
levelMaxOff = levelWidthT*levelHeightT; 1, 1, 1, 1, 1, 1, 1,... mamy parę: (29,1). Przy-
kładową implementację kodowania i dekodo-
levelX = 0; wania RLE przedstawiono odpowiednio na
levelY = 0; Listingach 1 i 2.
Na tym etapie zakończymy pracę z edyto-
screenWidthT = X_RES >> TILE_SHIFT; rem; wrócimy do niej przy dodawaniu prze-
screenWidth = screenWidthT<<TILE_SHIFT; ciwników i rozmieszczaniu dodatkowych ele-
if(screenWidth < X_RES) { mentów na planszy.
screenWidth += TILE_SIZE;
screenWidthT++; Struktura projektu,
} zaczynamy pisać kod!
screenWidth += TILE_SIZE; Rozpoczynając projekt gry na komórkę, ba-
screenWidthT++; zujący na platformie JME, dobrze jest przy-
jąć pewne założenia co do architektury
screenHeightT = Y_RES >> TILE_SHIFT; tworzonej aplikacji. Przede wszystkim na-
screenHeight = screenHeightT << TILE_SHIFT; leży pamiętać, iż nie ma tutaj mowy o pi-
if(screenHeight<Y_RES) { saniu czysto obiektowym. Dobrą praktyką
screenHeight += TILE_SIZE; jest minimalizowanie ilości klas. Najbez-
screenHeightT++; pieczniej jest zamknąć główny silnik gry
} w jednej klasie, do tego dodać jakieś dwie,
screenHeight += TILE_SIZE; trzy klasy pomocnicze do animacji, rysowa-
screenHeightT++; nia planszy itd. Stałe konfiguracyjne warto
trzymać w oddzielnym pliku jako publicz-
} ne statyczne. W tym celu dodajemy pustą
klasę tylko z polami typu public static

Dodawanie warstwy w edytorze poziomów


MappyWin32 umożliwia dodanie kilku warstw, co bardzo przydaje się w grach typu jump & run, i generalnie we wszelkich grach tworzonych w
oparciu o tilesety. Aby uzyskać zamierzony efekt, otwieramy plik projektu (zakładam, że był on wcześniej zapisany na dysku). Następnie wybie-
ramy z menu głównego opcję Layer i dalej Add Layer. Włączamy poprzednią warstwę z wcześniej utworzonym poziomem, aby widzieć, gdzie na
planszy stawiamy symbole maski. Następnie wybieramy z menu głównego Layer i dalej Onion skin... (skrót: [Ctrl+K ]). Otwiera się okienko, w któ-
rym zaznaczamy opcję Enable Onion Skin, oraz wybieramy z listy Background layer wartość ALL. Dla lepszej widoczności można lekko ściemnić
pierwszą warstwę z planszą gry (layer1). W tym celu wybieramy z menu głównego Layer oraz Background Layers darkened. Nowa warstwa (lay-
er2) jest w tym momencie dodana do naszego projektu, aktywna i gotowa do modyfikacji: możemy umieszczać na niej tile maski.

158 SDJ Extra 34 Biblia


Gra w Javie na komórkę

albo javowy interfejs (interface), imple- mowka, Start, /icon.png. Należy pamiętać Ekran będziemy przewijać za pomocą me-
mentowany przez dowolną klasę. Jeśli zaj- o tym, żeby nie używać polskich znaków tod moveHorizontal() oraz moveVertical()
dzie potrzeba edycji stałych, będziemy w nazwach. Część parametrów możemy (patrz: Listing 6), podając jako parametr
mieli łatwy i szybki dostęp do nich. Od- bezpiecznie pominąć (np. API Permissions, wartość zmiennej delta, określającej, o ile
radzam stosowanie skomplikowanych hie- itd.). W zakładce Libraries & Resources usta- pikseli chcemy przesunąć mapę. Metody te
rarchii dziedziczenia, zachęcam do pro- wiamy ścieżki do naszych zasobów, wybie- ustawiają zmienne levelX i levelY, czyli
stoty, zarówno na poziomie struktury klas, rając Add folder. W zakładce Obfuscating aktualną pozycję planszy (lewy górny róg).
jak i kodu. Punktem startowym naszej apli- ustalamy wysoki (ang. high) poziom tzw. Główny kod renderujący zaimplementowa-
kacji będzie klasa Start, dziedzicząca po obfuskacji, czyli zaciemniania kodu. Jest to ny jest w metodzie renderLayer() (patrz:
MIDlet. Upraszczamy ją do maksimum, pa- bardzo ważne, gdyż proces obfuskacji zna- Listing 7), w niej odrysowujemy interesują-
miętając, że nie będzie ona optymalizowa- cząco redukuje rozmiar plików *.class, wy- cy nas fragment planszy. Zaczynamy od le-
na. Cały silnik gry zawarty będzie w klasie generowanych w procesie kompilacji. Po- wej górnej krawędzi i przesuwamy się w pra-
Game, zaś klasy pomocnicze to: LevelLayer prawia się też wydajność kodu, usuwane są wo, kolejno odrysowując kafelek po kafelku.
(wyświetlanie planszy), Character (repre- nieużywane metody i informacje o symbo- Gdy osiągniemy prawą krawędź, przecho-
zentacja bohatera oraz przeciwników). Do- lach. W trakcie tego procesu upraszczane dzimy do kolejnej linii aż osiągniemy pra-
datkowo stworzymy interfejs konfigura- są również nazwy zmiennych (np. zmienna
cyjny ze stałymi (Config.java) oraz z za- int levelMap[] będzie zmieniona na int Listing 6. Obsługa przewijania planszy w
sobami graficznymi (Res.java). Posiada- a[]). W zakładce Creating JAR definiuje- poziomie i pionie
nie narzędzi do zarządzania zasobami w my nazwy naszych plików wynikowych jad
grze jest bardzo korzystne i znacząco uła- i jar na Platformowka.jad oraz Platformow- public int moveHorizontal(int aDelta) {
twia pracę przy ich dodawaniu i edytowa- ka.jar. Wybieramy OK i na tym kończymy int r = 0;
niu. Na potrzeby naszej prostej gry wystar- konfigurację właściwości projektu.
czy, jeśli umieścimy w pliku Res.java tabli- levelX += aDelta;
cę nazw plików graficznych, stałe typu pu- Wyświetlamy if(aDelta<0) {
blic final static int z offsetami do tej tablicy planszę na ekranie... if(levelX<0) {
oraz tablicę obiektów typu Image[], w któ- Najwyższy czas zobaczyć jakieś efekty do- r = levelX;
rej będziemy przechowywać wszystkie gra- tychczasowej pracy na ekranie naszej ko- levelX = 0;
fiki. Listing 4 przedstawia fragment imple- mórki. W tym celu napiszemy prosty sil- }
mentacji interfejsu Res. nik renderujący mapę naszego świata. W }
Skoro struktura klas jest już ustalona, tym celu stworzymy klasę LevelLayer i na else {
tworzymy nowy projekt w naszym IDE, starcie dodamy do niej wyeksportowaną ta- int s = levelX-(levelWidth-
oraz ustawiamy kolejne właściwości dla blicę bajtów opisującą strukturę naszego screenWidth);
projektu. W przypadku NetBeans działa- poziomu. Interfejs klasy będzie zawierał if(s>0) {
my jak następuje: w zakładce Platform wy- metodę inicjującą initLayer() (patrz: Li- levelX = levelWidth-
bieramy Manage Emulators... i podpina- sting 5), w której wyliczymy, jaki obszar bę- screenWidth;
my nasz emulator Sony Ericsson'a, postę- dziemy renderować co ramkę (ilość tili do r = s;
pując zgodnie z poleceniami. Jak wszyst- renderowania w poziomie i w pionie). Je- }
ko zrobimy poprawnie, to mamy możli- śli ekran naszego telefonu wynosi X_RES = }
wość wybrania modelu telefonu z rozwi- 240 i Y_RES=320 pikseli, to musimy rende-
janego pola Device. Wybieramy SonyErics- rować odpowiednio 240/16 = 15+1 tili w return r;
son_W900_Emu. Następna właściwość to poziomie oraz 320/16 = 20+1 tili w pionie. }
już parametry midletu (aplikacji na tele- Obszar renderowania rozszerzamy w oby-
fon komórkowy), a dokładniej mówiąc, pa- dwu kierunkach o wartość 1, aby przewi- public int moveVertical(int aDelta) {
rametry pliku JAD oraz Manifest. W Attri- janie ekranu odbywało się płynnie. Ponie- int r = 0;
butes musimy podać MIDlet-Name (nazwa waż rozmiar jednego tila jest potęgą dwój-
aplikacji, np. Platformowka), MIDlet-Ven- ki, dzielenie można zastąpić przesunięciem levelY += aDelta;
dor (nazwa sprzedawcy/dostawcy, np. SDJ) bitowym TILE_SHIFT. Obszar renderowa- if(aDelta<0) {
oraz MIDlet-Version (numer wersji gry, np. nia (screenWidth oraz screenHeight) wy- if(levelY<0) {
1.0). Kolejna ważna właściwość aplikacji znaczamy więc odpowiednio za pomocą r = levelY;
to MIDlets, podajemy tu nazwę projektu, następujących wyrażeń: X_RES>> SHIFT +1 levelY= 0;
nazwę klasy startowej oraz ścieżkę do iko- oraz Y_RES>> SHIFT +1. W kodzie będzie- }
ny aplikacji, czyli odpowiednio: Platfor- my potrzebowali przechowywać wyznaczo- }
ny obszar renderowania zarówno w pikse- else {
lach (screenWidth, screenHeight), jak i w int s = levelY-(levelHeight-
kafelkach (screenWidthT, screenHeightT). screenHeight);
Dobrą praktyką w pisaniu gier na komór- if(s>0) {
ki (i nie tylko!) jest wyliczenie pewnych levelY = levelHeight-
wartości, które będziemy wykorzystywać screenHeight;
często w kodzie po to, aby zminimalizo- r = s;
wać maksymalną ilość obliczeń. Taką war- }
tością będzie na przykład maksymalny }
rozmiar tablicy z danymi naszej planszy: return r;
levelMaxOff, dlatego na początku metody }
Rysunek 3. Obszar kolizyjny bohatera initLayer() wyliczamy jego wartość.

www.sdjournal.org 159
Programowanie JavaME

wy dolny wierzchołek. Rysunek 2 przedsta- naszej planszy. Dodając jedną pełnoekrano- kolwiek zupełnie wystarczająca na potrze-
wia poglądowo kolejność rysowanych tili. wą grafikę wyświetlaną na drugim planie, by naszej gry. Należy jednak wspomnieć,
Kafelki w naszym tilsecie ułożone są piono- przesuwaną z inną prędkością niż prędkość że silnik ten można dodatkowo zoptymali-
wo jeden pod drugim, więc możemy ustawić planszy, możemy uzyskać dodatkowy efekt zować, tak aby renderowanie odbywało się
tzw. klipa (tj. prostokąt obcinania) raz na ca- pseudo-paralaksy; jak taki efekt zaimplemen- na off-screenie. Bardziej skomplikowany spo-
ły renderowany w danym momencie wiersz tować, pokazane jest na początku metody sób w skrócie oznacza, że mamy dodatko-
(X_RES * TILE_SIZE = 240 * 16). Mapa renderLayer(). Bardzo przydatna będzie wy bufor wielkości ekranu, a po przesunię-
wyeksportowana z edytora jest dwuwymia- jeszcze prosta metoda do ustawiania pozy- ciu planszy silnik miałby odrysowywać tyl-
rową tablicą indeksów do poszczególnych ti- cji poziomu setPos(x, y); metoda ta poka- ko jeden rząd bądź kolumnę kafelków. Ten
li. Jeśli chcemy wyświetlić kafelek o indek- zana jest na Listingu 8. Jeśli, dla przykładu, mechanizm renderingu jest dużo szybszy
sie 10, to wystarczy na osi Y przesunąć tilset chcemy rozpocząć grę w punkcie określo- od prostej implementacji przedstawionej w
o wartość TILE_SIZE * 10 = 160 pikseli, al- nym indeksami (100, 100) na naszej plan- niniejszym artykule, ale niestety – wymaga
bo jeszcze lepiej 10 << TILE_SHIFT. W celu szy, to wywołujemy setPos(100,100). Po ta- alokowania dodatkowej sporej ilości pamię-
optymalizacji operacji dokonywanych na ti- kim wywołaniu nasza pozycja będzie usta- ci, co w przypadku gier pisanych na telefo-
lach zastępujemy mnożenie i dzielenie prze- wiona centralnie na ekranie telefonu. ny komórkowe może stanowić poprzeczkę
sunięciem bitowym. W taki oto prosty spo- Opisana powyżej implementacja silni- nie do przeskoczenia. Drugim ogranicze-
sób kafelek po kafelku rysujemy fragment ka renderującego jest bardzo prosta, acz- niem tego rozwiązania jest brak możliwo-
ści ustawienia przeźroczystości na rendero-
Listing 7. Renderowanie tili na ekranie wanym buforze, co oznacza, że tile muszą
być pełne i nie mogą mieć obszarów prze-
public void renderLayer(Graphics g) { źroczystych.
//simply paralax'a
int x = X_RES - (levelX>>1)%X_RES; Dodajemy bohatera
int y = Y_RES - (levelY>>1)%Y_RES; W kolejnym kroku tworzymy klasę
Character, będzie ona charakteryzować
g.drawImage(Game.imgGet(Res.IMG_BG), x, y, ALIGN_LT); naszego bohatera. W tej klasie umieszcza-
g.drawImage(Game.imgGet(Res.IMG_BG), x, y, ALIGN_RT); my kod odpowiedzialny za kolizje, porusza-
g.drawImage(Game.imgGet(Res.IMG_BG), x, y, ALIGN_LB); nie się po planszy, odtwarzanie odpowied-
g.drawImage(Game.imgGet(Res.IMG_BG), x, y, ALIGN_RB); nich animacji i maszynę wszystkich sta-
nów postaci. Pierwszym obiektem, który
for(int ty=levelY>>TILE_SHIFT, py=-(levelY%TILE_SIZE); stworzymy dzięki tej klasie, będzie nasz he-
py<screenHeight; py+=TILE_SIZE, ty++) ro, ale wykorzystamy ją również w celu re-
{ prezentacji przeciwników występujących
g.setClip(0, py, X_RES, TILE_SIZE); w grze. Najpierw spróbujmy określić pod-
for(int tx=levelX>>TILE_SHIFT, px=-(levelX%TILE_SIZE); stawową maszynę stanów dla naszej posta-
px<screenWidth; px+=TILE_SIZE, tx++) ci. Pierwszy stan będzie stanem spoczyn-
{ ku, w którym postać stoi nieruchomo. Na-
g.drawImage(Game.imgGet(Res.IMG_TILESET), zwiemy ten stan STAND. Drugi i trzeci stan
px, py-(tileLevel[ty][tx]<<TILE_SHIFT), 0); będą reprezentować poruszanie się posta-
ci po planszy w poziomie, tj. w lewo bądź
//DRAW MASK w prawo. Stany te nazwiemy odpowiednio
switch( maskLevel[ty][tx] ) MOVE_LEFT oraz MOVE_RIGHT. Pozostało jesz-
{ cze poruszanie się postaci w pionie. Jed-
case MASK_COIN: nakże na naszej planszy nie przewidujemy
g.drawImage( Game.imgGet(Res.IMG_COIN), występowania takich elementów jak drabi-
px, py,0); ny czy liny do wspinania, a w zamian za to
break; planujemy umieścić tam platformy, na któ-
case MASK_HEART: re będzie można wskoczyć. Dlatego też zde-
g.drawImage( Game.imgGet(Res.IMG_HEART), finiujemy stan skoku: JUMP. Gdy postać na-
px, py,0); potka na swojej drodze wroga, powinna roz-
break; począć walkę. Dlatego kolejny stan nazwie-
case MASK_KEY: my FIGHT. Musimy także stworzyć stan, w
g.drawImage( Game.imgGet(Res.IMG_KEY), którym tracimy energię i życie: HURT. Prze-
px, py,0); łączanie stanów będzie odbywać się za po-
break; mocą metody setState(int state), zmia-
case MASK_TIME: na stanu polegać będzie na ustawieniu od-
g.drawImage( Game.imgGet(Res.IMG_TIME), powiedniej animacji postaci do odtworze-
px, py,0); nia (patrz: Listing 9).
break; Mamy już zdefiniowane stany, te-
} raz umieścimy naszego bohatera w świe-
} cie gry. Za pomocą dwóch zmiennych f_
} velocityX oraz f_velocityY będziemy ste-
} rować ruchem postaci. Poruszanie się po-
staci będziemy realizować wewnątrz me-

160 SDJ Extra 34 Biblia


Gra w Javie na komórkę

tod moveLeft(), moveRight() oraz jump() tuacji, w których nasz bohater nie będzie Rysunek 3 przedstawia prostokąt kolizyj-
(patrz: Listingu 10). Będziemy rozpatry- mógł ukończyć danego poziomu, ponieważ ny dla naszego bohatera. Prostokąt ten ma
wać dwa przypadki. Pierwszy z nich wy- nie wskoczy na strategiczną platformę. Lub rozmiar 25 na 50 pikseli. Dla porówna-
stępuje, gdy postać znajduje się na pod- odwrotnie: platformy będą tak ustawione, nia, wielkość klatki animacji bohatera to
łożu (onGround = true); wówczas doda- że za pomocą kilku skoków uda się zakoń- 51 na 72 pikseli. Mając takie informacje,
jemy lub odejmujemy wartość zmiennej czyć grę. możemy wykrywać kolizje postaci z tilami
speed do wektora prędkości. Drugi przy- Tworząc obiekt klasy Character, do kon- na planszy. Będziemy to robić co ramkę w
padek ma miejsce, gdy postać nie styka się struktora przekazujemy szerokość i wyso- metodzie updateCollision() (patrz: Li-
z podłożem, np. podczas skoku lub spada- kość postaci określone w pikselach. War- sting 11). Ograniczymy się do sprawdzania
nia z platformy; wtedy poruszamy się o po- to zauważyć, że nie jest to rozmiar klat- tylko jednego punktu kolizyjnego: od do-
łowę wolniej, czyli dodajemy lub odejmuje- ki animacji, ale definicja obszaru kolizyj- łu. Idea polega na sprawdzaniu odległości
my speed / 2 do wektora prędkości. Żeby nego (tak zwanego collision box'a). Jak sa- między środkiem collision box'a i środkiem
poruszanie było niezależne od czasu trwa- ma nazwa wskazuje, obszar ten służy głów- tila, z którym aktualnie występuje kolizja.
nia ramki, musimy dodawaną/odejmowaną nie do określania przestrzeni kolizyjnych. Pierwszy krok to sprawdzenie rodzaju ti-
wartość pomnożyć przez czas trwania ram-
ki (timeframe), a następnie podzielić przez
referencyjną stałą wartość (w tym celu wy-
korzystujemy przesunięcie bitowe o war-
tość 5, co jest równoznaczne z podziele-
niem przez 32). Ostatnim elementem tej
układanki jest ustawienie stanu postaci i
aktywowanie odpowiedniej animacji, czyli
wywołanie metody setState(). Do realiza-
cji pełnej funkcjonalności klasy Character
musimy zdefiniować podstawowe parame-
try fizyki, takie jak grawitacja, siła skoku,
tarcie, prędkość poruszania się. Parame-
try te będą kontrolowane przez następują-
ce stałe: GRAVITY, JUMP_POWER, FRICTION.
Prędkość poruszania się zdefiniujemy jako
zmienną speed; dzięki temu prostemu za-
biegowi będziemy w stanie modyfikować
prędkość poruszania się postaci. Dobra-
nie odpowiednich wartości tych parame-
trów jest szczególnie istotne z punktu wi-
dzenia uzyskania wysokiego poziomu gry-
walności. Bardzo ważne jest przyjęcie pew- Rysunek 4. Widok warstwy maski w level edytorze – MappyWin32
nych założeń co do siły skoku, najlepiej zro-
bić to przed rozpoczęciem projektowania
poziomów. Takie informacje trzeba uzgod- Umieszczanie aplikacji na telefonie
nić z projektantem planszy, aby uniknąć sy-
Za pomocą Bluetooth:

Listing 8. Ustawianie pozycji planszy • Włączamy w telefonie w opcjach połączenia Bluetooth;


• Znajdujemy na dysku wygenerowany plik Platformowka.jar (możemy go znaleźć w pod-
public void setPos(int aX,int aY) { katalogu dist naszego projektu);
• Zaznaczamy go i klikając prawy klawisz myszki, rozwijamy menu kontekstowe, z którego
wybieramy do Bluetooth. Rozwija się lista z urządzeniami, do których możemy przepro-
aX -= X_RES>>1;
wadzić transfer pliku;
aY -= Y_RES>>1; • Jeśli nie ma na tej liście naszego telefonu, wybieramy Szukaj innych urządzeń;
• Po udanym nawiązaniu połączenia PC-telefon komórkowy, na telefonie pojawi się pyta-
if(aX<0) aX = 0; nie o zgodę na odebranie pliku, a po odebraniu trzeba zaakceptować chęć instalacji na-
if(aY<0) aY = 0; szej gry i wybrać miejsce docelowe Gry lub Aplikacje;
• Po udanej instalacji telefon zapyta, czy uruchomić grę.
if(aX>levelWidth-screenWidth) Za pomocą sieci (ang. OTA, Over The Air):
aX = levelWidth -
screenWidth; • Umieszczamy oba pliki naszej gry Platformowka.jad oraz Platformowka.jar na dowolnym
serwerze online;
• W telefonie, który musi mieć poprawnie skonfigurowane ustawienia sieciowe, urucha-
if(aY>levelHeight-screenHeight)
miamy przeglądarkę internetową (z tym użytkownicy mają najwięcej problemów, aby
aY = levelHeight- dobrze skonfigurować telefon, zajrzyj na stronę operatora swojej sieci);
screenHeight; • W uruchomionej przeglądarce wpisujemy bezpośredni adres do pliku Platformowka.jad
umieszczonego na serwerze, np.: http://www.mojadomena.pl/gry/Platformowka.jad;
levelX = aX; • Po udanym połączeniu z serwerem pobrany zostanie plik *.jad, a z niego informacje o
grze i jej rozmiarze. Jeśli zaakceptujemy chęć instalacji gry, rozpocznie się pobieranie pli-
levelY = aY;
ku jar, a następnie instalacja;
} • Po udanej instalacji telefon zapyta, czy uruchomić grę.

www.sdjournal.org 161
Programowanie JavaME

la w celu weryfikacji, czy jest on kolizyj- f_velocityY > 0, to znaczy, że wektor jest Kolizja taka wystąpi wtedy i tylko wtedy,
ny (isTileCollision(tx, ty)). W dru- skierowany w dół i należy sprawdzić, czy gdy odległość między środkiem collision
gim kroku badamy wektor prędkości: jeśli czasem nie nastąpiła kolizja z podłożem. box'a i środkiem tila (dy) jest mniejsza od
sumy połowy tila (d2) i połowy wysokości
Listing 9. Metoda zmiany stanu postaci
prostokąta kolizyjnego (d1), czyli w sumie,
gdy spełniony jest warunek d1+d2<dy. Jeśli
public void setState(int state) { wystąpi taka kolizja, to ustawiamy zmien-
ną onGround na wartość true oraz korygu-
if(state == characterState || die) jemy współrzędną Y naszej postaci o war-
return; tość głębokości kolizji. Pozostaje jeszcze
obsługa animacji postaci. Reprezentację
switch(state) { graficzną naszego bohatera możemy spró-
case STAND: bować zrobić samemu, korzystając z do-
characterFrameMin = (characterSideLeft) ? resOffset : resOffset wolnego programu graficznego, lub poszu-
+RES_ANIM_STAY_RIGHT_OFF; kać darmowych animacji w sieci. Ja popro-
characterFrameMax = characterFrameMin siłem o pomoc znajomego grafika o pseu-
+ANIM_FRAMES_STAND; donimie Nelson (Bartosz Willim, Nanoga-
break; mes), który od wielu lat przygotowuje gra-
case MOVE_LEFT: fikę na potrzeby gier komputerowych. Bar-
characterFrameMin = resOffset tek znalazł chwilkę czasu i przygotował mi
+RES_ANIM_RUN_LEFT_OFF; potrzebne animacje. Jak dla mnie bomba!
characterFrameMax = characterFrameMin Przygotowana animacja ma 4 klatki dla
+ANIM_FRAMES_RUN; stanu spoczynku, 6 klatek reprezentują-
characterSideLeft = true; cych ruch, 1 klatkę dla skoku oraz 3 klatki
animLoopCount = 0; przewidziane na sytuację, gdy nasz boha-
break; ter ginie. Ilość klatek definiują stałe z pre-
case MOVE_RIGHT: fiksem ANIM_FRAMES_ w klasie Character.
characterFrameMin = resOffset Nie możemy zapomnieć o tym, że musimy
+RES_ANIM_RUN_RIGHT_OFF; obsługiwać animacje dla dwóch kierun-
characterFrameMax = characterFrameMin ków ruchu: w lewo oraz w prawo. Zmien-
+ANIM_FRAMES_RUN; na characterSideLeft będzie określać ak-
characterSideLeft = false; tualną orientację postaci. Dodatkowo trze-
break; ba zdefiniować czas trwania jednej klatki
case JUMP: ONE_FRAME_TIME (100 milisekund). Meto-
characterFrameMin = (characterSideLeft) ? da updateAnimation(timeframe), przed-
resOffset+RES_ANIM_JUMP_LEFT_OFF : stawiona na Listingu 12, odpowiada za
resOffset+RES_ANIM_JUMP_RIGHT_OFF; odtwarzanie animacji. Animacja ruchu w
characterFrameMax = characterFrameMin lewo lub prawo jest zapętlona, a zmien-
+ANIM_FRAMES_JUMP; na animLoopCount jest licznikiem aktual-
break; nie odtwarzanej animacji. Należy jeszcze
case FIGHT: zwrócić uwagę na pętlę while(), w meto-
characterFrameMin = (characterSideLeft) ? dzie updateAnimation(), która pełni tutaj
resOffset+RES_ANIM_JUMP_LEFT_OFF: rolę synchronizatora. W sytuacji gdy odpali-
resOffset+RES_ANIM_JUMP_RIGHT_OFF; my naszą grę na wolniejszym telefonie, nie
characterFrameMax = characterFrameMin musimy redukować ilości klatek anima-
+ANIM_FRAMES_FIGHT; cji, aby zachować całkowity czas jej trwa-
break; nia. Nasz synchronizator będzie wyświe-
case HURT: tlał co drugą albo co trzecią klatkę, zależ-
characterFrameMin = (characterSideLeft) ? nie od czasu trwania ramki. Nasza anima-
resOffset+RES_ANIM_HURT_LEFT_OFF : cja tym samym staje się niezależna od szyb-
resOffset+RES_ANIM_HURT_RIGHT_OFF; kości telefonu.
characterFrameMax = characterFrameMin Dochodzimy do momentu, w którym nasz
+ANIM_FRAMES_HURT; bohater sprawnie biega po planszy i wskakuje
die = true; na platformy, a kiedy stoimy w miejscu, oddy-
break; cha pełną piersią.
}
Dodajemy
animLoopCount = 0; przeszkadzajki i wrogów
characterFrame = characterFrameMin; Czas postawić na drodze naszego bohate-
characterFrameTime = 0; ra wrogów: w każdej grze musi być to coś,
characterState = state; co będzie utrudniało graczowi ukończe-
} nie poziomu. Na początek musimy okre-
ślić listę typów wrogów, których chcemy
stworzyć. Każdy z naszych wrogów powi-

162 SDJ Extra 34 Biblia


Gra w Javie na komórkę

nien mieć swoje charakterystyczne cechy, tylko raz na początku. Po utracie życia, kiedy nej. Sprawdzanie wykonujemy tylko w osi po-
odróżniające go od pozostałych. Może być trzeba przywrócić pozycje startowe wrogów, ziomej. Zachowanie drugiego rodzaju prze-
to odmienny sposób poruszania się, inny wywołujemy metodę enemyRestore(). Koli- ciwnika nazywa się fachowo patrolowaniem
rodzaj broni, większa wytrzymałość albo zja z bohaterem sprawdzana jest w metodzie obszaru typu A-B-A. Przeciwnik będzie prze-
przynajmniej odmienna kolorystyka czy enemyCheckCollision(), zaś rysowanie wro- mieszczał się w danym kierunku do czasu
ilość punktów przyznanych za zniszcze- gów zaimplementowano w enemyDraw(). Me- napotkania znacznika, czyli maski z nowym
nie. W naszej prostej grze proponuję zaim- chanizm sztucznej inteligencji oraz wszystkie kierunkiem. Porusza się od punktu A do
plementować dwa typy przeciwników. Typ obliczenia związane z obsługą wrogów odby- punktu B i następnie wraca do punktu A. W
pierwszy będzie czekał aż hero pojawi się w wają się co ramkę w metodzie enemyUpdate(). ten sposób można wyznaczyć przeciwnikowi
zdefiniowanym promieniu widzenia. Kie- Dla typu pierwszego zaczynamy od spraw- całkiem ciekawe ścieżki, obszary do patrolo-
dy odległość między hero i przeciwnikiem dzenia odległości między naszym bohate- wania. Nasze znaczniki na masce to strzałki
będzie mniejsza lub równa zdefiniowanej, rem a obiektem enemy. Jeśli odległość będzie w lewo i prawo. Dla tego typu sprawdzamy,
wróg natychmiast rozpocznie wędrówkę w mniejsza od zdefiniowanej, ustawiamy stan czy wystąpiła kolizja ze znacznikiem kierun-
kierunku bohatera. Drugi typ to klasyczny poruszania się w lewo albo prawo, zależnie kowym, jeśli tak, to ustawiamy kierunek po-
przeciwnik patrolujący obszar od znacz- od położenia hero. Przeciwnik będzie nas go- ruszania się hero zgodny z kierunkiem znacz-
nika do znacznika, utrudniając bohatero- nił tak długo, jak długo odległość między nim nika. Takie rozwiązanie daje duże możliwości
wi swobodne poruszanie się po planszy. a bohaterem będzie mniejsza od zdefiniowa- manewru, np. możemy dodać znaczniki przy-
Kolizja wroga z bohaterem odbiera temu
drugiemu energię. Wielkość traconej ener- Listing 10. Obsługa poruszania się postaci
gii zdefiniujemy stałą LOST_ENERGY wyra-
żoną w procentach i umieszczoną w pli- public void moveLeft() {
ku Config.java. Patrząc z programistycz- f_velocityX -=
nego punktu widzenia, każdy wróg bę- ((onGround ? speed : speed>>1)*timeframe)>>5;
dzie obiektem opisanej wcześniej klasy setState(MOVE_LEFT);
Character. Jak dodać nowe postacie do na- }
szej gry? W tym celu wracamy do edytora
poziomów i tworzymy drugą warstwę: bę- public void moveRight() {
dziemy nazywać ją maską (patrz: Ramka f_velocityX +=
Dodawanie warstwy w edytorze poziomów). ((onGround ? speed : speed>>1)*timeframe)>>5;
Maska musi mieć dokładnie takie same pa- setState(MOVE_RIGHT);
rametry jak mapa planszy: zarówno roz- }
miar, jak i wielkość kafelka. Rozmieścimy
na niej pozycje naszych przeciwników oraz public void jump() {
znaczniki, które będą wyznaczały grani- if(!onGround)
ce poruszania się wrogów. Do istniejącego return;
tileset'u dodajemy kilka nowych tili, które
będą symbolizowały odpowiednio: f_velocityY -= JUMP_POWER;
setState(JUMP);
• [E1]: przeciwnik typu pierwszego; }
• [E2]: przeciwnik drugiego typu;
• [strzałka w lewo]: znacznik poruszania Listing 11. Sprawdzanie kolizji z podłożem
się w lewo; public void updateCollision() {
• [strzałka w prawo]: znacznik poruszania onGround = false;
się w prawo. int px = f_characterX>>Config.FP;
int py = f_characterY>>Config.FP;
Pamiętajmy, że wszystko, co umieszczamy na
masce, nie będzie widoczne na ekranie. Teraz //bottom
pozostaje tylko umieścić nowo utworzone ti- int tx = px >> LevelLayer.TILE_SHIFT;
le maski w odpowiednich miejscach. Umie- int ty = (py+characterHeight2) >> LevelLayer.TILE_SHIFT;
ściłem dwóch wrogów na samym dole plan- int dy = (ty<<LevelLayer.TILE_SHIFT) - py -
szy oraz dwóch na platformach, po lewej i LevelLayer.TILE_SIZE;
po prawej stronie poziomu. Eksport maski
robimy w identyczny sposób jak w przypad- //omijamy efekt wciągania w góre, kolizja do polowy tila
ku pierwszej warstwy. Wygenerowaną sta- if( LevelLayer.isTileCollision(tx, ty) &&
tyczną tablicę bajtów maskLevel[] dodajemy f_velocityY>0 &&
do klasy LevelLayer i dopisujemy kilka me- dy > -(LevelLayer.TILE_SIZE>>1) )
tod do obsługi tej tablicy (patrz: Listing 13). {
Obsługę wrogów implementujemy w silniku py += dy;
gry, w klasie Game. Metody z przedrostkiem f_characterY = py << Config.FP;
enemy będą realizowały to zadanie, ich zawar- f_velocityY = 0;
tość przedstawiona jest na Listingu 14. Meto- onGround = true;
da enemyInit() tworzy obiekty wrogów oraz }
nadaje im odpowiednie pozycje na planszy }
pobrane z warstwy maski; wywoływana jest

www.sdjournal.org 163
Programowanie JavaME

spieszania, zwalniania itd. Jeśli w chwili koli- nvwert. Jest to bardzo prosty sposób na wy- mów, otwieramy naszą warstwę maski, na
zji tylko nasz bohater będzie znajdował się w konanie potrzebnej grafiki, choć jakość pozo- której rozmieszczaliśmy wrogów. Dodajemy
stanie walki (FIGHT), życie utraci przeciwnik. stawia wiele do życzenia... Postanowiłem jed- do tileset'u kolejne kafelki maski, które będą
W przypadku gdy obie postacie będą w stanie nak skupić się przede wszystkim na funkcjo- symbolizować odpowiednio:
walki, wówczas oboje tracą energie. Możemy nalności.
oczywiście dodać wiele innych, ciekawych za- • [kluczyk]: kluczyk;
chowań dla przeciwnika i wzbogacić jego in- Dodajemy bonusy i dodatki • [klepsydra]: zwiększenie licznika czasu;
teligencję w zależności od potrzeb, jednakże Nasza gra wygląda już całkiem przyzwoicie. • [serduszko]: dodatkowe życie;
w naszej grze poprzestaniemy na bardzo pro- Można poruszać się po planszy, swobodnie • [moneta]: punkty.
stych mechanizmach AI. Brakuje tylko gra- wskakiwać na platformy, a także spotkać wro-
fiki dla przeciwników. Możemy oczywiście gów. Jest to dobry moment na dołożenie bra- Elementy uzupełniające starannie rozmiesz-
przygotować takową osobiście, poprosić zna- kujących elementów, które założyliśmy so- czamy na planszy. Kiedy uznamy, iż ich
jomego grafika o pomoc lub poszukać darmo- bie wstępnie, projektując naszą grę. Mowa ilość jest wystarczająca, możemy eksporto-
wych animacji w sieci. Ja z pomocą IrfanView tu o rozmieszczeniu monet, które nasz bo- wać warstwę maski. Starą tablicę maski za-
zmienię grafikę naszego bohatera, wykorzy- hater będzie skrupulatnie zbierał. Trzeba stępujemy nową, po czym możemy bezpo-
stując funkcję Negative. W tym celu każdą też ukryć na planszy klucze, których znale- średnio korzystać z niej w kodzie. Wykrywa-
klatkę należy otworzyć w IrvanView, wybrać zienie będzie warunkiem koniecznym ukoń- nie kolizji bohatera z monetami lub bonusa-
z menu głównego IMAGE, a następnie z roz- czenia poziomu. Przyda się także dodanie bo- mi realizuje instrukcja switch(), umieszczo-
winiętego podmenu opcję NEGATIVE. Gra- nusów, pozwalających uzupełnić energię, ze- na w głównej pętli gry w klasie Game (patrz:
fikę dla drugiego przeciwnika uzyskałem na brać dodatkowe życie lub zwiększenie liczni- Listing 15). W zależności od wykrytej koli-
podobnej zasadzie, przerabiając naszego hero: ka, określającego ile czasu pozostało na ukoń- zji efekty widzimy na pasku statusu, w po-
tym razem przy pomocy opcji IMAGE > Co- czenie planszy. Wracamy do edytora pozio- staci dodatkowego serduszka, punktów czy
licznika znalezionych kluczy. Na tym etapie
Listing 12. Odtwarzanie animacji
trzeba też ustalić czas potrzebny na ukoń-
czenie planszy. Początek zwykle wiąże się
public void updateAnimation(int timeframe) { z zapoznaniem się gracza z obsługą klawi-
characterFrameTime += timeframe; szy, poruszaniem po planszy i podstawowy-
mi zasadami gry. Gracz powinien więc mieć
while(characterFrameTime > ONE_FRAME_TIME) { wystarczająco dużo czasu. Zakładamy, że
characterFrame++; 180 sekund, czyli 3 minuty, wystarczy na to
zadanie. Czas ten można wydłużyć o kolejne
if(characterFrame >= characterFrameMax) { 30 sekund, zbierając klepsydrę. Za zebranie
characterFrame = characterFrameMin; każdej monety gracz uzyskuje 100 punktów,
animLoopCount++; a za kluczyk dodatkowo 1000 punktów. Wa-
} runkiem zakończenia poziomu jest oczywi-

characterFrameTime -= ONE_FRAME_TIME;
}
}

Listing 13. Metody do obsługi maski

public static byte[][] getLevelMask() {


return maskLevel;
}

public int getMask(int x, int y) {


x = (x>>FP)>>TILE_SHIFT;
y = (y>>FP)>>TILE_SHIFT;
return maskLevel[y][x];
}

public boolean isMask(int x, int y, int type) {


x = (x>>FP)>>TILE_SHIFT;
y = (y>>FP)>>TILE_SHIFT;
return (maskLevel[y][x]==type);
}

public void setMask(int x, int y, int type) {


x = (x>>FP)>>TILE_SHIFT;
y = (y>>FP)>>TILE_SHIFT;
maskLevel[y][x] = (byte)type;
}
Rysunek 5. Screenshot z naszej gry

164 SDJ Extra 34 Biblia


Gra w Javie na komórkę

Listing 14. Metody do obsługi wrogów

private void enemyInit(int count) { case LevelLayer.MASK_ENEMY_TYPE1:


enemy = new Character[count]; int dx = (enemy[i].getX() -
count = 0; hero.getX())>>FP;
for(int y=0; y<LevelLayer.maskLevel.length; y++)
for(int x=0; x<LevelLayer.maskLevel[y].length; x++) if(Math.abs(dx) < ENEMY_DETECTION &&
{ Math.abs(dx) > 0)
if( LevelLayer.maskLevel[y][x] == {
LevelLayer.MASK_ENEMY_TYPE1 ) if(dx > 0)
{ enemy[i].moveLeft();
enemy[count] = new Character( else
x<<LevelLayer.TILE_SHIFT, enemy[i].moveRight();
y<<LevelLayer.TILE_SHIFT, }
ENEMY_WIDTH, enemyCheckCollision(i);
ENEMY_HEIGHT, break;
Res.IMG_ENEMY1_STAY_LEFT, case LevelLayer.MASK_ENEMY_TYPE2:
LevelLayer.maskLevel[y][x]); if(enemy[i].getSideLeft())
enemy[count].setSpeed(ENEMY_SPEED); enemy[i].moveLeft();
count++; else
} enemy[i].moveRight();
if( LevelLayer.maskLevel[y][x] == if( layer.isMask(enemy[i].getX(),
LevelLayer.MASK_ENEMY_TYPE2 ) enemy[i].getY(),
{ LevelLayer.MASK_RIGHT))
enemy[count] = new Character( enemy[i].setSideLeft(false);
x<<LevelLayer.TILE_SHIFT, if(layer.isMask(enemy[i].getX(),
y<<LevelLayer.TILE_SHIFT, enemy[i].getY(),
ENEMY_WIDTH, LevelLayer.MASK_LEFT))
ENEMY_HEIGHT, enemy[i].setSideLeft(true);
Res.IMG_ENEMY2_STAY_LEFT, enemyCheckCollision(i);
LevelLayer.maskLevel[y][x]); break;
enemy[count].setSpeed(ENEMY_SPEED); }
count++; }
} }
} private void enemyCheckCollision(int i) {
} if(!enemy[i].isDied() &&
private void enemyRestore(boolean forceLife) { !hero.isHurt() &&
for(int y=0, count =0; y<LevelLayer.maskLevel.length; y++) hero.isCharacterCollision(
for(int x=0; x<LevelLayer.maskLevel[y].length; x++) enemy[i].getX(), enemy[i].getY()))
if( LevelLayer.maskLevel[y][x] == {
LevelLayer.MASK_ENEMY_TYPE1 || heroEnergy -= LOST_ENERGY;
LevelLayer.maskLevel[y][x] == if(heroEnergy < 0) {
LevelLayer.MASK_ENEMY_TYPE2 ) heroLife--;
{ hero.setDie();
if(forceLife) if(heroLife != 0)
enemy[count].setLife(); heroEnergy = 100;
}
if(!enemy[count].isDied()) if(hero.getState() == Character.FIGHT)
enemy[count].setPos( enemy[i].setDie();
x<<LevelLayer.TILE_SHIFT, }
y<<LevelLayer.TILE_SHIFT); }
count++; private void enemyDraw(Graphics g) {
} if(enemy == null)
} return;
private void enemyUpdata(int time) { for(int i=0; i<enemy.length; i++)
if(enemy == null) if(enemy[i] != null)
return; enemy[i].draw(g);
for(int i=0; i<enemy.length; i++) }
if(enemy[i] != null) {
enemy[i].update(time);

switch(enemy[i].getType())
{

www.sdjournal.org 165
Programowanie JavaME

ście zebranie wszystkich kluczy. Wszystkie


Listing 15. Główna pętla gry te parametry definiujemy w pliku Config.ja-
public void gameLogic(int time) { va. Wielkimi krokami zbliżamy się do koń-
if(gameOver || levelCompleted) ca naszej przygody z programowaniem gry
return; na komórkę.
heroTime -= time;
if(heroTime < 0) { Brakujące elementy i testy
gameOver = true; Musimy jeszcze raz wrócić na chwilę do edy-
heroTime = 0; tora planszy i dodać znacznik pozycji starto-
} wej bohatera, dorobimy symbol zielonego
if(direction>0 && gametime-pressTime > 10) okręgu i umieścimy na masce. Potrzebujemy
move(direction); jeszcze graficzny interfejs użytkownika (ang.
enemyUpdata(time); GUI) w postaci paska statusu, na którym wy-
if(hero != null) { świetlone zostaną punkty, czas oraz ilość ze-
if(hero.isDied()) branych kluczy. W tym celu użyjemy grafi-
if(heroLife == 0) ki z planszy. Ważne, aby wszystko było czy-
gameOver = true; telne dla gracza. Na potrzeby naszego projek-
else { tu, do wyświetlania napisów i wartości uży-
hero.setPos(startX, startY); jemy czcionek systemowych. Czytelników,
enemyRestore(false); którzy chcieliby poeksperymentować, zachę-
hero.setLife(); cam do implementacji własnej klasy obsługu-
} jącej czcionki bitmapowe. Systemowa meto-
layer.setPos(hero.getX()>>FP, hero.getY()>>FP); da drawString() na komórkach działa bar-
hero.update(time); dzo wolno, a co gorsza, na każdym telefo-
switch(layer.getMask(hero.getX(), hero.getY())) { nie czcionki mogą się znacząco różnić. Do-
case LevelLayer.MASK_COIN: robimy jeszcze dwukolorowy pasek energii.
heroScore += COIN_PTS; Za pomocą boolowskiej zmiennej gameOver
layer.setMask(hero.getX(), hero.getY(), będziemy określać stan zakończenia gry. W
LevelLayer.MASK_EMPTY); momencie, w którym zabraknie czasu na ze-
break; branie wszystkich kluczy bądź gdy stracimy
case LevelLayer.MASK_HEART: wszystkie życia, zmienna ta przyjmie war-
heroLife++; tość true. Kiedy zbierzemy wszystkie klu-
layer.setMask(hero.getX(), hero.getY(), cze, inna zmienna: levelCompleted, przyj-
LevelLayer.MASK_EMPTY); muje wartość true, zaś na ekranie pojawia
break; się komunikat Level Completed. Sterowanie
case LevelLayer.MASK_KEY: naszym bohaterem jest możliwe za pomo-
heroKeys++; cą d-pada/joysticka, jednak z uwagi na specy-
heroScore += KEY_PTS; fikę platformy niektórzy gracze używają kla-
levelCompleted = wiatury numerycznej – dlatego też gra obsłu-
(heroKeys == keyMax) ? true : false; guje również klawisze 2, 4, 5, 6, oraz 8, jako
layer.setMask(hero.getX(), hero.getY(), alternatywną wersję kontrolera. Główna kla-
LevelLayer.MASK_EMPTY); sa gry Game dziedziczy po klasie Canvas, w
break; której jest kilka metod do obsługi zdarzeń ta-
case LevelLayer.MASK_TIME: kich jak:
heroTime += EXTRA_TIME*1000;
layer.setMask(hero.getX(), hero.getY(), • showNotify(),
LevelLayer.MASK_EMPTY); • hideNotify(),
break; • pointerPressed(),
} • pointerDragged(),
} • pointerReleased(),
} • keyPressed(),
• keyReleased().

W Sieci
• http://java.sun.com/javase – strona Sun'a, gdzie można pobrać Java SDK;
• www.eclipse.org/ – stona z IDE Eclipse'a;
• http://www.netbeans.org/downloads/index.html – strona IDE NetBeans'a 6.5.1, wersja dla Javy waży ponad 200 Mb, ale zawiera już wbudo-
wany plugin Java ME;
• http://developer.sonyericsson.com/ – tutaj można pobrać emulator Sonny Ericsson'a;
• http://www.tilemap.co.uk – Mappy Win32, prosty program do tworzenia poziomów;
• http://tilestudio.sourceforge.net – TileStudio, alternatywny program do tworzenia poziomów;
• http://www.irfanview.com/ – program do przeglądania i podstawowej obróbki grafiki;
• http://www.spicypixel.net/category/downloads/ – autor strony udostępnia darmowe grafiki do wykorzystania w grach.

166 SDJ Extra 34 Biblia


Gra w Javie na komórkę

Uzupełniamy tylko ciało dwóch ostatnich


Listing 16. Obsługa klawiszy metod (patrz: Listing 16). Nasza gra nada-
protected synchronized void keyPressed(int keyCode) { je się do tego, aby wgrać ją na telefon i dać
bratu lub siostrze do zabawy (patrz: ram-
int key = 0; ka Wgrywanie aplikacji na telefon). Uwagi
try { użytkowników na tym etapie produkcji gry
key = getGameAction(keyCode); są niezwykle cenne i pomagają dobierać pa-
} catch (Exception e) {} rametry silnika, tak aby nasza platformów-
ka była jak najbardziej grywalna. Jako uko-
if( key == Canvas.LEFT || ronowanie naszych wysiłków dodamy jesz-
keyCode == KEY_NUM4 ) { cze ikonkę gry, która będzie widoczna w me-
direction |= KEY_LEFT; nu telefonu. W tym celu wystarczy przygo-
pressTime = gametime; tować plik graficzny w formacie PNG i roz-
move(direction); miarze 24x24 pikseli, umieścić go w naszych
} zasobach (np. w katalogu hero) oraz nazwać
else go icon.png.
if( key == Canvas.RIGHT || W tym momencie możemy uznać proto-
keyCode == KEY_NUM6 ) { typ naszej klasycznej platformówki za ukoń-
direction |= KEY_RIGHT; czony!
pressTime = gametime;
move(direction); Podsumowanie
} W powyższym artykule przedstawiłem bar-
dzo podstawowy mechanizm powstawania
if( key == Canvas.UP || gry w JME. Zaprezentowałem fundamen-
keyCode == KEY_NUM2 ) { talne aspekty i etapy takiego przedsięwzię-
direction |= KEY_TOP; cia: dobieranie narzędzi, tworzenie fabuły,
pressTime = gametime; planowanie architektury, pisanie kodu. Na-
move(direction); sza gra nie posiada specjalnych efektów, któ-
} re przede wszystkim nadają ton grze. Skupi-
łem się głównie na mechanice i funkcjonal-
if( key == Canvas.FIRE || ności. Kolejnym etapem produkcji powinno
keyCode == KEY_NUM5 ) być dopracowanie grywalności i wzbogace-
hero.fight(); nie o efekty.
Można przede wszystkim dorobić ani-
if( keyCode == KEY_RSK ) macje zbieranych monet, kluczy. Punkty za
midlet.notifyDestroyed(); zebrane monety powinny się pojawiać nad
monetą. Przy starcie planszy można ją za-
if( keyCode == KEY_LSK ) prezentować w całości, przewijając od koń-
if(gameOver || levelCompleted) { ca do punktu startu. Za zakończenie pozio-
enemyRestore(true); mu przed czasem naliczać dodatkowe punk-
restoreGame(); ty, oczywiście więcej wrogów z bardziej roz-
} winiętą inteligencją oraz wiele innych efek-
} tów i ulepszeń.

protected synchronized void keyReleased(int keyCode) {


int key = 0;

try { CEZARIUSZ KLONKOWSKI


key = getGameAction(keyCode); Pracuje na stanowisku Specjalista ds. Progra-
} catch (Exception e) {} mowania Gier Java w firmie Gamelion, wcho-
dzącej w skład Grupy BLStream. Cezariusz ge-
if( key == Canvas.UP || neralnie specjalizuje się w technologiach
keyCode == KEY_NUM2 ) związanych z programowaniem gier, w szcze-
direction &= (0xFF^KEY_TOP); gólności interesują go platformy mobilne. W
else branży pracuje około 6 lat z dorobkiem ponad
if( key == Canvas.LEFT || 50 wydanych gier. Grupa BLStream powsta-
keyCode == KEY_NUM4 ) ła, by efektywniej wykorzystywać potencjał
direction &= (0xFF^KEY_LEFT); dwóch szybko rozwijających się producentów
else oprogramowania – BLStream i Gamelion. Fir-
if( key == Canvas.RIGHT || my wchodzące w skład grupy specjalizują się
keyCode == KEY_NUM6 ) w wytwarzaniu oprogramowania dla klientów
direction &= (0xFF^KEY_RIGHT); korporacyjnych, w rozwiązaniach mobilnych
} oraz produkcji i testowaniu gier.
Kontakt z autorem: cklonkowski@gmail.com

www.sdjournal.org 167
Programowanie JavaME

Testowanie aplikacji
na platformie J2ME
Zbieranie informacji debugowych w ograniczonym
środowisku uruchomieniowym

tecznej weryfikacji. To właśnie ta grupa odbior-


Dowiesz się: Powinieneś wiedzieć ców, z racji oddalenia od zespołu i narzędzi pro-
• jakie funkcję pełnia systemy zbierania da- • czym jest środowisko J2ME gramistycznych, jest głównym adresatem syste-
nych debugowych • czym są zagadnienia związane z testowaniem mów zbierania danych debugowych, o których
• jakie ograniczenia muszą spełniać takie sys- • jak pisać i uruchamiać aplikacje wielowątko- traktuje ta praca.
temy w środowisku J2ME we
• w jaki sposób najbardziej optymalnie wyko- Zasady działania
rzystać dosępne zasoby i ograniczenia systemu
Beta Tester zazwyczaj nie działa według za-
projektowanego scenariusza, działając w spo-
% modeli telefonów komórkowych używanych sób zbliżony do działania zwykłego użytkow-
w Polsce, należałoby dostarczyć midlet wspie- nika. Jeśli podczas obsługi aplikacji napotka
Poziom trudności rany przez 154 modele telefonów. Wyróżnić na zachowanie, które według niego odbiega
można atrybuty takie jak wielkość ekranu, do- od oczekiwanego, ma możliwość zgłoszenia
stępna wielkość pamięci czy szybkość połącze- tego spostrzeżenia, wraz ze specyficznym lo-
nia, które można z powodzeniem zweryfikować giem aplikacji. Log jest zapisem obserwacji
Wstęp za pomocą symulatora programowego. Niestety działającej aplikacji. W najbardziej zaawanso-
Rosnąca popularność telefonów komórkowych, pozostaje spora ilość zależności, które wynikają wanych rozwiązaniach zbiera dane z pozio-
jako skutecznej platformy aplikacyjnej, wią- chociażby z innego stosu protokołów czy imple- mu wywołań funkcji, zależnie od poziomu
że się nierozerwalnie z pojawieniem się oraz mentacji maszyny wirtualnej, objawiające się śledzenia aplikacji - wybranych lub wszyst-
rozwojem specjalnej wersji Java dla tych urzą- w najbardziej niespodziewanych obszarach ko- kich. Celem tego typu rozwiązań jest umoż-
dzeń. Jest ona znana pod nazwą J2ME lub Java du i tylko wyłącznie, gdy pracuje na rzeczywi- liwienie programistom prześledzenie i ewen-
ME (Java for Mobile Edition) i stanowi podzbiór stym fizycznym urządzeniu. Aby zminimalizo- tualne odtworzenie scenariusza powodujące-
funkcjonalności zdefiniowanych w Java 2 Plat- wać możliwość wystąpienia tego typu błędów, go sytuację błędną w działaniu aplikacji. Jest
form, umożliwiając tworzenie aplikacji na sze- przed rozpowszechnieniem aplikacji dokonu- to kluczowe zwłaszcza w przypadku analizy
roki wachlarz terminali. W rzeczywistości pod- je się jego weryfikacji na docelowych urządze- problemów wielowątkowych. W tym celu czę-
zbiór ten jest dodatkowo definiowany za pomo- niach. Przed twórcami aplikacji stoi zatem ko- sto nie wystarczają takie informacje jak kolej-
cą określonych profili (CLDC/CDC/MIDP) i nieczność ręcznego uruchomienia i przetesto- ność otwieranych okien aplikacji, niezbędne
opcjonalnych wspieranych rozszerzeń, które to wania midletów na całkiem pokaźnej grupie jest odnotowanie zdarzeń w sensie programi-
ostatecznie określają finalny dostępny interfejs telefonów. Praktyka testowania modelu refe- stycznym, ze szczególną uwagą zwróconą na
API. Tak rozbudowana budowa platformy mo- rencyjnego dla platformy jest skutecznym spo- ich kolejności. Tego typu rozwiązania charak-
bilnej jest konsekwencją różnorodności wspie- sobem na zmniejszenie ich liczby. Przykłado- teryzują się pewnymi wspólnymi cechami nie-
ranych urządzeń, na których to aplikacje, zwane wo, według Java Verified Program, weryfikacja zależnymi od specyfiki obserwowanego syste-
midletami, powinny działać identycznie. Towa- midletu na telefonie Motorola V600 jest rów- mu. Najważniejszą z nich jest brak interakcji z
rzysząca twórcom zasada: „programuj raz, uru- noznaczna z przetestowaniem modeli V500, obserwowanym obiektem. Oczywiście jedynie
chamiaj wszędzie” jest oczywiście ograniczona V525 oraz V300. Można tego typu badanie rozwiązania wykorzystujące protokoły takie
granicami zdrowego rozsądku i wymaga uru- zlecić zewnętrznej firmie, na przykład w ra- jak JTAG i zewnętrzne analizatory mogą za-
chomienia i przetestowania aplikacji na danym mach certyfikacji JVP, w rzeczywistości jed- gwarantować pełną hermetyzację środowiska
urządzeniu. Zazwyczaj aby osiągnąć sukces ko- nak rzadko kiedy z powodu sporych kosztów pomiarowego od obserwowanego. Wszystkie
mercyjny niezbędne jest wspieranie przez do- pokrywa się nimi nawet wiodące na rynku plat- systemy uruchamiane na tym samym fizycz-
starczone aplikacje znaczącego procentu obec- formy, a co dopiero poszczególne modele telefo- nym urządzeniu, w tym także rozwiązania tu
nych na rynku terminali. Aby zobrazować skalę nów. Dużo bardziej popularne jest wykorzysty- przedstawiane, obciążone są pewną ilością ge-
problemu, przytoczono dane z listopada 2008 wanie własnej bazy rozproszonych testerów i te- nerowanych zakłóceń wynikających z takich
r. za spółką GetJar, według których aby objąć 90 stowanie wersji BETA midletu w ramach osta- czynników jak:

168 SDJ Extra 34 Biblia


Testowanie aplikacji

• dodatkowe obciążenie CPU; Kolejną niezwykle istotną cechą systemu jest Do najczęściej ewidencjonowanych atrybutów
• dodatkowe zużycie pamięci; gwarancja zachowania kolejności ewidencjono- należą: znacznik czasu (timestamp), typ zdarze-
• wykorzystanie innych zasobów systemo- wanych zdarzeń. Stanowi to dodatkowe wy- nia (np. Event, Exception, Assert), aktywny wą-
wych, takich jak semafory, uchwyty do zwanie w świetle założeń przyjętych powyżej, a tek (priorytet, nazwa). Bardzo często dołącza
plików itp. więc unikania klasycznych mechanizmów syn- się również dane specyficzne dla danego typu
chronizujących. Od architekta systemu zależy, zdarzenia w różnej postaci (np. predefiniowa-
W przypadku pamięci oraz procesora zakłóce- którą z możliwych opcji zastosuje: nych ciągów znaków, zrzutów pamięci, drze-
nia w pracy aplikacji łatwo staną się pomijalne wa egzekucji itp). Zazwyczaj są to dane zbie-
pod warunkiem dysponowania rezerwą tych • wykorzystanie mechanizmu synchroniza- rane opcjonalnie na odpowiednim poziomie
zasobów oraz odpowiednią optymalizacją ko- cyjnego – gwarantowana poprawna kolej- zbierania danych.
du obserwatorów. Niestety w przypadku pozo- ność zachowania zdarzeń w logu kosztem Ilość danych zbieranych przy rejestracji poje-
stałych zasobów systemowych, zwłaszcza gdy potencjalnego wpływu na działanie syste- dynczego zdarzenia ma istotny wpływ na suma-
realizują również funkcje synchronizacyjne, mu obserwowanego; ryczną wielkość logu. Ilość dostępnej pamięci,
sytuacja jest dużo bardziej złożona. Generalnie • ustalenie kolejności zdarzeń na podstawie przeznaczonej na przechowywanie danych de-
zalecane jest, aby unikać lub przynajmniej mi- wartości znacznika czasu lub pobranego bugowych, ograniczając wielkość logu, określa
nimalizować wykorzystanie tych elementów indeksu bez synchronizacji – nieskutecz- również przy założonej wielkości pojedyncze-
w przypadku tego rodzaju systemów. W prze- na w przypadku wydziedziczeń pomiędzy go zgłoszenia maksymalny horyzont czasowy
ciwnym razie możliwe jest doprowadzenie do wystąpieniem zdarzenia a pobraniem war- dla zbieranych danych. Najbardziej popularne
dwóch wersji interakcji patologicznych: tości znacznika lub indeksu; implementacje wykorzystują mechanizm bufo-
• zamknięcie zapisu w postaci atomowych ra cyklicznego (cycle-buffer), który w przypadku
• problem przestaje występować w chwili transakcji z weryfikacją stanu po zakoń- wypełnienia całości dostępnego miejsca nadpi-
włączenia instrumentów obserwacyjnych, czeniu – skutecznie potrafi wykryć kon- suje najstarsze dodane dane. Poważnym manka-
na przykład z powodu zmiany kolejności flikt dostępu, jednak nie w każdej sytuacji mentem tego podejścia jest możliwość nadpisa-
wywołań spowodowanej czekaniem na se- pomoże odnaleźć właściwą kolejność; nia danych istotnych z punktu widzenia obser-
mafor systemowy; • rozdzielenie kontenerów rejestrujących wowanej anomalii poprzez dane mniej ważne,
• problem występuje wyłącznie w przypad- zdarzenia pomiędzy poszczególnymi wąt- będące wynikiem ciągłego logowania zdarzeń.
ku włączenia instrumentów obserwacyj- kami – brak konfliktu dostępu do zaso- Aby minimalizować wpływ tej właściwości na
nych, które powodują przykładowo w da- bów okupiony jest ceną braku synchroni- przydatność systemu, stosuje się odpowied-
nym przypadku deadlock'a. zacji pomiędzy poszczególnymi zapisami nio duże wielkości logów zdarzeń, co jednak w
zdarzeń. przypadku systemów wbudowanych i ograni-
Należy pamiętać o tego typu właściwościach czonych stanowi rozwiązanie nieakceptowalne.
systemu, szczególnie jeśli w trakcie reproduk- Pomimo różnych zaprezentowanych podejść O ile opisywane powyżej zagadnienia doty-
cji zdarzenia pojawiają się problemy. do fizycznego zbierania danych, ich zewnętrz- czą sposobu działania systemu, nie mniej istot-
na struktura powinna pozostawać w większo- na jest integracja systemu zbierania danych de-
ści wypadków spójna. Określenie logicznej bugowych na poziomie kodu aplikacji. Użycie
struktury logu zdarzeń sprowadza się do okre- takich konstrukcji językowych, które umożli-
ślenia atrybutów widocznych na liście zda- wiają łatwą i praktycznie przeźroczystą inte-
rzeń, gdzie kolejność wynika z ich chronologii. grację z obserwowanym systemem, jest w wielu

Rysunek 1. Porównanie działania klasycznego bufora cyklicznego z rozwiązaniem wykorzystującym tryb safe-window

www.sdjournal.org 169
Programowanie JavaME

wypadkach nieosiągalne. Dobrym przykładem wym ograniczeniem. Dodatkowo niezwykle rzyć, iż zdarzenia krytyczne powodujące prze-
mogą być tu usługi zbierania danych w rozwią- istotnym ograniczeniem jest konieczność pre- niesienie zapisu do kolejnego bufora wystąpią
zaniach J2EE oparte o komponent Log4j. Nie- alokowania pamięci dla bufora danych debu- jeden po drugim. Jakkolwiek sytuacja taka nie
stety, bazuje on między innymi na mechaniź- gowych. Jest to podyktowane zarówno wzglę- jest rzadkością, to sposobem na optymalne wy-
mie refleksji, który nie jest dostępny na plat- dami wydajnościowymi (brak alokacji pamię- korzystanie dostępnej pamięci jest wykorzy-
formie J2ME. W przypadku tej drugiej wyma- ci przy logowaniu zdarzenia), jak i bezpieczeń- stanie mechanizmu safe-window. Polega on na
gane będzie, aby programista fizycznie umie- stwa (system w sytuacjach krytycznych może umieszczeniu w przestrzeni kontenera danych
ścił wywołania do systemu w obrębie obser- nie mieć możliwości zaalokowania pamięci). okna, w obrębie którego zdarzenia zapisywane
wowanej aplikacji. Istotna jest możliwość para- Wpływ na wymaganą wielkość mają zarówno są w sposób analogiczny jak w klasycznym bu-
metryzowania wywołań systemu – na przykład wielkość pojedynczego rekordu, jak i ich suma- forze cyklicznym. Pojawienie się zdarzenia kry-
poprzez ustalenie poziomu zdarzeń lub defi- ryczna ilość. Wybory, przed jakimi staje projek- tycznego powoduje przeniesienie bezpieczne-
nicje ich klas. Zaproponowany interfejs powi- tant systemu to zatem optymalizacja wyko- go okna na kolejne rekordy w buforze danych.
nien charakteryzować się również skondenso- rzystania dostępnej pamięci poprzez taki do- Jeśli bezpieczne okno zostało wykorzystane w
waniem zapisu, poprzez użycie odpowiednich bór zbieranych atrybutów oraz wyboru do za- całości, będzie to indeks większy o maksymal-
wzorców. Dzięki temu dodawanie wywołań sys- pisu odpowiednich rekordów, które w konse- ny rozmiar okna. W tym przypadku rozwiąza-
temu debugowego do tworzonego kodu może kwencji nieść będą najwięcej informacji o zda- nie safe-window będzie tożsame z kilkoma bu-
stać się czynnością prawie automatyczną. We- rzeniach w okolicy incydentu. Pomocne przy forami cyklicznymi. Różnica ujawnia się w mo-
dług autora całość pojedynczej interakcji z syste- rozwiązaniu tego problemu może być odwró- mencie, gdy kolejne zdarzenia krytyczne wy-
mem debugowym (na przykład zgłoszenie zda- cenie pytania: Które dane powinniśmy zbierać?, stąpią w odległości mniejszej niż maksymalny
rzenia na poziomie critical będące efektem wy- stawiając w jego miejsce: Bez których danych mo- rozmiar okna. Spowoduje to utworzenie bez-
stąpienia wyjątku) nie powinna zajmować wię- żemy sobie poradzić?. Zakładając wyeliminowa- piecznych okien o wielkości mniejszej niż mak-
cej niż pojedynczą linijkę kodu. nie redundancji danych na poziomie pojedyn- symalne, nie pozostawiając zaalokowanej i nie-
Zebrane dane staną się cennym wkładem czego rekordu, pozostaje zatem wybranie z ca- wykorzystanej pamięci kontenera.
w rozwój aplikacji jedynie w przypadku, gdy łego zbioru rekordów tych, które wniosą naj-
trafią do osób odpowiedzialnych za ich anali- większą wartość w procesie analizy i repro- Podsumowanie
zę. System dystrybucyjny danych debugowych dukcji problemu. W tym celu wprowadzenie Proponowane rozwiązania wykorzystania pa-
oraz szerzej zbierania i reagowania na zgłoszenia poziomu zdarzeń jest najbardziej naturalnym mięci zostały wykorzystane w rzeczywistych
anomalii jest osobnym zagadnieniem w stosun- sposobem na rozwiązanie zagadnienia. Każ- aplikacjach. W celu zbudowania komplekso-
ku do tematu tego opracowania. Pomimo tego dy rekord w logu charakteryzować się będzie wego systemu zbierania danych debugowych
warto jedynie zaznaczyć, że może być to obszar wagą, czyli istotnością dla analizatora. Typo- niezbędne jest rozwiązanie pozostałych wspo-
kluczowy dla sukcesu funkcjonowania opisy- we zdarzenia takie jak przejścia po domyślnym mnianych problemów, takich jak: dystrybucja
wanego systemu. Przedstawiając zarys rozwią- drzewie sterowania zazwyczaj otrzymywać bę- danych, integracja z produktem czy minimali-
zania, warto wspomnieć o takich aspektach jak: dą niski poziom istotności, w przeciwieństwie zowanie wpływu na wydajność i wykorzystanie
do wszystkich zdarzeń niespodziewanych. Na mechanizmów synchronizacyjnych oraz sku-
• łatwość i ergonomia dostarczania danych etapie implementacji bądź kolekcjonowania teczna serializacja danych. Z powodu znacznej
– minimalna ingerencja w proces wysy- danych określa się poziom dokładności zbiera- objętości podjętych tematów nie jest możliwe
łania zdarzenia ze strony testera, intuicyj- nych logów, co implikuje liczbę gromadzonych przedstawienie całości rozwiązań w tej pracy. Z
ny interfejs umożliwiający bezpośrednio danych. Aby wypełnić zobowiązanie co do za- całą pewnością ważne jest również wykorzysta-
po wystąpieniu anomalii na przesłanie da- jętości pamięci, wykorzystać można konstruk- nie nowych możliwości, jakie pojawiają się wraz
nych do centrum analizy; cje bufora cyklicznego, nadpisując dane, które z rozwojem platformy J2ME, takich jak rozsze-
• możliwość ewidencjonowania zgłoszeń da- pozostają w logu najdłużej. Z punktu widze- rzenie JSR 190 – Event tracking API, które w
nego testera, stanowiący czynnik motywu- nia analizy może się jednak okazać, że w wie- znacznym stopniu może rozwiązać problem
jący do dalszego testowania aplikacji; lu przypadkach albo liczba gromadzonych da- dystrybucji danych czy zewnętrznych bibliotek
• istnienie zwrotnego kanału komunikacyj- nych była tak duża (wysoka dokładność logo- wspomagających logowanie. Niestety platforma
nego, pozwalającego na doprecyzowanie wania), że w buforze nadpisane zostały istotne mobilna nie doczekała się jeszcze systemów na
zgłoszenia w przypadku problemów z jego dane z początku występowania problemu, albo tyle stabilncyh i uznanych jak Log4J na platfor-
reprodukcją. ilość danych jest zbyt mała (niska dokładność mie J2EE, stąd istnieje konieczność implemen-
logowania), aby dobrze odczytać tło procesu, tacji takich rozwiązań samodzielnie. Jest to bo-
Specyfika systemu wykorzystania który miał miejsce. Kolejnym udogodnieniem wiem jedno z kluczowych narzędzi umożliwia-
pamięci dla ograniczonego środo- może być stworzenie kilku równoległych bu- jących zapewnienie wsparcia midletu na wielu
wiska uruchomieniowego forów cyklicznych obsługiwanych na zasadzie platformach moblinych bez względu na pro-
Ograniczenia platformy uruchomieniowej ma- cyklicznego kontenera. Pojawienie się w bufo- ducenta czy model terminala, co jest obecnie
ją istotny wpływ na wybór architektury oraz rze zdarzenia krytycznego powodowałoby, iż głównym wymaganiem biznesowym stawia-
implementacji systemu. Obszary najbardziej kolejne zdarzenia trafiałyby do następnego bu- nym przed tego typu aplikacjami.
kluczowych ograniczeń wymagają dodatkowe- fora. Dopiero gdy wszystkie bufory byłyby za-
go omówienia proponowanych rozwiązań. Na- jęte, nastąpiłoby nadpisanie pierwszego z nich. MARCIN MACIEJEWSKI
leżą do nich: W ten sposób zamiast jednego bufora o wielko- Fascynat technologii mobilnych, realizujacy się od po-
ści N rekordów, powstałyby M buforów, z któ- nad 7 lat w rozwoju aplikacji dedykowanych dla sys-
• zajętość pamięci; rych każdy pomieściłby N/M rekordów. Pomi- temów komunikacyjnych (GSM, CDMA, TETRA) wy-
• moc obliczeniowa; mo iż takie rozwiązanie wydaje się dużym udo- korzystując różne platformy oraz technolgie (J2ME,
• redystrybucja/konfiguracja. godnieniem w stosunku do klasycznego bu- ANDROID, Web Applications). Obecnie pracuje nad
fora cyklicznego, nie jest to jeszcze rozwiąza- rozwojem aplikacji WEB/WAP dla systemów Tetra.
Zajętość pamięci jest w przypadku aplikacji nie optymalne. Natura obserwowanych zda- Kontakt z autorem:
zbierającej dane debugowe najbardziej kluczo- rzeń jest stochastyczna, dlatego może się zda- marcin.maciejewski@motorola.com

170 SDJ Extra 34 Biblia


Rozwiązania mobilne

Lotus Notes Traveler


W tym roku, zgodnie z danymi IBM, po raz pierwszy w historii, więcej ludzi na
świecie będzie miało telefon komórkowy niż stacjonarny. Wiele produktów
IBM jest dostępnych na platformach mobilnych. Ostatnim ważnym
wydarzeniem w tym zakresie było wprowadzenie oprogramowania Lotus
Notes Domino, czyli pakietu IBM do pracy grupowej na iPhone.
towej na serwerze bez konieczności wy-
Dowiesz się: Powinieneś wiedzieć: konania manualnej synchronizacji (po-
• Jak włączyć obsługę podstawowych aplika- • Jak zarządzać serwerem Lotus Domino (pod- przez dzwonek, wibracje itp.– w zależno-
cji Lotus w urządzeniach mobillnych; stawy); ści od indywidualnych ustawień użytkow-
• Jak zainstalować i skonfigurować Lotus No- • Jak wykorzystywać HTML (podstawy). nika). Oprócz wspomnianych usług, Tra-
tes Travelera; veler zapewnia obustronną synchroniza-
• Jak uruchomić i zarządzać usługą na serwe- cję z czynnościami do wykonania (to-do),
rze i kliencie. centralną książką adresową oraz dzienni-
kiem. Jeśli w danej chwili sieć jest niedo-
stępna, wiadomość lub wpis do kalenda-
jącym jednocześnie bezpieczną komunika- rza zostaną zapisane na urządzeniu prze-
cję, autoryzację, kompresję danych itd. nośnym oraz przesłane do serwera Domi-
Poziom trudności Począwszy od wersji Lotus Notes Do- no przy najbliższej okazji wykrycia połą-
mino 8.0.1, dostępnej w Polsce od po- czenia sieciowego.
nad roku, użytkownicy tego oprogramo- Aplikacja działa jako zadanie na serwe-
wania wraz z zakupem licencji użytkow- rze Lotus Domino, użytkownik pobiera ze

W
raz z rozwojem wszelkiej maści nika klienta Lotus Notes nabywają pra- strony serwera pakiet instalacyjny klienta,
urządzeń przenośnych, jak te- wo do korzystania z aplikacji Lotus No- który po uruchomieniu i podaniu danych
lefony komórkowe, ipody itp., tes Traveler, służącej do obsługi urządzeń dostępowych umożliwia od razu korzysta-
pojawiła się potrzeba korzystania z aplika- mobilnych. Najnowsza wersja oprogramo- nie z oprogramowania. Administrator mo-
cji pracy grupowej również na tych urzą- wania – 8.5, dostępna od stycznia bieżą- że kontrolować działanie poprzez zarzą-
dzeniach. cego roku, umożliwia współpracę z syste- dzanie dedykowanymi politykami na ser-
Początkowo była to głównie poczta, póź- mem operacyjnym Windows Mobile 5 i 6 werze oraz bezpośrednie wydawanie ko-
niej kalendarz, a ostatnio coraz więcej apli- oraz Symbian S60 (3rd edition oraz featu- mend z konsoli serwera.
kacji dostępnych w tradycyjnym środowi- re pack 1 i 2). Aby zwiększyć poziom bezpieczeństwa,
sku typu klient- serwer może być urucha- Wystarcza to w zupełności do zaspoko- można skonfigurować połączenie szyfro-
miana nie tylko w przeglądarce na kompu- jenia potrzeb wynikających z posiadania wane przy użyciu protokołu SSL pomię-
terze stacjonarnym, ale również na drob- przez firmę telefonów komórkowych, ipo- dzy urządzeniem mobilnym a serwerem.
nych urządzeniach przenośnych. Do ich dów itp. urządzeń większości popularnych Możliwe jest również wykorzystanie posia-
graficznej i logicznej prezentacji wykorzy- producentów oraz modeli. danego oprogramowania VPN (virtual pri-
stuje się najczęściej wbudowaną mobilną Aplikacja Lotus Notes Traveler wyko- vate network). Traveler współpracuje prak-
przeglądarkę, co nie jest zawsze wygodne, rzystuje do synchronizacji pomiędzy ser- tycznie z każdym rodzajem VPNu oraz po-
wydajne oraz bezpieczne. Także od stro- werem a klientem sieć GSM lub GPRS siada wbudowaną integrację z Lotus Mobi-
ny deweloperskiej przygotowanie aplika- (General Packet Radio Service) oraz WiFi le Connect, która umożliwia samodzielne
cji pod obie wersje – przeglądarki stacjo- przy użyciu standardu 802.11x. Synchro- zestawienie prywatnej sieci wirtualnej w
narnej, korzystającej zazwyczaj z szybsze- nizacja danych odbywa się w obie stro- modelu klient- serwer oraz serwer- usłu-
go i tańszego łącza – oraz mobilnej na urzą- ny, tak więc wszelkie zmiany w kalenda- ga WWW (szczególnie przydatne przy lo-
dzeniu przenośnym- nie jest zadaniem pro- rzu czy skrzynce nadawczej w urządze- gowaniu do usług np. w kafejce interne-
stym. niu przenośnym mogą być przeniesione towej).
Dodatkowe wymogi bezpieczeństwa nie do skrzynki pocztowej i kalendarza na ser- Użytkowanie oraz obsługa aplikacji jest
ułatwiają tego procesu. Stąd też pojawiają werze pocztowym. Powiadamianie o no- bardzo łatwa. Aplikacja posiada intuicyj-
się programy, które uruchamiane na urzą- wej wiadomości odbywa się na zasadzie ny oraz funkcjonalny wygląd umożliwiają-
dzeniu przenośnym są dedykowanym do push, czyli urządzenie mobilne powiada- cy szybką i prostą współpracę z oprogramo-
wybranych usług rozwiązaniem, zapewnia- mia właściciela o nowej wiadomości pocz- waniem bez wprowadzania rewolucyjnych

172 SDJ Extra 34 Biblia


Lotus Notes Traveler

zmian w urządzeniu użytkownika oraz je- maga serwera Domino 8.5.x, jednak obsłu- dwa porty – HTTP (80) oraz do synchro-
go dotychczasowym menu obsługi. guje skrzynki pocztowe na serwerach Lo- nizacji danych (8642).
tus Domino 7.0.2 lub nowszych. Możliwe Jeśli aplikacja będzie instalowana na
Instalacja jest więc dostawienie serwera Domino 8.5 nowym serwerze Domino, należy spraw-
Uruchomienie usługi Lotus Notes Traveler w istniejącej domenie, nawet jeśli główny dzić, czy ma on dostęp do skrzynek pocz-
w istniejącym środowisku jest bardzo pro- serwer jest w wersji niższej niż ósma. Do- towych użytkowników. Najlepiej jest wyge-
ste i można je wykonać w ciągu jednej go- datkowo, jeśli pracownicy korzystają ze nerować wcześniej plik ID nowego serwe-
dziny. starszej wersji klienta Lotus Notes, szablo- ra na serwerze głównym domeny Domino,
Jeśli z aplikacji ma korzystać do kilku- ny ich skrzynek będą wspierane- nie mogą a następnie podczas konfiguracji instalato-
dziesięciu osób jednocześnie, można prze- być starsze niż wersja 6.5 (6.5 jest wspiera- ra serwera wybrać opcję dodatkowego ser-
prowadzić ją na macierzystym serwerze Lo- na) i muszą znajdować się na serwerze nie wera Domino (additional server) oraz po-
tus Domino (oczywiście pod warunkiem, że starszym niż 7.0.2. dać ścieżkę do wygenerowanego wcześniej
nie jest on do tej pory obciążany w 100% lub W chwili obecnej Lotus Notes Traveler pliku ID serwera.
blisko tej wartości). W przypadku przewidy- współpracuje z następującymi systemami W ten sposób zaraz po uruchomieniu
wanej większej liczby potencjalnych jedno- operacyjnymi serwerów: nowej maszyny znajdzie ona główny ka-
czesnych użytkowników narzędzia lub ze talog użytkowników (musi mieć dostęp
względów bezpieczeństwa, warto jest wy- • MS Windows 2003 Server Standard w sieci do serwera, na którym się znajdu-
dzielić odrębny serwer Lotus Domino tyl- Edition 32 i 64 bity; je centralna książka adresowa names.nsf) i
ko pod Travelera. Jest to często o wiele wy- • MS Windows 2003 Server Enterprise pobierze z niego informacje o miejscu lo-
godniejsze, można skorzystać z wirtualiza- Edition 32 i 64 bity; kalizacji skrzynek pocztowych użytkow-
cji, aby w razie potrzeby dokładać kolejne • MS Windows 2003 Server R2 Stan- ników.
procesory czy pamięć, umieścić maszynę w dard Edition 32 i 64 bity; Aby się upewnić, iż konfiguracja nowe-
DMZ, otworzyć niezbędne porty tylko na • MS Windows 2003 Server R2 Enter- go serwera przebiegła pomyślnie, można
serwerze pośredniczącym, a w razie awarii prise Edition 32 i 64 bity. np. sprawdzić w programie Domino Ad-
lub eksperymentów z użytkownikami mo- ministrator, czy w książce adresowej no-
bilnymi nie ryzykuje się zagrożeniem cią- Serwer Domino, do którego będzie się od- wego serwera (names.nsf) znajdują się da-
głości pracy aplikacji krytycznych, do któ- woływał Traveler, może być zainstalowa- ne o wszystkich użytkownikach zarejestro-
rych należy m.in. poczta elektroniczna pra- ny pod innym systemem operacyjnym, wanych w domenie. Jeśli jest pusta, należy
cowników firmy. np. AIX czy Linux. sprawdzić, czy maszyna, na której jest no-
Instalacja na odrębnej maszynie pozwala Podczas komunikacji urządzenia prze- wy serwer, może nawiązać połączenie sie-
również na uruchomienie usługi w przed- nośnego z serwerem wykorzystywane są ciowe z głównym serwerem, oraz czy w wi-
siębiorstwach, które nadal wykorzystu- trzy porty (Rysunek 3), które należy udo- doku organizacji na liście znajduje się no-
ją starsze wersje serwerów Lotus Domi- stępnić aplikacji Travelera na zaporach po- wy serwer.
no oraz klientów Lotus Notes. Najnowsza między urządzeniami. Jeśli połączenie nie Można też wydać w konsoli nowego ser-
wersja aplikacji Lotus Notes Traveler wy- będzie używało SSLa, wówczas wystarczą wera polecenie replikacji bazy names.nsf z
serwerem centralnym (polecenie replicate)
i obserwować, co będzie rezultatem dzia-
łania. Komunikat o braku połączenia po-
twierdzi problemy sieciowe, natomiast po-
myślne zakończenie operacji może ozna-
czać, iż np. w trakcie instalacji nie zosta-
ła zaznaczona opcja Additional Server...
W obu przypadkach należy sprawdzić po-

Rysunek 1. Od wersji Lotus Notes Traveler 8.5 poczta wspierane są m.in. urządzenia przenośne i Rysunek 2. Widok skrzynki pocztowej w
telefony Nokii (Symbian S60) urządzeniu przenośnym

www.sdjournal.org 173
Rozwiązania mobilne

prawność połączenia sieciowego i najszyb-


ciej będzie odinstalować serwer Domino
przeznaczony dla Travelera, usunąć kata-
log Lotus/Data na dysku i zainstalować ser-
wer jeszcze raz (trwa to kilka minut). Pra-
widłowo zainstalowany serwer pobierze po
uruchomieniu dane z centralnej książki ad-
resowej i w zasadzie nie wymaga już więcej
żadnych prac konfiguracyjnych.
Jeśli nowy serwer Lotus Domino został
poprawnie zainstalowany i uruchomiony,
można go zatrzymać (polecenie quit lub po
prostu q w konsoli serwera) i rozpocząć in-
stalację Travelera.
Rysunek 3. Konfiguracja portów pomiędzy urządzeniem przenośnym a Travelerem Po uruchomieniu programu instalacyj-
nego, użytkownik jest proszony o wybra-
nie wersji językowej instalatora, zanim
przejdzie do kolejnych etapów konfigura-
cji (Rysunek 4).
Po kliknięciu przycisku OK pojawi się
ekran powitalny oraz przycisk uruchamia-
jący centrum informacji o produkcie. Są w
nim najnowsze dane o programie, opis in-
stalacji, konfiguracji, rozwiązania najczę-
ściej występujących problemów itp. Po przy-
ciśnięciu Next pojawi się tekst umowy licen-
cyjnej- aby przejść dalej, należy ją zaakcep-
tować.
W kolejnym oknie pojawią się trzy opcje
sposobu instalacji aplikacji (Rysunek 5).
Wersja Complete zainstaluje wszystkie
komponenty aplikacji na wskazanym ser-
werze Lotus Domino, czyli binaria apli-
kacji oraz stronę WWW programu, z któ-
rej użytkownicy będą mogli pobrać aplika-
cję kliencką do urządzenia przenośnego.
Rysunek 4. Rozpoczęcie instalacji Lotus Notes Traveler 8.5.0.1 Wersja Server Only instaluje tylko binaria

Tabela 1. Ustawienia autoinstalacji Travelera pod Windows Mobile


Parametr Wartość domyślna Opis
devPuserid brak Nazwa użytkownika Lotus Notes Traveler. Jeśli w adresach internetowych
występuje standardowa nazwa, można ją tu umieścić, aby nie podawać
pełnej nazwy przy konfiguracji oraz uprościć wpisywanie pełnej nazwy
wszystkim użytkownikom, np. @ibm.com
devPprimary brak Nazwa serwera Domino z Travelerem, ew. jego numer IP
devPSSL 0 Włączanie/wyłączanie SSL: 0- wyłączone; 1- włączone
devPsyncP 80 Numer portu wykorzystywanego do synchronizacji HTTP
devPsyncPS 443 Numer portu wykorzystywanego do synchronizacji poprzez SSL
devPservR /servlet Ścieżka dostępu do serwletu Lotus Notes Traveler, po zmianie należy prze-
/traveler nieść pliki serwletu w odpowiednie miejsce na dysku serwera
devPport 8642 Numer portu do kanału PUSH (AutoSync)
devPvpnenable 1 Włączanie/wyłączanie Lotus Mobile Connect (LMC): 0- wyłączone; 1- włą-
czone
devPvpnserver brak Nazwa hosta z bramką dostępową LMC, ew. jego numer IP
devPvpnport 8889 Numer portu do bramki LMC
devPvpnusesame 1 LMC ma używać tego samego loginu i hasła co Traveler: 0- nie; 1- tak
devPvpnuserid brak Login do LMC ma być inny od loginu do Travelera
devPvpnsavepassword 1 Czy hasło do LMC ma być zapisywane: 0- nie; 1- tak
devPvpnusedefault 1 Czy ma być używany domyślny profil LMC: 0- nie; 1- tak
devPvpnconnid brak Jeśli nie jest używany profil domyślny LMC, podaje nazwę używanego
profilu

174 SDJ Extra 34 Biblia


Lotus Notes Traveler

aplikacji. Jest to przydatne w przypadku, W tym celu należy w przeglądarce wpi- dając treści, grafikę itp. Jest to kwestia we-
kiedy stosowane są wysokie wymogi bez- sać adres internetowy serwera Domino, któ- wnętrznej polityki i potrzeb firmy.
pieczeństwa lub Traveler ma znajdować ry hostuje aplikację, dodając po niej nastę-
się na serwerze głównym razem, na któ- pujący ciąg znaków: http://nazwa_hosta_Lo- Konfiguracja i zarządzanie
rym są skrzynki pocztowe, natomiast stro- tus_Domino/traveler/index.html. Po uruchomieniu serwera Lotus Domino
na do pobierania oprogramowania klienc- Powinna pojawić się standardowa strona oraz Travelera, sprawdzeniu dostępności
kiego jest w DMZ lub będzie dostępna tyl- startowa Travelera, zawierająca odnośniki pakietów klienckich w Internecie, należy
ko czasowo. do pobrania oprogramowania klienckiego, jeszcze otworzyć wymagane porty na za-
Wówczas należy dwukrotnie instalować tak jak na Rysunku 7. Nie jest ona rozbu- porach, aby umożliwić komunikację po-
Travelera, przy czym na serwerze obsłu- dowana, gdyż jej zadaniem jest dystrybu- między urządzeniami a serwisem (Rysu-
gującym tylko stronę WWW aplikacji, w cja oprogramowania na urządzenia mobil- nek 3). Jeżeli ma być aktywne połączenie
trakcie instalacji należy wybrać ostatnią ne. Można ją oczywiście rozbudować, do- przy wykorzystaniu protokołu SSL, nale-
pozycję – Website Only.
Po kliknięciu w Next pojawią się pola z
opisem ścieżki dostępu katalogu, w któ-
rym będzie zainstalowany Traveler. Do-
myślnie jest wybierana lokalizacja, w któ-
rej znajduje się serwer Lotus Domino. Na
przedostatnim ekranie konfiguratora in-
stalacji można jeszcze zaznaczyć opcję Set
client download website as home page for
this server, co spowoduje, iż domyślną stro-
ną serwera Domino, na którym jest zain-
stalowana aplikacja, będzie strona starto-
wa Lotus Notes Travelera. Po wyświetle-
niu podsumowania, aplikacja zostanie za-
instalowana, co trwa kilkadziesiąt sekund.
Następnie należy uruchomić serwer Lo-
tus Domino i sprawdzić, np. w konsoli,
czy zadanie Travelera uruchomiło się (Ry-
sunek 6). Zawsze można sprawdzić, czy
wszystko działa, wydając w konsoli pole-
cenie show task (lub sh ta). Pojawi się lista
wszystkich zadań uruchomionych na ser-
werze, wśród których powinien znajdować
się Traveler.
Przed rozpoczęciem dystrybucji opro-
gramowania na urządzenia przenośne
użytkowników, trzeba jeszcze sprawdzić,
czy strona startowa jest dostępna w Inter-
necie. Rysunek 5. Wybór scenariusza instalacji Travelera

Tabela 2. Ustawienia autoinstalacji Travelera pod Symbianem


Parametr Wartość domyślna Opis
userid brak Nazwa użytkownika Lotus Notes Traveler. Jeśli w adresach internetowych wystę-
puje standardowa nazwa, można ją tu umieścić, aby nie podawać pełnej nazwy
przy konfiguracji oraz uprościć wpisywanie pełnej nazwy wszystkim użytkowni-
kom, np. @ibm.com
syncml.server brak Nazwa serwera Domino z Travelerem, ew. jego numer IP
syncml.protocol=http http/https Włączanie/wyłączanie SSL: http- wyłączone; https- włączone
syncml.port 80 Numer portu wykorzystywanego do synchronizacji HTTP
syncml.https.port 443 Numer portu wykorzystywanego do synchronizacji poprzez SSL
server.path /server Ścieżka dostępu do serwletu Lotus Notes Traveler, po zmianie należy przenieść pli-
/traveler ki serwletu w odpowiednie miejsce na dysku serwera
push.port 8642 Numer portu do kanału PUSH (AutoSync)
push.enabled 1 Włączanie/wyłączanie PUSH: 0- wyłączone; 1- włączone
predefined.connection brak Predefiniowanie wspólnego punktu dostępowego połączeń dla wszystkich użyt-
kowników (Access point)
lmc.server brak Jeśli predefiniowanym punktem dostępowym = Mobility Client, wówczas należy
podać nazwę hosta serwera LMC
lmc.port brak J.w. – numer portu LMC
lmc.iap brak J.w. – określenie połączenia LMC z najwyższym priorytetem

www.sdjournal.org 175
Rozwiązania mobilne

ży wcześniej odpowiednio skonfigurować zautomatyzować proces instalacji, edytu- że być brzemienne w skutkach. Umiesz-
serwer Domino. Po wykonaniu i spraw- jąc pakiet na serwerze i dodając do niego czenie „obok” zmienionej strony ułatwia
dzeniu wszystkich tych wymagań, moż- gotowe odpowiedzi, tak aby ograniczyć do późniejsze zmiany nawet na żywym orga-
na uruchomić przeglądarkę w urządze- minimum odpytywanie użytkownika. Aby niźmie oraz ustrzeże przed różnymi nie-
niu przenośnym, wpisać w niej adres in- rozbudować lub zmienić zawartość stro- spodziankami wynikającymi z błędów na
ternetowy, taki sam jak przy sprawdza- ny startowej Travelera, wystarczy poddać nowej stronie, literówek itp. W ten spo-
niu poprawności działania Travelera, na- edycji zawartość katalogu data/domino/ sób można m.in. szybko dystrybuować na
stępnie pobrać odpowiedni pakiet insta- html/traveler. Znajduje się on w głównym urządzenia mobilne także inne aplikacje,
lacyjny i uruchomić kreator instalacji. W katalogu instalacyjnym serwera Domino. np. klienta VPN- wystarczy dodać odno-
jej trakcie użytkownik zostanie zapytany Dobrą praktyką jest wcześniej przygoto- śnik do pliku instalacyjnego, aby użytkow-
o podanie kilku informacji, jak m.in. ad- wać zmienioną stronę, umieścić ją w in- nicy mogli go pobrać.
res hosta usługi, nazwę użytkownika, ha- nym miejscu na dysku serwera, a następ- Z kolei aby wpisać do kreatora instalacji
sło itp. Aby maksymalnie uprościć cały nie we wskazanym katalogu umieścić tyl- własne dane, ułatwiające użytkownikom
proces, tak aby każdy korzystający z serwi- ko odnośnik do niej. Nie należy również instalację na urządzeniu mobilnym, nale-
su nie zastanawiał się, jakiej odpowiedzi kasować żadnych plików z folderu Trave- ży poddać edycji plik Bootstrap.nts. Znaj-
udzielić, można umieścić dodatkowe in- lera, ponieważ niektóre z nich są używane duje się on w tym samym folderze co stro-
formacje na stronie startowej serwisu lub przez serwlet aplikacji i usunięcie ich mo- na Travelera.
W trakcie instalacji na urządzenia z sys-
temem operacyjnym Windows Mobile
użytkownik pobiera binaria w pliku o roz-
szerzeniu CAB (w Nokiach będzie to roz-
szerzenie SIS), natomiast wspomniany plik
kopiowany jest do folderu Moje Dokumen-
ty. Na Nokiach będzie to plik bootstrap_
s60.nts i musi się on znaleźć na karcie pa-
mięci lub gdziekolwiek pod folderem C:
\DATA. Jest on de facto plikiem o struktu-
rze XMLowej, choć rozszerzenie na to nie
wskazuje. W Tabeli 1. opisane są kolejne
znaczniki, które mogą być w nim zmienio-
ne, aby uprościć proces instalacji na urzą-
dzeniach przenośnych.
Oprócz przygotowania własnych pakie-
tów instalacyjnych oraz modyfikacji stro-
ny startowej, po zainstalowaniu na urzą-
dzeniach przenośnych aplikacji należy
jeszcze kontrolować jej poprawną pracę,
nadawać odpowiednie uprawnienia użyt-
kownikom, regulować ruch sieciowy itp.
Rysunek 6. Komunikat z konsoli serwera Domino potwierdzający uruchomienie aplikacji
Travelera
Do tego celu służy zestaw polityk, które
są dostępne po zainstalowaniu Travelera
w ustawieniach serwera Domino. Można
nimi zarządzać oraz modyfikować je, ko-
rzystając z programu Lotus Domino Ad-
ministrator.
Dla każdego użytkownika lub określo-
nej grupy (lub dla wszystkich w organi-
zacji) można utworzyć odrębne ustawie-
nia polityki, które są im nadawane przy re-
jestrowaniu nowej osoby na serwerze Do-
mino lub później, w trakcie np. przenosze-
nia do innego departamentu czy po prostu
w zależności od zaistniałych potrzeb i oko-
liczności.

ANDRZEJ OLSZTYŃSKI
Andrzej Olsztyński - w IBM od pięciu lat zajmuje
się narzędziami do pracy grupowej oraz zarzą-
dzania obiegiem dokumentów, starszy specjali-
Rysunek 7. Strona startowa Travelera, z której urządzenia przenośne pobierają oprogramowanie sta oprogramowania Lotus, poza pracą - trener
klienckie szachów

176 SDJ Extra 34 Biblia


Rozwiązania mobilne

Stary software
– nowa platforma
W jednym z numerów SDJ (2/2009) przybliżyliśmy profil prac zespołu
zajmującego się rozwojem oprogramowania dla radiotelefonów
systemu TETRA w krakowskim centrum Motoroli. Dziś przyjrzymy się
problemom, przed którymi stanęliśmy w ostatnich latach w związku z
migracją na nowe platformy sprzętowe.
apetyt na prąd, który zawsze będzie defi-
Dowiesz się: Powinieneś wiedzieć: cytowym zasobem w tego rodzaju rozwią-
• jakie są kluczowe aspekty przenoszenia • na czym polega specyfika programowania zaniach. Jest jednak kilka innych elemen-
oprogramowania urządzeń mobilnych na urządzeń mobilnych; tów, które na pierwszy rzut oka nie wydają
nowe platformy sprzętowe; • czym jest TETRA i jakie są podstawowe za- się kluczowe, jednak są na tyle ważne, aby
• jak wygląda taka migracja na przykładzie ra- stosowania systemów łączności tego typu. wziąć je pod uwagę.
diotelefonów systemu TETRA. Endianness (Rysunek 1). Wiele współ-
czesnych procesorów oferuje możliwość
zmiany ustawienia tego parametru. Zmia-
W każdym z tych przypadków pożą- na endiannessu w 600 tysiącach linii ko-
danym scenariuszem jest osiągnięcie peł- du jest zadaniem nietrywialnym. Nie ma
Poziom trudności nej integracji oprogramowania z nowym na rynku narzędzia, które byłoby w sta-
sprzętem przy ograniczeniu do minimum nie wykonać tę czynność całkowicie auto-
rozwspólnienia kodu źródłowego. Musi- matycznie. Istnieją aplikacje wspomagają-
my wszak pamiętać, że stare platformy czę- ce, ale żadna nie da nam gwarancji rozwią-

R
ynek urządzeń mobilnych co chwi- sto nie kończą życia wraz z wprowadze- zania wszystkich konfliktów z tym związa-
lę jest napędzany nowymi produk- niem ich nowszych odpowiedników i w nych. Jak poradzić sobie w takiej sytuacji?
tami różnych firm we wszystkich długiej perspektywie wysiłek związany z Pomocne okazują się unit testy, czyli ze-
jego segmentach. Wystarczy parę miesięcy, utrzymaniem naszego software’u będzie staw parametrów wejściowych i oczekiwa-
żeby trudności z zakupem najbardziej wy- tym większy, im więcej będzie w nim ele- nej odpowiedzi, która dla poszczególnych
szukanego gadżetu elektronicznego zamie- mentów specyficznych dla poszczególnych jednostek testowanych powinna być iden-
nić na trudności z jego odsprzedażą. Niewie- platform. Z drugiej strony stary kod nie po- tyczna. Niekoniecznie musimy testować
le jest na świecie rzeczy, które tracą tak szyb- zwala w pełni skorzystać z nowych możli- każdą funkcję, możemy to zrobić na całym
ko na wartości jak sprzęt elektroniczny. Urzą- wości, a tworzenie wielopoziomowej kom- module czy grupie funkcji. Jeśli nie mamy
dzenia mobilne nie są tutaj wyjątkiem, każ- pilacji warunkowej (#ifdef) dla kolejnych pewności, czy wszystkie możliwe miejsca
dy szanujący się producent w momencie pre- produktów wcale nie ułatwia późniejsze- w kodzie zostały przetestowane, możemy
miery swojego najnowszego dziecka już pro- go utrzymania takiego kodu. Srebrną kulę skorzystać z narzędzi do badania pokrycia
wadzi zaawansowane prace nad kolejnym je- każdy niestety musi znaleźć sam. kodu przez testy. Tak czy inaczej, unit te-
go wcieleniem. Każde kolejne wcielenie to sty zwiększają znacznie poziom ufności ta-
szansa na jeszcze większe zyski, a większe zy- Wybór mikroprocesora kiej zmiany.
ski napędzają kolejne wcielenia. Zakres takiej Nie będzie niespodzianką, jeśli powiemy, Podczas przeszukiwania w kodzie źródło-
zmiany może być bardzo różny: od wymia- że ten wybór odgrywa znaczącą rolę w wym obszarów narażonych na problemy en-
ny wyświetlacza LCD czy zmiany liczby lub dalszym procesie decyzyjnym. Producen- diannessu nie możemy zapomnieć o miej-
układu klawiszy, po dodanie nowych modu- ci mikroprocesorów prześcigają się w roz- scach rzutowania jednych struktur na dru-
łów rozszerzających istniejący sprzęt, jak od- wiązaniach ułatwiających tworzenie no- gie. Często do opisania interfejsu danego ta-
biornik GPS czy Bluetooth. Może wreszcie wych platform sprzętowych. Stos USB, IP, ska używa się unii wszystkich możliwych
zaistnieć potrzeba pełnego przeprojektowa- RS232 będące częścią uP przestają już dzi- struktur. Jeśli korzystamy z takiej definicji,
nia platformy sprzętowej tak, by stała się pod- wić, a czytając specyfikację najnowszych to upewnijmy się, że ta właśnie definicja jest
stawą dla implementacji funkcjonalności nie- ARM-ów, można zacząć się zastanawiać, wykorzystywana po stronie nadawcy komu-
możliwych do wprowadzenia na istniejącym czy aby to, co chcemy na nim stworzyć, nikatu. Przeszukajmy wszystkie unie w ko-
urządzeniu. Bardzo rzadko jednak taką zmia- już przypadkiem nie jest zaimplemento- dzie i sprawdźmy, czy rzutujemy ich pola po-
nę można ograniczyć do wyłącznie sprzęto- wane. Jednym z powodów dużej popular- między sobą.
wej, niemal zawsze konieczne są modyfikacje ności procesorów z rodziny ARM w urzą- Aby w przyszłości ustrzec się przed tego
w oprogramowaniu. dzeniach mobilnych jest ich ograniczony typu problemami, bardzo dobrym rozwią-

178 SDJ Extra 34 Biblia


Stary software – nowa platforma

zaniem jest oczywiście pisanie kodu nieza- software został zaprojektowany tak, aby jak temów czasu rzeczywistego i embedded, a
leżnego od kolejności bajtów w słowie (ale o najlepiej wykorzystywać zalety płynące z więc stosując się do jego wytycznych w na-
tym przekonamy się dopiero, jak będziemy rozdzielania czasu na podstawie ustalonych szej implementacji, będziemy mogli prze-
zmieniali kod zależny). Z tego też powodu, priorytetów tasków, to może on błędnie za- nosić nasz kod pomiędzy różnymi systema-
jeśli kiedykolwiek przyjdzie nam zmieniać działać na systemie, w którym czas jest pro- mi zgodnymi z POSIX.
endianness istniejącej implementacji, to za- porcjonalnie dzielony pomiędzy wszyst-
miast zamiany little na big lepiej zmieniać lit- kie zadania (Rysunek 2). Śledzenia proble- Proces
tle lub big na niezależny. mów mogących powstać w wyniku takiej W czasach, kiedy definiowano modele pro-
Kolejnym rozwiązaniem ułatwiającym zamiany nie życzymy najgorszemu wrogo- cesów do produkcji oprogramowania, bar-
jest serializacja komunikatów przesyłanych wi. Można spędzić wiele dni na analizie po- dziej niż moment pojawienia się nowego
pomiędzy taskami. Podczas ewentualnej jedynczego przypadku, aby stwierdzić, że produktu na rynku liczył się sam fakt je-
zmiany tylko metody do (de)serializacji po- większa część jakiegoś komponentu nadaje go pojawienia. Projektanci miesiącami sta-
trzebują ingerencji. Dodatkowo będziemy się jedynie do przepisania. Bez odpowied- rali się przewidzieć rozmaite scenariusze i
mieli gotowy mechanizm, który można wy- nio dobrych narzędzi do dynamicznej ana- zaprojektować wszystko na papierze z naj-
korzystać, jeśli kiedykolwiek rozproszymy lizy kodu (profiler) lepiej nie zabierać się w drobniejszymi szczegółami (waterfall). W
naszą implementację na kilka procesorów. ogóle do takiego zadania. dzisiejszych czasach takie podejście mo-
Metody do deserializacji są również dosko- Może się także okazać, że nowy RTOS głoby doprowadzić do opóźnień absolut-
nałymi miejscami do sprawdzenia popraw- udostępnia nam o wiele mniejszą liczbę nie nieakceptowanych przez rynek i klien-
ności wartości parametrów w przesyłanym priorytetów tasków niż poprzedni system. tów. Nie możemy utopić projektu w morzu
komunikacie. Częściowo staniemy przed podobnym pro- papierowych dokumentacji i analiz. Dlate-
blemem jak w poprzednim przypadku, tyle go właśnie powstają coraz to nowe meto-
System operacyjny że tutaj mamy kilka dróg do wyboru. Mo- dologie tworzenia oprogramowania, któ-
Wybór z pozoru tylko jest bardzo prosty. żemy na przykład pogrupować taski w taki re bardziej wpasowują się w aktualne wy-
Jednym z ważnych kryteriów jest oczywi- sposób, aby zadania zależne od siebie mia- magania i trendy na rynku. Jednym z przy-
ście cena RTOS-a, ale wcale nie znaczy to, ły różne priorytety. Można również połą- kładów może być Agile. Nie należy jed-
że system oparty na licencji GPL jest naj- czyć takie zadania w jeden moduł (z jed- nak bezkrytycznie stosować się do wszyst-
tańszy. Jeśli nie mamy w zespole samo- nym priorytetem) i samemu dopisać war- kich jego wytycznych, można wybrać kil-
dzielnego eksperta od takiego systemu, to stwę pośredniczącą, która będzie zarządza- ka praktyk (stand up meetings, scrum, itera-
musimy się liczyć z koniecznością prze- ła kolejnością wykonania tasków. Będziemy cje) i stosować się do nich. Koniec końców
szkolenia inżynierów oraz wykupieniem musieli skorzystać z bezpośrednich dyrek- jednak sukces wielkich i ”nieprzewidy-
pomocy technicznej. W systemach ko- tyw RTOS-a do zmiany stanów poszcze- walnych” projektów zawsze w dużej mie-
mercyjnych często takie szkolenia są wli- gólnych tasków. Innym rozwiązaniem jest rze opiera się na indywidualnych zdolno-
czone w cenę. Ważne jest również, aby ist- również napisanie całej warstwy abstrak- ściach poszczególnych członków zespołu.
niały wydajne i stabilne narzędzia deve- cji, która będzie tłumaczyła i symulowała Pamiętajmy, że proces ma nam pomagać,
loperskie wspomagające programowanie: zachowania poprzedniego RTOS-a na no- a nie utrudniać.
od IDE poczynając, na debugowaniu koń- wym systemie. Każdy RTOS udostępnia ja-
cząc. Licencja GPL posiada dodatkowo in- kieś własne API i jeśli z niego skorzystamy, Migracja w praktyce
ne ograniczenia. Na przykład trzeba udo- to musimy się liczyć z podobnymi proble- W przypadku terminali systemu TETRA
stępnić wszystkie zmiany wprowadzone w mami podczas zmiany systemu. Jeśli nasz mieliśmy i wciąż mamy do czynienia z nie-
takim kodzie. software korzysta z sygnałów do synchroni- mal pełnym spektrum prac związanych z
Najważniejszym elementem takiego sys- zacji zadań (na jednym systemie), możemy przenoszeniem oprogramowania na nowe
temu jest jednak Scheduler, czyli moduł re- nadpisać takie wywołania własnymi meto- platformy:
alizujący algorytm zarządzania czasem pro- dami realizującymi ten sam cel za pomo-
cesora. To od niego bezpośrednio zależy, w cą semaforów (na drugim systemie). Dzię- • istniejące radio samochodowe zostało
jakiej kolejności i na jakich zasadach po- ki temu nie będziemy musieli zmieniać od- wyposażone w nowy typ panelu przed-
szczególne taski będą realizowały swoje za- wołań w istniejącym kodzie. Warto tutaj niego z kolorowym wyświetlaczem gra-
dania. I tutaj właśnie może spotkać nas naj- dodać, że istnieje standard POSIX dla sys- ficznym w miejsce tekstowego;
gorszy z możliwych scenariuszy. Jeśli nasz

����������������������������������������� ������������ ������������ ������������ ������������ ������������ ������������ ������������

���������������
���� ���� ���� ����
�����������
�����������������������������������������
������������
���� ���� ���� ����

Rysunek 2. Podstawowa różnica w dzieleniu czasu procesora pomiędzy taski. – Na podstawie


Rysunek 1. Reprezentacja 4-bajtowej liczby regularnych interwałów czasowych (powyżej). – Na podstawie priorytetów (poniżej). Istnieją
całkowitej w pamięci oczywiście różne odmiany obu idei jak również ich kombinacje

www.sdjournal.org 179
Rozwiązania mobilne

• na potrzeby modemu TETRA istniejące W praktyce stanęliśmy tu przed proble- niczenie maksymalnego poboru prą-
radio zostało przeniesione na kartę SD, mem opisanym kilka akapitów wyżej: ada- du. W tym celu trzeba było w modelu
którą można zainstalować w urządzeniu ptacją kodu do nowego procesora, kompila- ATEX synchronizować zadania obsługi
typu PDA; tora i systemu operacyjnego. Częścią prac LCD oraz obsługi nadajnika radiowego,
• wprowadzenie radiotelefonu w standar- stało się stworzenie warstwy abstrakcji nad aby nigdy nie wystąpiły jednocześnie.
dzie ATEX wiązało się ze zmianą kla- systemem operacyjnym, która umożliwiła Trudność polegała na tym, że pierwsze
wiatury i zapewnieniem współpracy w miarę łagodne przejście na nową platfor- z nich jest asynchroniczne, a drugie ple-
z elementami obudowy spełniającymi mę. Konieczne stało się również opracowa- zjochroniczne.
szczególnie rygorystyczne kryteria; nie protokołu komunikacyjnego pomiędzy • W przypadku radiotelefonu kamuflo-
• model Covert obywa się bez wyświe- radiem a panelem przednim i serializacja wanego (covert radio) konieczne było
tlacza, ale dysponuje zdalnym urządze- przesyłanych danych (wraz ze zmianą en- maksymalne uproszczenie interfejsu
niem sterującym; dianessu). użytkownika poprzez eliminację wy-
• powstała również całkowicie nowa plat- Z drugiej strony projekt ten stał się oka- świetlacza i zapewnienie sterowania
forma sprzętowa z innymi procesorami i zją do wprowadzenia znaczących uspraw- przez zdalnego pilota. Głównym pro-
nowym systemem operacyjnym. nień i optymalizacji. Ponieważ ostateczna blemem było tu usunięcie wszelkich
decyzja o linii podziału software’u pomię- interakcji z użytkownikiem, które po-
NGCH - Next Generation dzy radiem i panelem wskazała jako najlep- legały na dialogu (np. żądanie nume-
Control Head sze rozwiązanie oddzielenie ściśle interfej- ru PIN) – w zamian pozostawiono tyl-
Potrzeba posiadania zunifikowanego inter- sowej części tasku aplikacyjnego, można ko te funkcje, które mogły być wyko-
fejsu użytkownika dla różnych modeli ra- było poprawić separację tego obszaru ko- nane natychmiast (jedyna informacja
diotelefonu wydaje się naturalna, więc po du i rozbić go, zgodnie z regułami sztuki, zwrotna dostępna była jako dźwięk w
wprowadzeniu przenośnych radiotelefo- na dwa osobne taski. Konieczność stworze- słuchawce).
nów MTH800 i MTP850 posiadających nia dużych ilości nowego kodu pozwoliła • Użycie protokołu tetrowego na PDA
kolorowe wyświetlacze i interfejs użytkow- wykorzystać techniki modelowania obiek- zmusiło nas do odseparowania części lo-
nika podobny do znanego z telefonów ko- towego – znaczna część kodu powstała w giki od pozostałej funkcjonalności. En-
mórkowych Motoroli, było jasne, że po- środowisku Rational Rose RealTime. Nie kapsulacja modułu ściśle zależnego od
dobna zmiana dotknie samochodowy mo- do przecenienia była również możliwość otoczenia nie jest największym wyzwa-
del MTM800. wyprowadzenia z radia elementów o du- niem dla programisty, ale z pewnością
Stary panel przedni zaopatrzony w trzy- żym zapotrzebowaniu na pamięć: w pa- wymaga dużej ostrożności i dokładne-
liniowy tekstowy wyświetlacz służył prak- mięci panelu przedniego znalazły się np. go przebadania zależności w istniejącym
tycznie tylko do wyświetlania czystego tek- czcionki; pracuje tam również WAP brow- kodzie.
stu i przekazywania do radia informacji o ser, który jest dostarczany jako zewnętrz-
naciśniętych klawiszach. Nowe urządzenie na biblioteka. Gdy osiągnię
z silnym procesorem Texas Instruments Równie ciekawym aspektem pracy nad to granice możliwości
stwarzało o wiele większe możliwości w tym projektem były problemy sprzętowe, Choć nieodłączną częścią programowania
postaci przeniesienia na nie części funk- które się w nim pojawiły (synchronizacja po- urządzenia mobilnego jest optymalizacja
cjonalności aplikacji. Należało jednak zna- między radiem a panelem przednim połączo- istniejącej implementacji w celu pomiesz-
leźć najodpowiedniejszy punkt przecięcia nym z nim wielometrowym kablem; zapew- czenia kolejnych funkcjonalności na tej sa-
warstwy aplikacji i połączyć niskie war- nienie panelowi odpowiedniego napięcia do mej platformie sprzętowej, zawsze istnieć
stwy oprogramowania z modelu MTM800 jego włączenia i wykrycie na radiu sytuacji, będą ograniczenia, których żadną imple-
z wysokimi warstwami z modeli z wyświe- w której się to nie udało) oraz nowe dostępne mentacją ominąć się nie da. Można wów-
tlaczem graficznym. Wydatnie pomogła w konfiguracje sprzętu: kilka release’ów później czas pójść ścieżką wymiany poszczegól-
tym zadaniu modułowa struktura oprogra- wprowadzono na przykład możliwość szere- nych elementów hardware’u w celu za-
mowania, jednak myli się ten, kto sądzi, że gowego podłączenia dwóch paneli do jedne- chowania maksymalnej kompatybilności,
wystarczyło trywialne przeniesienie czę- go radia (co może być użyteczne na przykład ale można też postawić na zupełnie nową,
ści kodu. w karetkach). tworzoną od podstaw, platformę, która w
dłuższej perspektywie czasowej zastąpi
Ograniczanie i rozszerzanie modele istniejące. I jest to kolejna ze ście-
– ATEX, Covert i TETRA Modem żek rozwoju oprogramowania radiotelefo-
Niejednokrotnie w parze z wprowadza- nów TETRY, którą podążyliśmy.
niem funkcjonalności dla nowych plat- Nawet jeśli wymianie ma ulec niemal cały
form idzie ograniczenie funkcjonalności sprzęt radia, nie oznacza to jeszcze, że pro-
istniejących. Jeśli użytkownik będzie pra- ces tworzenia oprogramowania należy roz-
cował w grubych, strażackich rękawicach, począć od nowa. Wręcz przeciwnie – migra-
liczne drobne klawisze będą dla niego ra- cja software’u na nową platformę jest znako-
czej przeszkodą niż pomocą. Jeśli radio po- mitą okazją na poprawienie separacji kodu
zbawione będzie wyświetlacza, to jak za- i uporządkowanie go po procesie wielolet-
pewnić dostępność funkcjonalności, któ- niego developmentu. Udoskonalenia wpro-
re dotychczas z korzystaniem z wyświetla- wadzone w ramach tego procesu mogą być
cza się wiązały? równie pożyteczne dla starych platform, jak
i dla nowej.
• Jednym z wymagań stawianych radio- Największa migracja, jaką wykonaliśmy
telefonom pracującym w warunkach w krakowskiej Motoroli, była jednocześnie
Rysunek 3. ATEX MTP850Ex szczególnie niebezpiecznych jest ogra- zmianą kompleksową, polegającą na grun-

180 SDJ Extra 34 Biblia


Stary software – nowa platforma

townym przemodelowaniu architektury ra- formy, developerzy i projektanci snują swo- Mimo iż przenoszenie istniejącego opro-
dia, i obejmującą: je wizje reorganizacji istniejącego kodu, gramowania na nową platformę sprzętową
gruntownego refaktoringu, zmiany prze- czy też nowy system operacyjny nie jest za-
• wprowadzenie nowych procesorów, za starzałego designu i prawie zawsze spoty- daniem trywialnym, to szczerze polecamy
czym podążyła konieczność separacji ta- kają się z jedną odpowiedzią ze strony kie- taką lekcję każdemu programiście (a już
sków dotychczas pracujących na jednym rownictwa: Rewolucji nie będzie. Tylko nie- na pewno tym lubiącym wyzwania). Spek-
procesorze; zbędna ewolucja. I chociaż dla tych pierw- trum problemów, z którymi będziemy się
• wraz z nowymi procesorami pojawił szych właśnie zawalił się świat, to w rze- musieli zmierzyć, zmusi nas do bardzo do-
się nowy system operacyjny, posiadają- czywistości właśnie droga ewolucji pozwa- głębnej analizy zagadnień znanych jedynie
cy mechanizmy systemu czasu rzeczy- la śledzić wpływ wprowadzonych zmian ze studiów bądź literatury. Poznamy rów-
wistego, którymi nie dysponowaliśmy na wielokrotnie przetestowaną i napra- nież narzędzia wspomagające taką tranzy-
wcześniej, ale też nie do końca zgodny wianą funkcjonalność końcową. Stara zasa- cję lub sami takie napiszemy. Bez wątpie-
z naszym systemem w zakresie prioryte- da mówi, że nie zmienia się drużyny, która nia doświadczenie zdobyte w takim pro-
tów tasków; wygrywa. Dla kodu dopieszczanego i testo- jekcie uczyni nasz warsztat bardziej doj-
• przepisanie części assemblerowej na C wanego przez kilka lat i tysiące użytkow- rzałym i przemyślanym oraz przyczy-
przy jak najmniejszej utracie wydajności; ników rewolucja nie jest pierwszą rzeczą, ni się do wzrostu naszej wartości na ryn-
• wprowadzenie mechanizmów szybkiego którą należy brać pod uwagę. Czasami jed- ku pracy.
reagowania i wczesnego wykrywania po- nak wyjścia już nie ma.
trzeb danego taska zamiast przygotowy-
wania wszystkich ewentualnych odpo-
wiedzi kosztem zwiększonego zapotrze- KAMIL KOWALSKI
bowania na prąd; Wykształcenie w dziedzinie telekomunikacji, związany z programowaniem radiotelefonów Motoroli od
• zaimplementowanie sterowników obsłu- prawie 5 lat. Odpowiedzialny za rozwijanie protokołu komunikacji z infrastrukturą.
gujących dotychczasowe akcesoria, jak Kontakt z autorem: kamil.kowalski@motorola.com
również przygotowanych na przyszłych
następców. ARTUR CHRUŚCIEL
Z wykształcenia informatyk, w Motoroli pracuje od 2005 roku. Od początku w zespole tworzącym
Podsumowanie oprogramowanie dla radiotelefonów systemu TETRA. Zajmuje się głównie warstwami związanymi z
Zawsze, gdy pojawia się na horyzoncie pro- protokołem komunikacji po interfejsie radiowym w trybie DMO (bez infrastruktury).
jekt związany w rozszerzaniem nowej plat- Kontakt z autorem: artur.chrusciel@motorola.com

R E K L A M A
Rozwiązania mobilne

Oracle SPATIAL
Opis podstawowych funkcjonalności
opcji Oracle SPATIAL dedykowanej dla systemów GIS
Poprzez opcję SPATIAL baza danych zyskuje możliwość zarządzania
danymi o charakterze przestrzennym. To zupełnie nowy wymiar
pozwalający jeszcze precyzyjniej odwzorowywać rzeczywistość,
modelować dane i analizować zależności.
bądź bardziej złożone, np:
Dowiesz się: Powinieneś wiedzieć:
• Czym są systemy GIS; • Podstawy SQL. • przecinanie;
• Jakie mamy rodzaje danych przestrzennych. • łączenie;
• zwracanie centroidu;
• upraszczanie, czyli generalizacja.
Istnieje cały zestaw sztywnych reguł zwią-
zanych z tworzeniem obiektów, które mu- „Układy współrzędnych”
Poziom trudności szą zostać spełnione, aby obiekt był po- Każdy obiekt geometryczny powinien być opa-
prawny, a takie procedury jak SDO _ trzony informacją o układzie współrzędnych, w
G E O M .V A L I D A T E _ G E O M E T R Y _ W I T H _ którym się znajduje tzw. SRID(System Reference
CONTEXT bądź SDO _ GEOM.VALIDATE _ ID). Jest to szczególnie istotne dla poprawno-

O
gromna popularność takich serwi- LAYER _ WITH _ CONTEXT umożliwiają zdia- ści obliczeń prowadzonej dla obiektów znajdu-
sów jak Google Maps, Google Earth, gnozowanie nieprawidłowości, natomiast jących się w układach odniesienia opartych na
MS Virtual Earth niewątpliwie SDO _ UTIL.RECTIFY _ GEOMETRY służy do geoidzie(czyli kuli ziemskiej).
przyczyniła się do wzrostu zapotrzebowania automatycznej naprawy niespełniające- Od wersji 10gR2 bazy danych, wszyst-
na systemy do obsługi różnych typów danych go reguł poprawności obiektu geometrycz- kie definicje układów odniesienia są za-
przestrzennych. Praktycznie każdy z nas ma nego. pisane w standardzie EPSG, predefinio-
do czynienia z GISem, korzystając z nawigacji Na obiektach geometrycznych możemy wanych jest 4402 układów (select co-
samochodowej bądź planując trasy za pomo- wykonywać proste operacje obliczeniowe: unt(1) ile from MDSYS.sdo_cs_srs). Wy-
cą różnych serwisów internetowych lub wy- mienię kilka z nich, które używane są
szukując informacje o lokalizacji interesujące- • zwróć długość; w Polsce : PUWG 2000(SRID od 2176
go nas obiektu. Można stwierdzić, że dokonała • zwróć powierzchnie; do 2179), PUWG 1992(SRID=2180),
się ewolucja, a GIS z wyspecjalizowanych sys- • zwróć objętość(dla brył), WGS84(SRID=8307).
temów, dostępnych tylko wąskiej grupie użyt-
kowników, stał się ogólnodostępny. ����� ����������� ��������������� ��������������������

Obiekty przestrzenne
Podstawowym typem danych służącym do
przechowywania obiektów geometrycznych
jest w bazie danych Oracle typ SDO_GEO-
METRY, który potrafi przechować obiek- ������������� ���� ��������
�������
����������������� ������� �������
ty typu:

• punkt;
• linia;
• poligon;
• + warianty wymienionych
�������������
• multipunkt; ���������
������������
�������������
• multilinia; �������� ������������������
�����
• multipoligon;

a także od wersji 11g bryły

• solid;
• composite solid. Rysunek 1. Rodzaje obiektów przechowywanych w SDO_GEOMETRY

182 SDJ Extra 34 Biblia


Oracle SPATIAL

Istnieje możliwość zdefiniowania własne- Dostępne analizy wykorzystują następujące go, kosztownego czasowo, etapu anlizy prze-
go układu współrzędnych, jeśli definicja zo- operatory przestrzenne: strzennej.
stanie wprowadzona poprawnie, można dy- Wszystkie analizy działają również dla da-
namicznie bądź statycznie przekształcać • - Inside - Contains nych 3D i uwzględniają specyfikę związaną z
obiekty z jednego układu współrzędnych na • - Touch - Disjoint układami współrzędnych.
inny, używając pakietów SDO_CS.TRANSFORM • - Covers - Covered By
bądź SDO_CS.TRANSFORM_LAYER. • - Equal - Overlaps LRS – liniowy
Nowością w wersji 11g jest wprowadzenie system referencyjny
układów współrzędnych 3D. Operator odległości Do działania wymaga obiektów geometrycz-
nych liniowych z wypełnionym przynajm-
Wydajność – indeksy • - Within Distance (funkcja SDO _ niej dla początku i końcu odcinka parame-
przestrzenne i partycjonowanie WITHIN _ DISTANCE) trem M(w poniższym przykładzie będzie
Indeks przestrzenny, obecnie typu R-TREE, to kilometraż). Funkcjonalność znajduje za-
budowany jest w oparciu o MBR(Minimum Możliwa jest również analiza sąsiedztwa stosowanie np. do ewidencjonowania infra-
Bounding Rectangle) indeksowanego obiek- struktury drogowej, wykorzystywana jest
tu przestrzennego, następnie dokonywana • - Nearest Neighbor (funkcja SDO _ NN) do obsługi zjawisk o charakterze liniowym
jest agragacja do wyższego poziomu. Może- bądź punktowym. Na przykład chcąc uzy-
my sterować parametrami indeksu, np: czy ma Wszystkie analizy wykonywane są dwuetapo- skać odcinek drogi, na którym obowiązu-
być budowany tylko w oparciu o współrzęd- wo, najpierw na podstawie przeszukiwania in- je ograniczenie prędkości na segmencie od
ne XY obiektu, czy także indeksowaniu pod- deksu przestrzennego wybierani są kandydaci, 53 do 59 kilometra, wywołujemy funkcję
lega współrzędna Z(SDO_INDX_DIMS=3); bądź a następnie wykonywana jest szczegółowa ana- SDO_LRS.CLIP_GEOM_SEGMENT z odpowiedni-
w na jakiej wielkości paczkach zatwierdzonych liza geometryczna i zwracany wynik. mi parametrami, która wycina i zwraca z ca-
nowych/zmienionych indeksowanych elemen- Odstępstwem od powyższej reguły jest łego odcinka drogi tylko segment od 53 do
tów ma być dokonana przebudowa (SDO_DML_ funkcja SDO_FILTER, która przeznaczona jest 59 kilometra. Korzyści: geometrię przecho-
BATCH_SIZE=1000). do bardzo szybkiego wyszukiwania obiektów wujemy tylko raz, natomiast wszystkie geo-
Od wersji 11g istnieje możliwość partycjo- np: w celu ich przekazania do wizualizacji. metrie pochodne tworzone są na zasadzie dy-
nowania tabel zawierających obiekty geome- Funkcja jako wynik zawraca zawsze wszyst- namicznej segmentacji geometrii bazowej na
tryczne. Tworzony jest tzw. SDO_ROOT_MBR dla kich kandydatów, nie wykonując drugie- podstawie dostarczonych atrybutów. Możli-
indeksu przestrzennego na poziomie każdej
partycji i podczas wyszukiwania obiektów geo-
metrycznych za pomocą zapytania przestrzen-
nego optymalizator w procesie PARTITION_
PRUNINGU eliminuje partycje indeksu niepod-
legające dalszemu skanowaniu.
Kolejną korzyścią z użycia partycjonowa-
nia jest możliwość równoległej przebudowy
indeksu przestrzennego, a także zrównolegle-
nia wykonywania zapytania przestrzennego.
Dla dużej ilości danych ma to ogromne zna-
czenie wydajnościowe.

Analizy przestrzenne
Wykonywane za pomocą języka SQL, SPA- Rysunek 2. Rodzaje obiektów 3D
TIAL wzbogaca standardowego SQL’a o moż-
liwości przestrzenne. W przykładzie poka-
zano analizę przestrzenną ze złożeniem kli- �����������
ku relacji przestrzennych w jednym zapyta- �
� � �
niu SQL.


SELECT /*+ordered*/ D.Geometry, D.obreb, D.nr
FROM obszary_zagrozen Z, dzialki D �������������� ������������ �����
WHERE Z.typ = ‘S’ ������������ ���������������
AND SDO_RELATE(D.Geometry, Z.Geometry, � � ����� �������
‘mask=touch+coveredby’) = ‘TRUE’; � �

Należy pamiętać o właściwej kolejności po-


dania obiektów dla funkcji SDO _ REALATE, ������������������� ������������������ �����

jako pierwszy parametr podana jest zaindek- �


sowana kolumna tabeli w której znajdują się

wyszukiwane obiekty, natomiast drugim pa-
rametrem(zawiera obiekt/obiekty wyszuku-
jące) może być podany jawnie pojedynczy ��������

obiekt SDO _ GEOMETRY lub kolumna typu


SDO _ GEOMETRY. Rysunek 3. Przykłady relacji przestrzennych

www.sdjournal.org 183
Rozwiązania mobilne

wa jest również sytuacja odwrotna – poda- ność (Load On Demand – NDM LOD) de- „OpenGIS Implementation Specification for
jemy współrzędne na odcinku, a otrzymuje- dykowana do obsługi bardzo dużych grafów Geographic information – Simple feature ac-
my kilometraż. sieci(złożoność liczona w dziesiątkach milio- cess – Part 1: Common architecture”.
nów elementów). Jest w stanie wydajnie ob-
Numeryczny model sługiwać ogromne ilości analiz sieciowych w Workspace manager
terenu – TIN, LIDAR czasie zbliżonym do rzeczywistego. Stanowi Zarządza pracą grupową, wspiera następują-
narzędzie do budowy systemów zarządzają- ce funkcjonalności:
• LIDAR – chmura punktów, z bardzo du- cych obliczeniami na złożonych modelach
żą dokładnością odwzorowuje powierzch- sieciowych, np. SmartGrid • zarządzanie przywilejami;
nię, posiada natomiast tę niedogodność, że NDM LOD – wprowadza istotne zmiany w • blokowanie obiektów;
wolumen danych liczony jest w milionach stosunku do samego NDM; najważniejszą jest • multiversjonowanie;
punktów na odwzorowywany obiekt. możliwość wykonywania wszystkich analiz sie- • operacje na przestrzeniach roboczych;
Stworzono specjalną funkcjonalność do ciowych na partycjonowanym – podzielonym • detekcja i rozstrzyganie konfliktów;
zarządzania tym rodzajem danych(SDO _ na klastry modelu sieciowym(niezależnie od
POINT _ CLOUD). Dane są agregowane, kom- opcji partycjonowania do bazy danych). Kolejne Umożliwia obsługę danych historycznych i
presowane i indeksowane. Dostęp do nich funkcjonalności, które warto wymienić, to: udostępnienie danych aktualnych na precy-
jest bardzo szybki. Dostarczany jest rów- zyjnie określony moment w czasie.
nież konwerter do czytania danych(for- • analiza wielokosztowa;
mat LAS)z urządzeń pomiarowych. • możliwość wyniesienia obliczeń poza Udostępnianie
• TIN – model terenu w postaci nieregu- bazę danych; danych – serwisy WWW OGC
larnej siatki trójkątów, opcja SPATIAL udo- • obsługa bufora na zasadzie kolejki LRU. Opcja SPATIAL udostępnia następujące ser-
stępnia również zestaw pakietów służą- wisy WWW zgodne ze specyfikacją OGC:
cych budowie TIN’a. Można dokonać W kolejnych wersjach Oracle SPATIAL rozwi-
przekształcenia SDO _ POINT _ CLOUD na jana będzie tylko funkcjonalność NDM LOD. • WFS;
SDO _ TIN. • WFS-T;
Georaster • CSW;
Topologia trwała Zestaw funkcjonalności służący do składowa- • Geocoding;
Relacyjny sposób zapisu warstwy poligonowej, nia i przetwarzania obrazów rastrowych. • Routing;
charakteryzuje się jednokrotnym przechowy- Największe instalacje przechowują tera- • OpenLS.
waniem współrzędnych wspólnych punktów bajty danych i realizują kilkaset żądań na se-
dla sąsiadujących poligonów. Zapewnia spój- kundę. Serwis WMS realizowany jest w produkcie
ność i ciągłość warstwy geometrycznej. Nie- Składowane dane są dzielone na bloki i MapViewer, posiadającym silnik renderują-
które analizy przestrzenne (np. najbliższego są- kompresowane(kompresja stratna JPEG-B, cy i służącym do wizualizacji danych prze-
siedztwa) wykonywane są na warstwie topolo- JPEG-F; bezstratna – deflate(zip)), budowa- strzennych oraz tworzenia aplikacji.
gicznej znacznie szybciej, niż na warstwie geo- ny jest także dedykowany dla obrazów in-
metrycznej. Możliwe jest również budowanie dex piramidalny. Można zmieniać kryteria Współpraca z oprogramowa-
zależności hierarchicznych pomiędzy obiekta- podziału rastra na bloki oraz liczbę pozio- niem GIS innych producentów
mi w celu utrzymania spójności granic obiek- mów indeksu piramidalnego, jest to jeden z Długa obecność na rynku oraz wykorzystanie
tów nadrzędnych dziedziczących kształt z za- elementów optymalizacji przechowywania i przez ORACLE SPATIAL standardów OGC
gregowanych obiektów podrzędnych. Funkcjo- dostępu do georastrów. Na obrazach może- sprawiły, iż sposób zapisu danych przestrzen-
nalność dedykowana dla systemów katastral- my dokonywać georeferencji, filtrować, do- nych stworzony przez ORACLE jest obsługi-
nych. pisywać własne atrybuty, wycinać. Szcze- wany przez oprogramowanie firm: AUTO-
gółowy opis funkcjonalności w obszernym DESK, BENTLEY, ESRI, INTERGRAPH,
Modele sieciowe dokumencie, który dostępny jest pod ad- MAPINFO, SMALLWORLD(GE) i innych.
Network Data Modeling(NDM) – funkcjonal- resem: http://www.oracle.com/pls/db111/to_ Lista partnerów technologicznych pod
ność do obsługi grafów sieci. pdf?pathname=appdev.111/b28398.pdf adresem: http://www.oracle.com/technology/
NDM może przechowywać model sieci products/spatial/spatial_partners_isv.htm
bez odniesienia przestrzennego(logiczny) al- Adnotacje
bo model z reprezentacją geometryczną. Mo- Służy do przechowywania tekstu i jego atry- Dokumentacja
del z geometrią może być dodatkowo zaopa- butów(oznaczenia obiektów np: numeracja, produktu w internecie
trzony w LRS(Linear Referencing System) budynków, nazwy ulic, itp.). Składowane są Artykuł w sposób bardzo skrótowy przedsta-
NDM obsługuje następujące obszary funk- następujące informacje: wia podstawowe funkcjonalności opcji Orac-
cjonalne: le Spatial. Szczegółowa dokumentacja pro-
• tekst; duktu dostępna jest w Internecie na stronach
• składowanie modelu sieciowego w struk- • sposób prezentacji tekstu(czcionka, ko- OTN pod adresem:
turze tabelarycznej powiązanej relacjami; lor, odstępy); http://www.oracle.com/pls/db111/portal.portal_
• wsparcie dla edycji, kontroli poprawno- • położenie tekstu(punkt wstawienia, kie- db?selected=7&frame=#oracle_spatial_and_loca-
ści i optymalizacji struktury; runek, linia wiodąca); tion_information.
• wbudowane analizy sieciowe(najkrótsza
ścieżka, problem komiwojażera, niedo- Istnieje możliwość tworzenie opisów złożo-
stępność grafu, wszystkie ścieżki itp.); nych składających się np: z licznika mianow- TOMASZ MURTAŚ
• możliwość dodawania własnych reguł. nik i kreski ułamkowej. Pracuje w Oracle Polska jako Principal Sales Con-
Typ danych zaimplementowany zgodnie sultant, Eastern Europe.
W wersji 11g pojawiła się nowa funkcjonal- ze specyfikacją OGC opisaną w dokumencie Kontakt z autorem: tomasz.murtas@oracle.com

184 SDJ Extra 34 Biblia


Rozwiązania mobilne

Wzorce
projektowe w Java Card
Tworzenie oprogramowania wymaga starannego projektu i dużej
dokładności. Platforma Java Card pozostawała swego rodzaju skansenem,
gdzie szeroko akceptowany był kod, który w innych obszarach byłby
absolutnie nieakceptowalny. Warto więc poświęcić czas na zapoznanie się z
dobrymi praktykami projektowania i programowania także na tej platformie.
tów typu STK Handler w określonych
Dowiesz się: Powinieneś wiedzieć: momentach życia aplikacji STK,
• o dobrych praktykach programowania w Ja- • Znać podstawy programowania dla platfor- • rozważne wykorzystanie globalnych i lo-
va Card; my Java Card; kalnych zmiennych (tj. w szczególności:
• Jak dostosować istniejące wzorce projekto- • Posiadać wiedzę o projektowaniu oprogra- unikanie redundancji, minimalizacja
we do specyfiki platformy Java Card; mowania; liczby zmiennych lokalnych, minimali-
• Jak tworzyć kod Java Card, który można te- • Znać język UML. zacja liczby parametrów metod itd.),
stować przy użyciu testów jednostkowych; • zakaz przechowywania referencji do
tzw. Entry Point Object, jakim jest na
przykład obiekt klasy APDU.
pojawiają się czerwoną, pogrubioną czcionką już
we wstępie do Java Card™ & STK Applet Deve- Z kolei, z punktu widzenia bezpieczeństwa
Poziom trudności lopment Guidelines – dokumentu dostarczanego tworzonego apletu, deweloperzy powinni
przez jednego z producentów kart SIM. stosować się do następujących zaleceń:

Wytyczne dla • unikanie przechowywania kluczy krypto-

J
ak ważne jest właściwe prowadzenie pro- programistów Java Card graficznych oraz kodów PIN w tablicach
jektów informatycznych ściśle po torach Najbardziej podstawowe zalecenia wymienia- prymitywnych typów, np. tablicy bajtów,
wyznaczanych przez metodologie two- ne we wspomnianych Development Guideli- • ograniczenie czasu życia poufnych da-
rzenia oprogramowania, wiadomo nie od dziś. nes, jakimi powinien kierować się programi- nych do czasu trwania sesji, w której są
Nawet dla najmniejszych komponentów, budu- sta Java Card, dotykają zarówno kwestii ści- one wykorzystywane (np. poprawna ob-
jących większe systemy, niezbędne jest tworze- śle powiązanych z samą technologią (mode- sługa tworzenia i kasowania kluczy se-
nie modelu, który pozwala m.in.: na spojrzenie lem pamięci, programowaniem SIM Applica- syjnych w aplecie),
na kod z perspektywy wymagań systemu już od tion Toolkit - STK), jak i ogólnie przyjętych za- • przechowywanie poufnych danych w ta-
samego początku jego tworzenia, łatwiejszą we- sad związanych z tworzeniem bezpiecznych blicach typu transient,
ryfikację, testowanie oraz integrację poszczegól- aplikacji oraz czysto inżynierskimi praktyka- • ochrona danych przed atakiem typu rol-
nych części oprogramowania. mi tworzenia oprogramowania. Do rekomen- lback, związanym z występowaniem w
Podobnie jak istnienie projektu dla tworzo- dacji wynikających wprost ze stosowania tech- API Java Card mechanizmu transakcji (po
nego oprogramowania, równie istotnym ele- nologii Java Card można zaliczyć: dokładny opis odsyłamy do Java Card &
mentem procesu jego budowy jest sama jakość STK Applet Development Guidelines).
wytwarzanego kodu. Można przytoczyć szereg • stosowanie modyfikatora static dla za-
ogólnych rad, tzw. best practices, funkcjonujących inicjalizowanych buforów danych (prze- Pierwszym i najprostszym krokiem do za-
w środowisku programistów, które pozwalają na chowujących na przykład teksty wy- spokojenia wyżej wymienionych wyma-
osiągnięcie wysokiej jakości kodu. Stosowanie się świetlane w komunikatach STK), gań jest z pewnością kierowanie się dobry-
do tych praktyk czy wytycznych przez dewelo- • obsługa współbieżności (tzw. reentrance) w mi praktykami programistycznymi. Zale-
perów jest szczególnie ważne przy programowa- STK, czyli zwrócenie uwagi, że sesja STK cenia dla programistów Java Card są tutaj
niu w technologii Java Card. Możliwości aktuali- zainicjowana przez jedną aplikację STK praktycznie identyczne jak dla inżynierów
zacji apletu nagranego na kartę i udostępnione- może zostać przerwana na czas wykony- pracujących z wykorzystaniem innych tech-
go użytkownikowi są praktycznie żadne, a po- wania sesji zainicjowanej przez inną apli- nologii. Przede wszystkim podkreśla się ko-
pełnianie błędów w implementacji może w naj- kację, nieczność:
gorszym przypadku doprowadzić do nieodwra- • stosowanie się do zaleceń opisanych w
calnego uszkodzenia karty. Ostrzeżenia o ko- dokumentach 3GPP 31.130 ((U)SIM • wykorzystywania metod API Java Card
nieczności dokładnego stosowania się do zale- API) i/lub TS 101476 (GSM API) doty- wszędzie, gdzie jest to możliwe, zamiast
ceń, np. odnośnie użycia pamięci w Java Card, czących dostępności określonych obiek- wprowadzania własnych implementacji,

186 SDJ Extra 34 Biblia


Wzorce projektowe w Java Card

������������������������� �����������
��������������
�������������������
������������������������������

����������������
������������������������� ������ �������������������������������������������
��������������������������������

��������
�����

������������������� ������
����������������������
����������������������
�������
���������
���������������� ������������������������������������
�����������������������������������

������ ������
�����������������

���������������������������������������������
������������������ �����������������
�����������������������������������������������

�������������������������� ���������������
�������������� ������������������������

����������������

Rysunek 1. Diagram klas apletu Java Card używającego wzorców projektowych

• tworzenia krótkich metod, co poprawia rzystywać, projektując i implementując opro- gdy weźmiemy pod uwagę ograniczenia wyni-
czytelność i pielęgnowalność kodu, gramowanie dla kart inteligentnych. W dal- kające z zastosowania tej technologii.
• nie przekraczanie liczby 256 metod na szej części artykułu przyjrzymy się, jakie wzor- Do przedstawienia kolejnych wzorców, jakie
klasę z uwagi na ograniczenia pamięci ce można z powodzeniem stosować w apletach mogą być wykorzystane w apletach Java Card
EEPROM oraz także ze względu na po- Java Card oraz jak wygląda ich implementacja, posłuży, zaprezentowany na Rysunku 1, dia-
prawność projektu apletu (należy mieć
na względzie separację odpowiedzialno-
ści klas w aplecie). ������� ��������������� �������������������� �����������

�������������
Wzorce projektowe
����������������
Jednym z najistotniejszych elementów na dro-
dze do osiągnięcia wysokiej jakości oprogramo- �����������������������
wania jest zastosowanie wzorców projektowych ������������������
wszędzie tam, gdzie okazuje się, że określony ������
problem można uogólnić do schematu definio-
���������������������
wanego przez określony wzorzec. Pozwala to za- ��������������������
równo na utrzymanie prostego i zrozumiałego
modelu systemu, jak i na wygodniejszą i praw-
�������������������������������
dopodobnie lepszą implementację. Poza tym
�������������������������������
oszczędność czasu, jaką daje nam zastosowanie
wzorców projektowych w porównaniu z ponow- ��������������������
nym odkrywaniem koła, ma istotny wpływ na
koszty związane z całym projektem.
�����������
Czy wzorce projektowe można wykorzy- ����������
����������
stać, programując w technologii Java Card? Po-
mimo sygnalizowanej już specyfiki tej techno-
logii odpowiedź z pewnością jest twierdząca.
Co więcej, należałoby powiedzieć, że wzorce
projektowe nie tylko można, ale należy wyko- Rysunek 2. Diagram sekwencji wywołania komendy zmiany kodu PIN

www.sdjournal.org 187
Rozwiązania mobilne

gram przygotowany w języku UML, ilustrujący gramowaniu współbieżnym, jednak w technolo- pakietów APDU (Application Protocol Data
niepełny projekt pewnego apletu. Niniejszy ar- gii Java Card nie mają one zastosowania. Unit). Zachowanie apletu jest wtedy determi-
tykuł został podzielony według przyjętej ogólnie nowane poprzez wartość określonego bajtu
kategoryzacji, która wyróżnia wzorce kreacyjne, Wzorce behawioralne (nazywanego w specyfikacji ISO7816-4 INS).
strukturalne oraz behawioralne. Wyróżnia się tak- Jednym ze sposobów komunikacji z apletem Rozpowszechnioną praktyką (obecną nieste-
że kategorię wzorców wykorzystywanych w pro- Java Card jest komunikacja z wykorzystaniem ty nawet w przykładach kodu udostępnianych
przez producentów kart, takich jak Gemal-
Listing 1. Klasa AuthorizedCommand to) jest użycie tego bajtu do budowy instruk-
cji switch w metodzie przetwarzającej aple-
package pl.mobileexperts.sdj.samples; tu, która wywołuje dalej kod odpowiednich
operacji. Dodatkowo, większość tych operacji
/** przetwarza dane dostarczone w pakiecie AP-
* Root class for commands which need PIN authorization. DU (w polu Data).
*/ Tymczasem taka sytuacja doskonale nadaje
public abstract class AuthorizedCommand implements Command { się do wdrożenia wzorca komendy. Na podsta-
wie przychodzącego do apletu APDU tworzo-
/** ny jest odpowiedni obiekt komendy (zgodnie z
* Execute the command. wartością bajtu INS), którego metoda run jest
* następnie wywoływana z odpowiednimi ar-
* @return number of bytes written to output buffer gumentami w celu dokonania przetwarzania.
*/ Diagram sekwencji takiego wywołania przed-
public final short run(Token token, byte[] buffer, short length, stawiony został na Rysunku 2.
short offset) { Interfejs Command przedstawiony na dia-
short size = 0; gramie klas z Rysunku 1 definiuje interfejs
byte pinLength = buffer[(short) (offset + length - 1)]; wszystkich komend, które ma do dyspozy-
if (token.verifyPIN(buffer, (short) (offset + length - pinLength - 1), cji tworzony aplet. Rzeczywiste komendy
pinLength)) { implementują ten interfejs, definiując kon-
try { kretne czynności, które są wykonywane w
size = runAuthorized(token, buffer, ramach danej komendy.
(short) (length - pinLength - 1), offset); Wprawne oko zauważy jeszcze jeden wzo-
} finally { rzec, który został wykorzystany przy budo-
token.logout(); wie komend przykładowego apletu. Chodzi
} tutaj o wzorzec template method, który został
} else { wykorzystany do zapewnienia uwierzytelnie-
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); nia dla metod dziedziczących po komendzie
} AuthorizedCommand. Komenda ta implemen-
return size; tuje interfejs Command w ten sposób, że wywo-
} łuje najpierw metodę sprawdzania kodu PIN
przekazanego w danych wejściowych, a na-
/** stępnie deleguje dalsze przetwarzanie do abs-
* Perform normal command functioning after PIN authorization. trakcyjnej metody runAuthorized, której im-
*/ plementacja jest dostarczana przez konkret-
protected abstract short runAuthorized(Token token, byte[] buffer, ne klasy dziedziczące po AuthorizedCommand.
short length, short offset); Dzięki temu, implementacja komend wyma-
} gających uwierzytelnienia jest dużo prostsza
i nie wymaga powielania w każdej komen-
dzie kodu sprawdzającego kod PIN. Kod źró-
dłowy komend AuthorizedCommand oraz
ChangePinCommand został zaprezentowany na
���������������������������������������������� Listingu 1 i 2. Warto zwrócić uwagę na ele-
�� � �� � �� � gancję oraz czytelność takiej implementacji,
������������������� które uzyskane zostały dzięki zaproponowa-
nemu podejściu.
������������������������������������������

�� � �� � Wzorce kreacyjne
W poprzedniej sekcji omówiony został sche-
mat, według którego można zastosować wzo-
�� ��������������������� rzec komendy w aplecie Java Card. Jego na-
� ��������������������������������������� turalnym uzupełnieniem jest wykorzystanie
����������������������
wzorca fabryki obiektów, dzięki któremu moż-
liwe jest oddzielenie logiki tworzenia komend
��������������������� od ich uzycia.
Zgodnie ze specyfiką technologii Java Card,
Rysunek 3. Organizacja i zarządzanie pamięcią typu TLV zastosowane fabryki muszą jednocześnie dzia-

188 SDJ Extra 34 Biblia


Wzorce projektowe w Java Card

łać jako repozytoria obiektów (tzw. object pool), Innym przykładem zastosowania wzorca fa- jąc w Java Card spotykamy się z zaleceniem,
gdyż z założenia, cała pamięć wymagana przez sady w programowaniu Java Card jest wyko- aby cała pamięć potencjalnie wykorzystywa-
aplet powinna zostać zaalokowana podczas jego rzystanie w kodzie apletu obiektu klasy de- na przez aplet była zaalokowana podczas jego
instalacji (we wspomnianych wyżej Development dykowanej do zarządzania pamięcią. Jak już instalacji. Należy również pamiętać o braku
Guidelines znajduje się m.in. zalecenie: ALL ob- zostało wspomniane wcześniej, programu- mechanizmu typu garbage collector. Zachodzi
jects making up the application are created during
installation). Dlatego też fabryka komend (Com- Listing 2. Klasa ChangePINCommand
mandFactory) budowanego apletu przechowu-
je referencje do wszystkich wykorzystywanych package pl.mobileexperts.sdj.samples;
przez aplet komend i udostępnia mu je zgodnie import javacard.framework.ISO7816;
ze wzorcem fabryki obiektów. import javacard.framework.ISOException;
Wspomniane ograniczenia, wynikające z import javacard.framework.Util;
modelu zarządzania pamięcią w Java Card, /**
wymuszają także, aby fabryka komend dzia- * Command used to change PIN. Requires at least two bytes of data.
łała jako singleton. Szczególnie w przypadku, * Last byte indicates new PIN length.
gdyby interfejs fabryki został rozszerzony o */
kolejne metody, bardzo wygodna jest możli- public final class ChangePinCommand extends AuthorizedCommand {
wość pobrania referencji do fabryki z dowol- protected short runAuthorized(Token token, byte[] buffer, short length,
nego miejsca kodu bez potrzeby odwoływa- short offset) {
nia się do innych obiektów, które taką refe- // at this moment current PIN was already authorized
rencję mogłyby przechowywać. if (length < 2) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
Wzorce strukturalne }
Wzorzec strukturalny, jaki zastosowany zo- // new PIN length
stał w przykładowym aplecie, wynika niejako byte pinLength = buffer[(short) (offset + length - 1)];
ze specyfiki technologii Java Card. Dostęp do if (pinLength < TokenConstants.MIN_PIN_SIZE ||
operacji na kodach PIN, zarządzanie kluczami pinLength > TokenConstants.MAX_PIN_SIZE) {
czy wykonywanie operacji kryptograficznych ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
odbywa się przez niskopoziomowe API defi- }
niowane przez specyfikację Java Card (obiekty // check if received data has correct length
typu OwnerPIN, Cipher itp.) Aplikacje korzy- if ((short) (length - 1) != Util.makeShort((byte) 0, pinLength)) {
stające z apletu z reguły wykorzystują jedynie ISOException.throwIt(ISO7816.SW_WRONG_DATA);
określoną część tych funkcjonalności. Dodat- }
kowo, bezpośrednie wykorzystanie niskopo- // change PIN
ziomowego API zawsze zaciemnia kod i utrud- token.changePin(buffer, offset, pinLength);
nia jego analizę i późniejszą pielęgnację. Natu- return 0;
ralnym rozwiązaniem dla tego typu sytuacji }
jest zastosowanie wzorca fasady, którego celem }
jest dostarczenie prostszego interfejsu dla bar-
dziej skomplikowanych elementów czy biblio- Listing 3. Klasa Token
tek. W projekcie apletu założono, że obiekty package pl.mobileexperts.sdj.samples;
klasy Token przesłaniają niskopoziomowe API import javacard.framework.ISO7816;
Java Card służące m.in. do operacji przeprowa- import javacard.framework.ISOException;
dzania operacji kryptograficznych. Obiekty import javacard.framework.OwnerPIN;
klasy Token mogą być utożsamiane z wirtual- /**
nymi i hermetycznymi miejscami na karcie in- * An abstraction of a cryptographic token, storing keys and PINs.
teligentnej, w których to przechowywany jest */
materiał kryptograficzny, i które pozwalają na public class Token implements TokenConstants {
wykonywanie określonych operacji (analogicz- private boolean pinInitialized;
nie do pojęcia tokenu zdefiniowanego w spe- private OwnerPIN pin;
cyfikacji RSA PKCS#11 dla bibliotek krypto- public Token() {
graficznych). pin = new OwnerPIN(MIN_PIN_SIZE, MAX_PIN_SIZE);
Komendy wywoływane przez aplet korzy- // ...
stają z fasady w postaci obiektów klasy Token, }
co pozwala na utrzymanie zwięzłości i przej- public boolean verifyPin(byte[] buffer, short offset, byte length) {
rzystości ich kodu. Przykładowo, jak już po- return pinInitialized && (pin.isValidated() ||
kazano wcześniej, zmiana kodu PIN to wy- pin.check(buffer, offset, length));
wołanie odpowiedniej metody na obiekcie }
klasy Token, który to dopiero operuje już bez- public void logout() {
pośrednio na API Java Card. Takie podejście pin.reset();
pozwala na dodatkową strukturalizację i her- }
metyzację poszczególnych elementów budu- // ...
jących aplet, co wpływa na poprawę jakości }
wytwarzanego kodu.

www.sdjournal.org 189
Rozwiązania mobilne

więc potrzeba elastycznego dostępu i zarzą- Przykładem implementacji takiego za- przechowywanych przez zarządcę pamięci,
dzania pamięcią tak, aby była ona wykorzy- rządcy jest wykorzystanie mechanizmu list poprzez który zapisane dane są udostępnia-
stana w sposób optymalny. Najbardziej efek- TLV (tag – length – value). W ten sposób ła- ne. Zastosowanie list TLV zostało pokazane
tywnym sposobem rozwiązania tego proble- two utrzymać ciągłość pamięci i przydzielać na przykładzie usuwania obiektu z pamięci
mu jest stworzenie obiektu zarządcy pamię- ją w optymalny sposób. Obiekty, które korzy- na Rysunku 3.
cią, co może być widziane także jako zastoso- stają z zasobów pamięci, posługują się jedy-
wanie wzorca puli zasobów. nie identyfikatorem (tzw. tagiem) obiektów Czy warto?
Czy warto stosować wzorce projektowe, pro-
Listing 4. Test jednostkowy klasy ChangePINCommand gramując w technologii Java Card? Z pewno-
ścią tak! Oprócz zysków z punktu widzenia
package pl.mobileexperts.sdj.samples; procesu wytwarzania systemów informatycz-
import static org.easymock.EasyMock.*; nych, przy takim podejściu programuje się po
import static org.junit.Assert.*; prostu wygodniej i przyjemniej. Kod jest czy-
import javacard.framework. telny i przejrzysty, a znalezienie miejsca im-
import javacard.framework.*; plementacji określonej funkcjonalności nie
import org.junit.*; wymaga uważnego wpatrywania się w ekran
podczas przewijania setek linii jednej wielkiej
public class ChangePinCommandTest { instrukcji switch... Cały aplet wygląda zgrab-
private Command command; nie i, co najważniejsze, działa!
@Before Poza aspektami estetycznymi pojawia się
public void setUp() throws Exception { jeszcze kilka dodatkowych zalet. Po pierw-
command = new ChangePinCommand(); sze, tak napisany kod apletu łatwo poddaje się
assertNotNull(command); testowaniu z wykorzystaniem bibliotek typu
} JUnit. Konieczność dokładnego testowania
@Test kodu jest równie istotna, jak wszystkie inne
public void testRun() { czynności podczas całego procesu tworzenia
byte oldPinLength = (byte) 3; oprogramowania, o których była mowa wcze-
byte newPinLength = (byte) 4; śniej. Test komendy stworzonej dla przykła-
byte[] buffer = new byte[] { (byte) 1, (byte) 1, (byte) 1, (byte) 1, dowego apletu jest widoczny we fragmencie
newPinLength, (byte) 2, (byte) 2, (byte) 2, oldPinLength }; Listingu 4. Takie testy pozwalają na stosowa-
// record expected behaviour nie sprawdzonego i uznanego podejścia test-
Token token = createMock(Token.class); driven development i pokrycie testami więk-
expect( szości logiki tworzonego appletu.
token.verifyPin(buffer, (short) (newPinLength + 1), Jest oczywiste, że stosowanie dobrych in-
oldPinLength)).andReturn(true); żynierskich zwyczajów, do jakich należy mię-
token.changePin(buffer, (short) 0, newPinLength); dzy innymi stworzenie odpowiedniego mo-
expectLastCall(); delu systemu, używanie wzorców projekto-
token.logout(); wych czy testowanie oprogramowania, jest
expectLastCall(); nieodzownym elementem tworzenia opro-
replay(token); gramowania wysokiej jakości. Co więcej, pro-
// run and verify gramowanie dla tak specyficznej platformy
short result = command.run(token, buffer, (byte) buffer.length, jak Java Card nie może być wymówką do za-
(short) 0); przestania stosowania tych praktyk! Niestety,
assertEquals(result, (short) 0); nawet przykłady kodu prezentowane dewe-
verify(token); loperom przez producentów kart, zawierają
} koszmarne instrukcje switch i kod zamknię-
} ty w jednej klasie… Pozostaje mieć nadzieję,
że ten artykuł choć trochę przekona czytelni-
ków, że można osiągnąć duży przyrost jakości
naprawdę niewielkim nakładem pracy.
W Sieci
• Strona domowa technologii Java Card: http://java.sun.com/javacard/;
• Bieżąca wersja specyfikacji Java Card 2.2.2: http://java.sun.com/javacard/specs.html;
• Dokumentacja on-line do Java Card 2.2.1: http://www.cs.ru.nl/~woj/javacardapi221/; O AUTORACH
• Informacje dla deweloperów Java Card u producenta kart – Gemalto: Leszek Siwik, pracownik naukowy Katedry In-
http://developer.gemalto.com/home/java-card.html;
formatyki Akademii Górniczo - Hutniczej, oraz
• Wskazówki dla deweloperów Java Card/STK: http://developer.gemalto.com/fileadmin/
contrib/downloads/pdf/Java_Card_STK_Applet_Development_Guidelines.pdf; Krzysztof Lewandowski i Adam Woś - architek-
• (U)SIM Application Programming Interface (API); (U)SIM API for Java Card: ci i zarządzający m.in. projektem mobilnego PKI
http://www.3gpp.org/ftp/Specs/html-info/31130.htm realizowanym w Mobile Experts sp. z o.o. Wszy-
• USIM Application Toolkit (USAT): http://www.3gpp.org/ftp/Specs/html-info/31111.htm scy trzej specjalizują się w zagadnieniach szero-
• Dokumenty Stepping Stones organizacji SIMalliance i inne materiały dla deweloperów: ko pojętego oprogramowania mobilnego, a w
http://www.simalliance.org/;
• Repozytorium wzorców projektowych dla języka Java: http://www.javacamp.org/designPattern/; szczególności tematyką wykorzystania urządzeń
• Opis koncepcji list TLV: http://en.wikipedia.org/wiki/Type-length-value. mobilnych w płatnościach, bankowości oraz do
zapewnienia bezpieczeństwa informacji.

190 SDJ Extra 34 Biblia


Rozwiązania mobilne

Open GL ES 1.x
Fakty i mity

Programowanie aplikacji na urządzenia przenośne jest fascynującym


zajęciem. Dodanie im trzeciego wymiaru sprawia jeszcze większą
satysfakcję. Czy tworzenie efektownej, ale i wydajnej grafiki na te
malutkie urządzenia nie jest abstrakcją lub mitem? Jak wykorzystać
moc krzemu, który mieści się w dłoni? Czy można wierzyć standardom?
Oceńcie sami.
związane z różnicami w implementacjach
Dowiesz się: Powinieneś wiedzieć: OpenGL ES na różnych platformach oraz
• Jak wyglądała ewolucja standardu OpenGL • Jak programować w języku C lub C++; sposoby ominięcia niektórych zagrożeń czy
ES i jakie były przesłanki jego utworzenia; • Czym jest OpenGL i jak jest skonstruowany wąskich gardeł. Wszystko to ma w zamiarze
• Jakie są ograniczenia związane z programo- potok graficzny; autora stanowić zbiór użytecznych wskazó-
waniem aplikacji 3D na urządzeniach prze- • Przynajmniej trochę na temat programowa- wek na temat programowania mobilnych
nośnych; nia aplikacji OpenGL na desktopach. światów 3D, ale zacznijmy od początku.
• Jak wykorzystać GLES API do wydajnego ry-
sowania efektownych scen 3D; Jak to się wszystko
• Gdzie szukać potencjalnych wąskich gardeł zaczęło, czyli droga
aplikacji; od desktopów do mobilków
• Jakie są ograniczenia niektórych implemen- Ewolucja grafiki 3D na urządzeniach mobil-
tacji i w jaki sposób można je obejść; nych rozpoczęła się bardzo niewinnie i bez
• Jak wygląda przyszłość grafiki 3D na urzą- zbytniego rozgłosu w 2001 roku. Gdzie?
dzeniach mobilnych. Oczywiście w Japonii. Pierwsze telefony z
wbudowanym silnikiem 3D dostarczył ja-
poński operator komórkowy J-Phone. Silnik
W treści artykułu przyjęto kilka prostych graficzny dostarczony przez HI Corporation
konwencji. W celu skrócenia opisu niektó- był wczesną wersją Mascot Capsule, obecnie
Poziom trudności rych funkcji w nawiasach klamrowych poda- jednego z bardziej rozpoznawanych wielo-
ne są jej warianty dla różnych typów parame- systemowych graficznych API. Potrzeba było
trów wejściowych. I tak: prawie roku, aby pierwsze tego typu urządze-
nia ukazały się poza krajem wiśni. W 2002

J
esteś programistą C/C++, pasjonujesz glColor4{f,x,ub}( T r, T b, T a ); odbyła się premiera Nokii 3410, która pomi-
się grafiką 3D, używałeś już OpenGL’a mo iż posiadała monochromatyczny wyświe-
na desktopach i pragniesz rozpocząć swą oznacza, że funkcja jest zdefiniowana dla tlacz, umożliwiała użytkownikom tworzenie
przygodę z programowaniem aplikacji mo- trzech typów parametrów (odpowiednio trójwymiarowych animacji tekstu, ściąganie
bilnych? A może nie miałeś wiele do czynie- GLfloat, GLfixed, GLubyte). Wszystkie wystą- animowanych wygaszaczy, a nawet zawiera-
nia z programowaniem OpenGL, ale z chęcią pienia T można zastąpić odpowiednim ty- ła wbudowaną grę Java z prostą trójwymia-
to nadrobisz, zaczynając na smart phone’ach? pem parametru z klamerek (tak jakbyśmy rową grafiką!
Czemu nie, mam nadzieję, że każdy znajdzie definiowali template’y): Potrzebę wzbogacenia przenośnych urzą-
tu coś dla siebie. Choć artykuł ten nie jest peł- dzeń w zaawansowaną grafikę 3D dostrzegli
nym kompendium wiedzy na temat OpenGL glColor4f( GLfloat r, GLfloat g, GLfloat b, również producenci middleware’u. Powstało
ES, nie posłuży on nawet jako dokumentacja GLfloat a ); w tym czasie wiele wysoko-poziomowych sil-
standardu, to zawiera kilka ciekawych, a mo- glColor4x( GLfixed r, GLfixed g, GLfixed b, ników graficznych, które nie tylko pozwalały
że nawet zaskakujących wskazówek związa- GLfixed a ); na tworzenie trójwymiarowych scen, ale nie-
nych z tworzeniem aplikacji 3D na urządze- glColor4ub( GLubyte r, GLubyte g, GLubyte rzadko udostępniały narzędzia do odtwarza-
nia przenośne. Jeśli jesteś już doświadczo- b, GLubyte a ); nia animacji, obsługi dźwięków i przechwyty-
nym programistą, obytym w branży mobil- wania zdarzeń od użytkownika. ExEn (Execu-
nej, być może pracujesz nad wysokopozio- Czytając artykuł, napotkacie również ramki tion Engine), Swerve, X-Forge czy mophun to
mowym silnikiem 3D, a może zastanawiasz oznaczone nagłówkiem TIP lub UWAGA; te tylko niektóre z nich. Rozwiązania te, oparte
się, jak zwiększyć wydajność swoich aplikacji, pierwsze zawierają użyteczne informacje na na software’owej rasteryzacji, jakkolwiek bar-
nie musisz czytać całej treści, przejrzyj ramki temat efektywnego wykorzystania API, na- dzo elastyczne, nie pozwalały na dalszy roz-
oznaczone nagłówkiem TIP i UWAGA. tomiast drugie zazwyczaj opisują problemy wój w kierunku szybszego przetwarzania gra-

192 SDJ Extra 34 Biblia


OpenGL ES 1.x

fiki i polepszenia jej finalnej jakości . Korek- Listing 1. Przykładowy kod do obsługi matematyki stałoprzecinkowej, wygenerowane z szablonów
cja perspektywy, cieniowanie Gouraud’a czy funkcje zwracają poprawne (ale niekoniecznie dokładne – w zależności od Q) rezultaty dla Q z zakresu <1,
blending pozostawały raczej tylko na sloga- 31>. Dla przejrzystości implementacji z funkcji usunięto kod testujący przepełnienia (overflow) zmiennych
nach reklamowych producentów (i sferze // Types definitions
marzeń programistów), niż w finalnej aplika- typedef signed char Int8;
cji – były po prostu zbyt kosztowne, aby zrzu- typedef unsigned char UInt8;
cać to na CPU. Ponadto większość pojawiają- typedef signed int Int32;
cych się rozwiązań nie miała wystarczające- typedef long long Int64;
go pokrycia rynku, aby zapewnić rentowność template < UInt8 Q >
bazujących na nich projektów, a także byt ich class FixedTraits {
wydawcom. public:
Duża fragmentacja rynku była wynikiem static UInt8 const SHIFT = Q;
walki o klienta i prześcigania się w nowin- static GLfixed const ZERO = 0;
kach przez producentów telefonów. Jednak static GLfixed const ONE = ( 1 << Q );
sytuacja ta była bardzo niekorzystna dla deve- static GLfixed const TWO = ( 2 << Q );
loperów aplikacji, którzy zostali zmuszeni do static GLfixed const HALF = ( 1 << (Q-1) );
generowania tuzinów build’ów na setki urzą- static GLfixed const FRAC_BITS = (ONE - 1);
dzeń, co skutecznie wydłuża czas develope- static GLfixed const INT_BITS = (~FRAC_BITS);
mentu, jak i zwiększa jego koszty. }; // class FixedTraits
Przemysł zareagował bardzo szybko, naj- template < UInt8 Q >
ważniejsi gracze w tym biznesie zamiast wy- inline float Fixed2Float( GLfixed x)
kańczać się w ciągłym wyścigu technologicz- {
nym, zauważyli potrzebę stworzenia stan- return x / ( (float)( FixedTraits< Q >::ONE ) );
dardu – wspólnego API do tworzenia apli- }
kacji graficznych, które miałoby być punk- template < UInt8 Q >
tem odniesienia również dla producentów inline int Fixed2Int( GLfixed x)
urządzeń. Jako że standard miał być otwar- {
ty, za jego ojca wybrano OpenGL i już w ma- // Simple x >> Q - may be not portable
ju 2002 grupa Khronos ustanowiła sekcję ro- if (x >= 0) return (int)( x >> Q );
boczą (3d4W, 3Dlabs, ARM, ATI, Imagina- else return (int)( ~( (~x) >> Q ) );
tion Technologies, Motorola, Nokia, Seawe- }
ed, SGI i Texas Instruments), której celem template < UInt8 Q >
było zdefiniowanie nowej, odchudzonej wer- inline GLfixed Float2Fixed( float x)
sji API dla urządzeń mobilnych. Rezultatem {
tego była lipcowa premiera OpenGL ES 1.0 return (GLfixed)(x * (float)( FixedTraits< Q >::ONE ) );
(OpenGL for Embeded Systems) na konferencji }
SIGGRAPH 2003, a już rok później pojawiły template < UInt8 Q >
się pierwsze urządzenia z GLES’em na pokła- inline GLfixed Int2Fixed( GLfixed x )
dzie. Przy jego projektowaniu za najważniej- {
sze uznano następujące przesłanki: return ( ( (GLfixed)(x) ) << Q );
}
• otwartość i niezależność od producen- template < UInt8 Q >
tów; inline GLfixed FixedMul( GLfixed val1, GLfixed val2 )
• zapewnienie odpowiedniej warstwy abs- {
trakcji w celu ukrycia przed programi- return (GLfixed)( ((Int64)val1 * (Int64)val2) >> Q );
stami specyfiki konkretnych rozwiązań }
natywnych oraz zagwarantowania wy- template < UInt8 Q >
sokiej przenośności kodu - niezależności inline GLfixed FixedDiv( GLfixed val1, GLfixed val2 )
od platformy; {
• minimalne zużycie zasobów systemo- return (GLfixed)( ( ( (Int64)val1 ) << Q ) / val2 );
wych, jak i ograniczona konsumpcja }
energii (urządzenia przenośne zasilane template < UInt8 Q >
są z baterii); inline GLfixed FixedSqrt( GLfixed val, UInt8 prec = Q / 2 )
• swobodę implementacji – od rozwiązań {
czysto software’owych po sprzętową ak- register GLfixed s;
celerację; // At least one iteration is required
• rozszerzalność i minimalna fragmenta- if( prec == 0 )
cja – ograniczenie opcjonalnych dodat- prec = 1;
ków. s = ( val + FixedTraits< Q >::ONE ) >> 1;
for ( ; prec > 0; --prec )
Kolejny SIGGRAPH 2004 zaowocował in- s = ( s + FixedDiv< Q >(val, s) ) >> 1;
auguracją OpenGL ES w wersji 1.1, któ- return s;
rej celem była lepsza integracja z hardware- }
’em i wprowadzenie nowinek ułatwiających

www.sdjournal.org 193
Rozwiązania mobilne

programowanie gier. Następnym krokiem Mobile i Symbian’ie (niestety producent nawet chip ze wsparciem dla GLES 2.0
było pojawienie się OpenGL ES 2.0 (ma- Hybrid został wykupiony przez NVI- montowany fabrycznie w Samsungu
rzec 2007), bazowanego na desktopowym DIA i w praktyce biblioteka jest trudno Omnia HD;
OpenGL 2.0. Wprowadza on możliwości dostępna); • Biblioteki dostarczane wraz z SDK’a-
programowania potoku graficznego na po- • Vincent, implementacja Open Source mi przez producentów urządzeń: Qu-
ziomie przetwarzania wierzchołków i frag- OpenGL ES 1.1, przeznaczona głównie alcomm BREW OpenGL ES 1.0 Exten-
mentów (oświetlenie, materiały, texturing). na Pocket PC 2003 i Microsoft Smart- sion, Nokia Series 60 (urządzenia z se-
Rezultatem tego jest zastąpienie wielu funk- phone 2003, doczekała się portów rów- rii S60 2nd Ed FP 2 i wyżej posiadają co
cji kontrolujących potok graficzny imple- nież na innych platformach; najmniej implementację software’ową,
mentacją własnych na poziomie vertex i frag- • OpenGL ES 1.0 Linux, przykładowa im- seria N, poczynając od pionierskiej N93,
ment shader’ów. plementacja oparta na OpenGL 1.3; jest wyposażana w sprzętowy akcelera-
• PowerVR OpenGL ES, dostępny na urzą- tor grafiki), SonyEricsson Symbian UIQ
A jak jest dzisiaj? dzeniach z akceleratorami Imagina- 3 SDK;
Obecnie OpenGL ES doczekał się wielu im- tion Technologies. W wersji 1.0 głównie • GLES’a na urządzeniach spod znaku
plementacji zarówno software’owych, jak i sprzęt oparty na Windows Mobile Po- nadgryzionego jabłka (Apple): IPod 5th
sprzętowych, dość wymienić kilka z nich: cket PC 5.0 i 2003. Natomiast w wersji Generation iPod, iPod Nano 3rd oraz
1.1 dostępny na telefonach koncernu So- iPod Classic, iPhone/iTouch;
• Rasteroid, upubliczniona biblioteka ny Ericsson (M600, P990 W950, itd.), • Pre-instalowane biblioteki na PDA i
OpenGL ES 1.1 i OpenVG 1.0, kom- a także Motorola (Z8), Samsung (GT- smartphone’ach.
patybilna z desktopowymi okienkami i8510 i GT-i7110) i Nokia (N93/N95
i urządzeniami opartymi na Windows oraz wiele innych). Ostatnio pojawił się Aktualną listę urządzeń ze wsparciem dla
GLES’a można znaleźć na stronie GLBench-
mark (patrz ramka: W sieci). Oprócz tego
TIP: Fixed point znajdziecie tam wyniki dokładnych testów
W praktyce oszczędności wynikające z użycia funkcji stałoprzecinkowych nie są duże w po-
równaniu do ich zmiennoprzecinkowych zamienników. Wyjątkiem mogą być oczywiście wydajności, a także informacje o wspieranej
funkcje służące przekazujące dane o wierzchołkach (glVertexPointer, itp). Użycie małego wersji API i listę dostępnych rozszerzeń.
formatu do przekazywania wierzchołków powinno zwiększyć wydajność aplikacji (odchudzo-
ny transfer danych, mniejsza złożoność operacji arytmetycznych). Jednak zamiana GLfloat na Trochę numerologii
GLfixed nie zawsze skutkuje oczekiwanym rezultatem. Jakkolwiek ma duże znaczenie w przy-
Zanim rozpoczniemy naszą wycieczkę w głąb
padku software’owych implementacji, to w przypadku urządzeń z akceleracją sprzętową mo-
że być zupełnie odwrotnie! Dzieje się tak dlatego, gdyż niektóre sprzętowe implementacje standardu, warto powiedzieć kilka słów w te-
mogą i tak dokonywać wewnętrznej konwersji liczb stałoprzecinkowych na float (zaintereso- macie wersjonowania OpenGL ES. Numery
wanych odsyłam do artykułu R.S.Wright’a OpenGL and Mobile Devices, patrz: Literatura) wersji możemy podzielić na znaczące/główne
(ang. major) pierwsza cyfra, i mniej znaczące
(ang. minor) po kropce. Specyfikacje w obrę-
bie tej samej wersji głównej 1.x, 2.x są kom-
UWAGA: Rozmiary punktów patybilne wstecz, tak więc aplikacja wykorzy-
Nie można zakładać, że na danej platformie uda nam się narysować punkty o każdej wiel-
kości. Przy włączonym antialiasing’u punkty zostają pomniejszone do najbliższej wspieranej stująca GLES 1.0 powinna się kompilować,
wielkości - można ją odczytać przez glGetInteger(GL _ SMOOTH _ POINT _ SIZE _ RANGE). linkować i uruchamiać na platformie 1.1.
Z kolei GL _ ALIASED _ POINT _ SIZE _ RANGE zwraca zakres rozmiarów bez wygładzania. Nie będzie ona jednak użyteczna na urządze-
Niestety, jedynym wymaganym przez standard wymiarem jest 1. niach z wersją 2.0 na pokładzie.
Choć zdawałoby się, że renderowanie punktów jest najprostszą (a zatem najszybszą) ope-
racją, niektóre (szczególnie sprzętowe) implementacje OpenGL emulują punkty poprzez ry-
sowanie dwóch trójkątów przy wyłączonym wygładzaniu, a maksymalna wielkość punktu z Ile z GL’a pozostało w GLES’ie
włączonym antialiasing’iem często sprowadza się do jednego piksela. OpenGL ES 1.0 bazowany był na desktopo-
wej wersji 1.3 i odziedziczył od swojego po-
przednika sporo funkcjonalności. Jednakże
jest to jego lżejsza wersja i oprócz usunięcia
UWAGA: Linie redundantnych funkcji niektóre punkty spe-
Rysowanie linii może być bardzo przydatne na etapie debugowania aplikacji: rysowanie
przecięć, normalnych, promieni itp. Niestety niektóre (nawet komercyjne) implementacje cyfikacji zostały diametralnie zmienione.
GLES’a opierają się standardom i nie umożliwiają ich rysowania!
Float czy fixed
Choć desktopowy GL jest oparty na arytme-
tyce zmiennoprzecinkowej, większość urzą-
dzeń mobilnych nie ma wsparcia do sprzęto-
TIP: Paski trójkątów wej obsługi float’ów (brak FPU). Dlatego też
Wielu programistów słusznie używa pasków trójkątów do generowania geometrii, lecz baga-
telizuje kolejność definiowania wierzchołków (kierunek zawijania), która determinuje zwrot dodano wsparcie dla nowego typu GLfixed,
normalnej trójkąta, a zatem jego przednią ścianę. Niewidoczne trójkąty (odwrócone tylną który zapisany jest na 32-bitowej wartości cał-
ścianą do obserwatora) rysują, wyłączając lub zmieniając, mechanizm usuwania niewidocz- kowitej. Pierwsze 16 bitów opisuje tu część
nych ścianek: całkowitą liczby ze znakiem, a pozostałe 16
wartość ułamkową - wielkość tę interpretu-
glDisable(GL_CULL_FACE); // Disable back-face culling
glCullFace(GL_FRONT); // Enable culling of front faces je się jako x/2^16. Ilość bitów zarezerwowa-
ną na część ułamkową określa się często jako
Należy wystrzegać się takich skrótów, gdyż prowadzą one do szybkiego spadku wydajności Q, OpenGL ES używa formatu Q16. Listing
aplikacji. 1 zawiera kilka użytecznych funkcji arytme-
tyki stałoprzecinkowej dla dowolnego Q. Ze

194 SDJ Extra 34 Biblia


OpenGL ES 1.x

standardu usunięto również wszystkie funk- Przetwarzanie fragmentów danych w pamięci i konwersję do natyw-
cje obsługujące typ zmiennoprzecinkowy o Ten etap potoku graficznego pozostał pra- nych formatów);
podwójnej precyzji (GLdouble) , zastępując je wie nienaruszony, aczkolwiek bufor szablo- • rozszerzenie (niestety opcjonalne)
wariantami na float’ach. Natomiast dla każ- nu (stencil) zdefiniowano jedynie opcjonal- do wydajnego rysowania grafiki 2D
dej funkcji przyjmującej GLfloat dodano wa- nie. Operacje GL_INCR_WRAP i GL_DECR_WRAP glDrawTexOES – rysowanie odbywa się
riant obsługujący GLfixed. Dane wierzchoł- zostały całkowicie usunięte. tu przez jednostkę teksturowania, więc
ków są przesyłane do potoku tylko za pomo- bitmapy mogą być przechowywane po
cą typów: GLbyte, GLubyte, GLshort, GLfloat Operacje na buforach stronie serwera;
lub GLfixed. Ze względów na ograniczoną wydajność i roz- • point sprites to nowy sposób na rende-
miary docelowej biblioteki na zaszło tu naj- rowanie billboardów 2D i efektów czą-
Brak trybu immediate więcej zmian: steczkowych, więcej na ten temat w dal-
Wersja mobilna GL’a nie pozwala na natych- szej części opracowania;
miastowe (bezpośrednie) przesyłanie wierz- • rendering 2D (glDrawPixels, glBitmap) • mechanizm generowania mip-map
chołków za pomocą komend glBegin, glEnd. został całkowicie wycięty ze standardu (GL _ GENERATE _ MIPMAP);
Wywołania te bardzo komplikowałyby imple- (może być łatwo emulowany przez ma- • rozszerzenie minimalnej wymaganej licz-
mentację maszyny stanów OpenGL, a defi- powanie dwóch trójkątów); by jednostek teksturowania do dwóch;
niowanie geometrii z ich użyciem jest dale- • bufory głębokości i szablonu nie • texture combiners, czyli nowe możliwości
kie od optymalności i w rzeczywistości nie mogą zostać odczytane (usunięto przetwarzania tekstur, operacje na wy-
zalecane nawet w zastosowaniach desktopo- glReadBuffer i glCopyPixels); branych rejestrach wejściowych (kanał
wych. Stąd też dane wierzchołków są przesy- • zrezygnowano z bufora akumulacji; alfa, składowe kolorów) oparte na zesta-
łane w postaci tablic (vertex arrays) za pośred- • odczyt bufora ramki (glReadPixels) wie pre-definiowanych instrukcji (włą-
nictwem glDrawElements czy glDrawArrays, jest wciąż możliwy w dwóch forma- czając mnożenie skalarne wektorów,
zrezygnowano również z display lists. tach pikseli: GL _ RGBA i natywnym GL _ przydatne przy implementacji bump
I M P L E M E N T A T IO N _ C O L O R _ R E A D _ mapping’u);
Stosy macierzy FORMAT _ OES, za to bardzo niewskazany • dynamic state queries, śledzenie zmian
GLES 1.0 wprowadza mniej restrykcyjne wy- (i w rzeczywistości nie zawsze dostępny). stanów maszyny GL w trakcie działania
magania co do wielkości stosów macierzy: aplikacji, dzięki czemu nie jest koniecz-
Nowości w ES 1.1 ne ich zapisywanie po stronie aplikacji
• wymaganą głębokość stosu modelu-wi- Jak już wspomniano, odświeżona wersja – glIsEnabled pozwala na łatwe spraw-
doku (ang. model-view) zmniejszono z 32 GLES 1.1 została przygotowana z myślą o dzenie, czy testowany stan jest aktywny.
do 16; wydajnym wykorzystaniu akceleratorów
• pozostałe stosy (transformacji współ- graficznych. Poczyniono też ukłon w stronę ES w praktyce
rzędnych tekstury i macierzy projekcji) programistów gier i dodano kilka użytecz-
muszą mieścić przynajmniej 2 elementy. nych funkcji. Spośród kilku nowości warto Trochę prymitywnych informacji
wymienić: Rozpoczynając to krótkie wprowadzenie po
Tekstury tajnikach OpenGL ES, trudno nie wspo-
OpenGL ES nie pozwala na odczyt po- • wsparcie dla VBO (Vertex Buffer Object), mnieć o dostępnych prymitywach graficz-
przednio załadowanej tekstury (brak to jest buforów do przechowywania da- nych. Prymitywy w tym kontekście ozna-
glGetTexImage). Multi-texturing aczkolwiek nych wierzchołków po stronie serwera czają najprostsze obiekty graficzne (geome-
pozostał w standardzie, to wymagania co do (odczyt tych buforów nie jest możliwy, tryczne), które służą do budowania całej sce-
ilości jednostek teksturowania ograniczono co pozwala na optymalizację rozkładu ny. Od wersji OpenGL ES 1.0 wspiera ryso-
początkowo (GLES 1.0) do jednej (sic!), a na-
stępnie dwóch (GLES 1.1). Pozostawiono je- Listing 2. Przygotowanie do rysowania point sprites
dynie wsparcie dla tekstur 2D, gdyż jedno-
wymiarowe odpowiedniki są zbyt proste do // Setup point sprites rendering
emulacji, aby doczekały się osobnej imple- glPointSize( 5 );
mentacji. Natomiast tekstury 3D uznano za glEnable( GL_TEXTURE_2D );
zbyt kosztowne obliczeniowo. glActiveTexture( GL_TEXTURE0 );
Tylko pięć najważniejszych formatów tek- glBindTexture( GL_TEXTURE_2D, textureHandle );
stury pozostało w standardzie, wprowadzono glEnable( GL_POINT_SPRITE_OES );
wsparcie dla formatów z paletą oraz możli- glTexEnvi( GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, GL_TRUE );
wość obsługi formatów natywnych dla danej // Draw all points
platformy. Generowanie koordynatów tek- glDrawArrays( GL_POINTS, offset, size );
stury zostało usunięte ze standardu, gdyż ist-
nieje możliwość jego programowej emulacji. Tabela 1. Liczba składowych (per vertex) oraz typy danych wspierane przez mechanizm vertex arrays
(GLES 1.0/1.1)
Kolory i oświetlenie
Vertex array Sizes Types
Specyfikację kolorów wierzchołków ograni-
glVertexPointer 2,3,4 GL_BYTE, GL_SHORT, GL_FIXED, GL_FLOAT
czono do RGBA, indeksowanie kolorów zo-
stało całkowicie usunięte (choć żadna to stra- glTexCoordPointer 2,3,4 GL_BYTE, GL_SHORT, GL_FIXED, GL_FLOAT
ta). Poza tym wprowadzono kilka mniej istot- glNormalPointer 3 GL_BYTE, GL_SHORT, GL_FIXED, GL_FLOAT
nych zmian ograniczających możliwości defi- glColorPointer 4 GL_UNSIGNED_BYTE, GL_FIXED, GL_FLOAT
niowania materiałów – więcej szczegółów w
glPointSizePointerOES 1 GL_FIXED, GL_FLOAT
dalszej części opracowania.

www.sdjournal.org 195
Rozwiązania mobilne

wanie punktów, linii i trójkątów, natomiast glEnable( GL_POINT_SMOOTH ); jest to najlepsze rozwiązanie do rysowa-
wersja 1.1 rozszerza ten zestaw o tak zwane glDisable( GL_POINT_SMOOTH ); nia kilku połączonych segmentów;
point sprites. • GL _ LINE _ STRIP (pasek linii), każdy ko-
W kolejnych akapitach znajdziecie krótki W pierwszym przypadku punkty rysowa- lejny wierzchołek na liście jest łączony li-
opis dostępnych prymitywów. Programiści, ne są jako koła, a piksele brzegowe na ekra- nią z poprzednim;
którzy mają już spore doświadczenia z wersją nie są półprzezroczyste w zależności od stop- • GL _ LINE _ LOOP, podobny do poprzed-
desktopową GL’a, mogą po prostu pominąć nia ich pokrycia przez rysowany punkt. W sy- niego, z tym że pierwszy i ostatni wierz-
tę część, warto jednak rzucić okiem na cieka- tuacji braku wygładzania punkt reprezento- chołek z listy są dodatkowo łączone.
wostki umieszczone w ramkach. wany jest na ekranie przez kwadrat o wielko-
ści zaokrąglonej do najbliższej wartości całko- Podobnie jak punkty, linie posiadają szerokość:
Punkty witej. Gdy zaokrąglenie sprowadza się do ze-
Punkt jako najprostszy prymityw wymaga ra, punkty reprezentowane są w postaci pik- void glLineWidth{fx}(type width);
określenia tylko jednego wierzchołka, acz- sela (wielkość 1).
kolwiek pamiętajmy, że punkt to nie to sa- i mogą być wygładzane przez wykorzystanie
mo, co piksel ekranowy, gdyż oprócz po- Linie antialiasing’u:
łożenia punkt może mieć określoną wiel- Do zdefiniowania najprostszej linii potrzebu-
kość: jemy co najmniej dwóch wierzchołków, wiąże glEnable(GL_LINE_SMOOTH);
się z tym kilka sposobów na ich rysowanie:
void glPointSize{f,x}( T size ); Trójkąty
• GL _ LINES (segmenty), rysowane są W mobilnej wersji GL’a trójkąty są jedyny-
Wartość ta będzie obowiązywać do następ- przez połączenie każdej pary dwóch mi prymitywami, które pozwalają na ryso-
nego wywołania funkcji, a domyślnie wyno- wierzchołków. Oznacza to, iż do na- wanie wypełnionych siatek. Czworokąty
si 1. Rysowanie punktów może odbywać się rysowania dwóch segmentów musimy (GL_QUADS) i wielokąty (GL_POLYGONS) obec-
w dwóch trybach: z wygładzaniem (tzw. an- przesłać 4 wierzchołki (odpowiednio 1, ne co prawda w wersjach desktopowych zo-
tialiasing) lub bez. 2 pierwszy segment i 2, 3 na drugi). Nie stały pominięte w celu odchudzenia imple-
mentacji. Istnieją trzy sposoby definiowania
geometrii na podstawie trójkątów:
UWAGA: Point sprites
Przycinanie point sprites w obszarze widoku realizowane jest w ten sposób, iż jeśli transfor-
mowana pozycja punktu jest poza polem widzenia, cały sprite jest wycinany z potoku (nieza- • GL _ TRIANGLES, każdy trójkąt przesyła-
leżnie od jego wielkości). Ułatwia to wydajną implementację na poziomie API, jednak stwa- ny jest do potoku niezależnie – definiu-
rza problemy programistom aplikacji – cząsteczki znikają zbyt wcześnie! Rozwiązaniem te- ją go zawsze 3 wierzchołki (2 trójkąty =
go problemu jest ustawienie widoku (glViewport) na rozmiar większy od wielkości ekranu 6 wierzchołków). Jest to najmniej wydaj-
(o połowę wielkości największego sprite’a) oraz ustawienie testu nożyc (scissors test) do roz-
na metoda, która pozwala na definiowa-
miarów ekranu:
nie dowolnych siatek, a nawet luźno roz-
glViewport( -PS_SIZE/2, -PS_SIZE/2, dispW+PS_SIZE/2, dispH+PS_SIZE/2 ); rzuconych trójkątów. Niestety, jeśli trój-
glEnable( GL_SCISSOR_TEST ); kąty współdzielą ze sobą wierzchołki (są
glScissor( 0, 0, dispW, dispH ); połączone w logiczną całość), mamy do
czynienia z redundancją danych;
Niestety większość dostępnych implementacji ma problemy z przycinaniem sprite’ów w sytu-
acji rozszerzonego widoku lub z rysowaniem point sprites wogóle.
• GL _ TRIANGLE _ STRIP, paski trójkątów
umożliwiają tworzenie siatek z połączo-
nych trójkątów, w której każdy z nich
współdzieli dwa wierzchołki z sąsia-
dem. Pierwsze 3 wierzchołki z listy de-
TIP: Systemy cząstek a point sprites finiują pierwszy trójkąt, a każdy następ-
Pomimo braku rozszerzenia glPointSizePointerOES w GLES 1.0 i niektórych implementa-
cjach 1.1, nie jesteśmy zupełnie bezradni (przynajmniej w teorii). Przesyłanie pojedynczych ny wierzchołek generuje nowy trójkąt
punktów z każdorazowym ustawieniem domyślnej wielkości (glPointSize) jest również złożony z tego wierzchołka i dwóch po-
pewnym rozwiązaniem. Oczywiście ciągłe wysyłanie tak małych porcji danych stworzy nie- przednich. Metoda ta jest dużo szybsza
potrzebny ruch na szynie, stawiając pod znakiem zapytania wydajność tej metody. Oczywi- od GL _ TRIANGLES, wymaga jednak or-
ście pod GLES 1.0, który nie wspiera teksturowania punktów, implementacja systemów czą- ganizacji listy wierzchołków i zwrócenia
stek opartych na kolorowych punktach ma niewiele sensownych zastosowań. Jedynym wyj-
ściem może być implementacja własnych billboardów (złożonych z dwóch trójkątów – patrz
szczególnej uwagi na kolejność ich zawi-
ramka: W sieci) lub w przypadku GLES 1.1 skorzystanie z rozszerzenia glDrawTexOES (o nim jania;
w dalszej części artykułu). • GL _ TRIANGLE _ FAN, wachlarz trójką-
tów, idea podobna do poprzedniej z tą
różnicą, że każdy kolejny wierzchołek
(poczynając od trzeciego) generuje no-
TIP: Vertex arrays wy trójkąt przez dodanie poprzedniego
Przechowywanie vertex arrays po stronie aplikacji implikuje dwa istotne fakty. Umożliwia
to modyfikowanie zawartości tablic (ale nie ich rozmiarów) w trakcie wykonywania progra- i pierwszego wierzchołka z listy.
mu. Animowanie kolorów wierzchołków, zmiana współrzędnych tekstury czy animacja sia-
tek oparta na vertex’ach nie wymagają dodatkowych odwołań do API - dane te są zawsze ko- Billboardy, systemy cząstek,
piowane z pamięci klienta przed narysowaniem geometrii. Choć można by to uznać za istot- czyli krótka saga o point sprites
ną zaletę tego rozwiązania, kopiowanie w każdej ramce geometrii statycznych siatek jest
mocno nadmiarowe i nie pozwala na optymalizację. Ponadto obszar pamięci wykorzysty-
i ich wielkości
wany przez tablice nie może zostać zwolniony dopóki są one wykorzystywane do renderin- Wykorzystanie tak zwanych billboardów po-
gu sceny. zwala na uzyskanie ciekawych efektów gra-
ficznych, a w szczególności efektów cząstecz-

196 SDJ Extra 34 Biblia


OpenGL ES 1.x

kowych (ang. particle effects). Umożliwia to przekazując do niej tablicę {a, b, c} (para- użytkownika, zakres ten można ustalić, uży-
symulację wielu zjawisk występujących we metr coefTabb). W praktyce ustalenie war- wając funkcji:
wszechświecie: dym, obłoki, ogień, śnieg, tości współczynników na {1, 0, 0} (usta-
deszcz, iskry, rozbryzgi wody itp. (Rysunek wienie domyślne) wyłącza wygaszanie. void glPointParameter{fx}(
2 przedstawia kilka przykładów zastosowań Points size arrays, czyli tablice wielkości GL_POINT_SIZE_MIN, T param);
systemów cząstek). W niektórych przypad- punktów, umożliwiają całkowicie dowolne void glPointParameter{fx}(
kach dwuwymiarowe billboardy mogą za- precyzowanie rozmiarów sprite’ów. Pozwa- GL_POINT_SIZE_MAX, T param);
stąpić nawet rzeczywistą geometrię (drzewa, la to na implementację bardziej subtelnych
krzaki, trawa, patrz Rysunek 3). Począwszy efektów cząsteczkowych. Tablica wielkości Na koniec wielkość sprite’ów (czy też punk-
od GLES 1.1 obiekty tego typu mogą być ren- punktów jest zapisywana po stronie klienta tów) może być jeszcze modyfikowana przez
derowane za pomocą tzw. point sprites, czyli poprzez wywołanie glPointSizePointerOES, bibliotekę, jeśli wykracza ona poza rozmia-
teksturowanych punktów o określonej wiel- aby aktywować jej użycie, należy dodać ry wspierane przez implementację (warto
kości zawsze zwróconych w stronę kamery. glEnableClientState(GL_POINT_SIZE_ więc sprawdzić, jakie są dozwolone rozmia-
Oczywiście używanie ich do symulowania ARRAY_OES) – więcej na ten temat w części ry punktów na platformie docelowej). Ilu-
drzew sprawdzi się tylko wtedy, gdy kame- poświęconej vertex arrays. Tablice rozmiarów struje to poniższy pseudokod:
ra porusza się jedynie w dwóch wymiarach mogą być używane jednocześnie z ich wy-
(np. po powierzchni terenu), w innym przy- gaszaniem (points attenuation, patrz wyżej), screen_size = implementation_clamp (
padku, gdy obserwator unosi się nad billbo- wielkość zapisana w tablicy stanowi wtedy user_clamp ( att_size ) );
ardami, powstaje bardzo nieprzyjemny efekt bazę do pomniejszania. Oznacza to, że mana-
kładzenia się obiektów. Do tego typu symula- ger cząstek może obsługiwać ich skalowanie w Przekazywanie geometrii
cji stosuje się billboardy sferyczne, które obra- czasie (na przykład powiększające się obłoki do potoku renderującego
cają się tylko w jednej osi i konieczna jest ich dymu), podczas gdy zmniejszaniem sprite’ów
własna implementacja (patrz ramka: W sieci). zgodnie z odległością od obserwatora (skrót Vertex arrays, jak wycisnąć soki z GLES 1.0
Rozszerzenie point sprites ma w zamyśle zastą- perspektywiczny) zajmie się OpenGL. OpenGL ES 1.0 definiuje tylko jeden sposób
pić mało wydajne tworzenie billboardów na Wyjściowy rozmiar punktu (już po przesyłania geometrii do potoku renderują-
bazie trójkątów. Czy jest ono faktycznie tak przekształceniach GL_POINT_DISTANCE_ cego, wszystkie atrybuty wierzchołków (po-
pomocne, osądzicie sami, ale najpierw kilka ATTENUATION) jest przycinany do zakresu zycja, kolor, koordynaty tekstury itp.) mu-
słów o interfejsie.
Aby rozpocząć rysowanie sprite’ów, na-
leży aktywować odpowiedni stan, używa-
TIP: Wydajność tablic wierzchołków a formaty danych
Format danych przechowywanych w tablicach wierzchołków może mieć duży wpływ na
jąc tokena GL_POINT_SPRITE_OES, urucho- wydajność aplikacji. Użycie pamięciożernych (a więc i dokładniejszych) typów powoduje
mić teksturowanie i ustawić środowisko dla większe obciążenie zasobów systemowych zwłaszcza przy przesyłaniu geometrii do GPU,
aktywnej jednostki teksturowania na GL_ może być też powodem mało efektywnego wykorzystania rejestrów procesora i cache mis-
COORD_REPLACE_OES (przykład – Listing 2). ses. Szczególnie implementacje software’owe nie lubią obszernych typów, a w szczegól-
ności formatów zmiennoprzecinkowych w warunkach braku FPU. Choć większość imple-
Od tej pory wszystkie punkty przesyła-
mentacji sprzętowych oparta jest na w pełni zmiennoprzecinkowym potoku, aby ograni-
ne do serwera graficznego będą rysowane czyć transfer danych, warto i tu zastanowić się nad naszymi potrzebami. Używanie GL _
w formie teksturowanych billboardów. Ich FLOAT do przekazywania kolorów wierzchołków jest sporym nadużyciem – w zupełności
wielkość może być określona odgórnie dla wystarczy GL _ UNSIGNED _ BYTE . Jeśli to tylko jest możliwe, warto zamienić float na by-
całej tablicy przesyłanej do serwera (przez te lub short przy przekazywaniu koordynatów tekstury. Zmniejszanie formatu danych ma
oczywiście sens tylko wtedy, jeśli sterownik nie wykonuje zbyt kosztownych konwersji da-
wywołanie glPointSize, opisane w sekcji
nych do natywnego formatu.
poświęconej punktom) lub, co jest nowo-
ścią w GLES 1.1, indywidualnie dla każde-
go wierzchołka.
Możliwość definiowania indywidualnych
wielkości punktów jest sporym krokiem na-
TIP: Wykorzystanie wartości domyślnych
Wartości domyślne, choć z pozoru proste rozwiązanie, otwierają olbrzymie pole do optyma-
przód w kontekście tworzenia systemów czą- lizacji. Nie chodzi tu jedynie o zmarnowane linie kodu i redundancje danych. Jeśli jakikolwiek
steczkowych. Począwszy od GLES 1.1 istnie- z atrybutów (normalna, kolor, koordynaty tekstury) nie ulega zmianie, nie musimy zapychać
ją dwa sposoby modyfikowania rozmiarów szyny przesyłaniem tych samych wartości. Co więcej, sterownik graficzny może efektywniej
punktów w obrębie jednej tablicy: points at- wykorzystywać zawartość rejestrów i pamięci podręcznej.
tenuation i points size arrays.
Points attenuation, czyli wygaszanie wielko-
ści wraz z odległością od obserwatora, odby-
TIP: Indeksowanie wierzchołków
wa się z użyciem formuły: Użycie indeksowanych tablic wierzchołków pozwala na zastosowanie bardziej wyszukanych
technik optymalizacji. Nie znając algorytmów wykorzystania pamięci podręcznej przez ste-
att_size = base_size * sqrt(1 / (a + b*d + rownik graficzny, najlepiej przekazać mu dane w jak najbardziej przystępnej postaci. Najlep-
c*d*d)); sze rezultaty osiągniemy, jeśli dane przetwarzanego trójkąta mogą być wykorzystane do na-
rysowania kolejnego trójkąta na liście; unikniemy wtedy ciągłego przeładowywania reje-
strów (czy pamięci podręcznej). Relację tę możemy uzyskać, sortując listę trójkątów w ten
gdzie d jest odległością od obserwatora w sposób, aby powtarzające się indeksy wierzchołków sąsiadowały ze sobą na liście, a kolejne
przestrzeni widoku. Pozostałe współczynni- trójkąty wykorzystywały jak najwięcej wspólnych danych. Należy przy tym pamiętać o kolej-
ki równania ustalamy za pomocą funkcji: ności definiowania wierzchołków w obrębie trójkąta (vertex order). Jednym z prostszych spo-
sobów optymalizacji listy może być posortowanie trójkątów po średniej z indeksów wierz-
void glPointParameter{fx}v( chołków. Optymalizacje te powinniśmy wprowadzać już na etapie eksportowania geometrii
do odpowiednich formatów, a nie podczas ładowania aplikacji.
GL_POINT_DISTANCE_ATTENUATION,
const T* coeffTab);

www.sdjournal.org 197
Rozwiązania mobilne

szą być przekazane w formie tablic. Mecha- dwa {x,y} lub trzy {x,y,z} koordynaty. Tabli- dane wierzchołków. Pamiętajmy, iż wymie-
nizm ten (tzw. vertex arrays) definiuje prosty ce normalnych i wielkości punktów nie po- niona tu funkcja glPointSizePointerOES
zestaw funkcji, który pozwala na ustawienie siadają tego parametru, gdyż normalne za- została wprowadzona jako opcjonalne roz-
wskaźników do tablic atrybutów przechowy- wsze określamy w trzech wymiarach, a roz- szerzenie dopiero w wersji 1.1 standardu,
wanych po stronie klienta. miar punktu jest wielkością skalarną (jed- więcej na jej temat w sekcji poświęconej ry-
Funkcje do obsługi vertex arrays mają po- nowymiarową). Parametr stride określa sowaniu point sprites.
dobną strukturę dla każdego typu atrybu- odległość (w bajtach) pomiędzy poszcze- Wykorzystanie upakowanych tablic wierz-
tów, różnice objawiają się w obsługiwanych gólnymi elementami tablicy. Pozwala to na chołków (stride większy od zera) choć nie
formatach danych: używanie jednego bloku pamięci do okre- prowadzi do mniejszego zużycia pamięci,
ślania wielu atrybutów wierzchołków, na może skutkować wzrostem wydajności apli-
void glColorPointer(GLint size, przykład w formacie {pozycja, normalna, kacji. Ułożenie wszystkich atrybutów jedne-
GLenum type, GLsizei stride, pozycja normalna, ...}. Wartość stride jest go wierzchołka w spójnym obszarze pamięci
GLvoid* pointer); używana do adresowania kolejnych ele- pozwala sterownikowi na efektywniejsze za-
void glTexCoordPointer(GLint size, mentów określonego atrybutu (normalna, rządzanie pamięcią (cache-coherency).
GLenum type, GLsizei stride, kolor itp.) w obrębie spakowanej tablicy. Aby konkretna tablica atrybutów zosta-
GLvoid* pointer); Stride równy zero oznacza, że przekazuje- ła wykorzystana w potoku graficznym, nale-
void glVertexPointer(GLint size, my wskaźnik do tablicy jednego atrybutu, ży uruchomić odpowiedni stan klienta, uży-
GLenum type, GLsizei stride, kolejne wystąpienia tego atrybutu w tablicy wając funkcji:
GLvoid* pointer); nie są rozdzielone przez inne dane. Z kolei
void glNormalPointer(GLenum type, gdy chcemy użyć upakowanej tablicy kilku void glEnableClientState(GLenum cap);
GLsizei stride, GLvoid* pointer); atrybutów, podajemy tu wartości większe
// Starting from ES 1.1 (optional od zera. Przykładowo użycie jednej tabli- analogicznie możemy (i powinniśmy) wyłą-
extension) cy do określenia pozycji i normalnych zde- czyć ją z przetwarzania (po wykonaniu ryso-
void glPointSizePointerOES( GLenum type, finiowanych trzema składowymi typu GL _ wania) poprzez:
Lsizei stride, GLvoid* pointer); SHORT wymaga przekazania stride równe-
go 6 (3 * sizeof(GL _ SHORT)). Parametr void glDisableClientState(GLenum cap);
Parametr size określa tu wymiarowość da- type określa format przechowywanych da-
nych, dla tablicy współrzędnych wierzchoł- nych (GL _ BYTE, GL _ SHORT, etc.), patrz: Do obu funkcji przekazujemy typ tabli-
ków (glVertexPointer) może to być 2 lub 3 Tabela 1. Natomiast pointer to wskaźnik cy: GL_COLOR_ARRAY, GL_NORMAL_ARRAY, GL_
– pozycja jest określana przez odpowiednio do obszaru pamięci zawierającego właściwe TEXTURE_COORD_ARRAY, GL_VERTEX_ARRAY, GL_
POINT_SIZE_ARRAY_OES. Domyślnie wszystkie
tablice są wyłączone.
UWAGA: VBO różnice w implementacjach Dodatkowego wyjaśnienia wymaga uży-
Choć standard pozwala na nadpisywanie pamięci VBO, rzeczywistość jest trochę inna. Wie-
le implementacji narzuca pewne ograniczenia co do realokacji bufora. Możemy spotkać się z cie tablicy koordynatów tekstury, ponieważ
sytuacją, gdzie bufory nie mogą być rozszerzane. Oznacza to, iż jeśli raz zostanie dla nich za- OpenGL ES wspiera multi-texturing, przed
alokowany pewien obszar pamięci, nie będzie można przekazać nowego bloku o większej ich ustawieniem musimy poinformować
objętości. Obejście tego problemu może wymagać implementacji własnej puli buforów i za- maszynę, która jednostka teksturowania jest
rządzania nimi na poziomie aplikacji.
używana, dotyczy to wywołań:

glEnableClientState(GL_TEXTURE_COORD_ARRAY),
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
TIP: Czy zawsze warto używać VBO?
Zdawałoby się, że użycie buforów z parametrem GL _ DYNAMIC _ DRAW nie wnosi wiele w
porównaniu ze standardowym mechanizmem tablic wierzchołków (vertex arrays). Sterow- oraz
nik poinformowany o zamiarze częstego odświeżania bufora nie powinien dokonywać kosz-
towych optymalizacji reprezentacji danych. Jakkolwiek dane są wciąż przechowywane po glTexCoordPointer.
stronie serwera, a wykorzystanie niezmienionego bufora (lub nawet jego części) w dwóch
cyklach pozwala na pewne oszczędności. Poprawę wydajności aplikacji można też uzyskać
Służy do tego:
przez użycie tego samego bufora kilkakrotnie w jednej ramce. Na przykład, gdy renderujemy
wiele klonów tego samego modelu lub część opisu geometrii, powtarza się dla wielu obiek-
tów. Nawet dynamiczne modele mogą współdzielić sporo danych, na przykład bufor koloru i void glClientActiveTexture(GLenum texture);
koordynatów tekstury, podczas gdy pozycje wierzchołków są animowane.
gdzie texture definiuje aktywowaną jed-
nostkę teksturowania (GL _ TEXTURE0,
GL _ TEXTURE1, aż do GL _ MAX _ TEXTURE _
TIP: Wydajny update VBO UNITS). Pamiętajmy, że GLES 1.0 gwarantu-
Preferuj użycie glBufferSubData nad glBufferData , kiedy tylko możesz, nawet gdy nad-
pisujesz zawartość całego bufora. Użycie tej funkcji nie powoduje realokacji obszaru pamię- je wsparcie tylko dla jednej jednostki tekstu-
ci dla bieżącego VBO, podczas gdy glBufferData spowoduje zwolnienie całej pamięci i za- rowania, a wersja 1.1 dla dwóch, reszta jak
alokowanie jej na nowo. Niektóre sterowniki mogłyby wykryć sytuacje, w których bufor jest zwykle zależy od implementacji.
odświeżany z tym samym zestawem parametrów (ta sama wielkość i sposób użycia), ale nie
polegaj na ich implementacji. Wartości domyślne
Warto rozważyć użycie kilku buforów (double buffering, triple buffering) dla wierzchołków,
które podlegają animacji – są odświeżane co ramkę, a już na pewno należy unikać kilkukrot-
Oprócz przekazywania danych geometrii
nego nadpisywania bufora w jednej ramce. Nadpisanie danych bufora, które są wciąż uży- przez tablice OpenGL ES (już od wersji 1.0)
wane w potoku renderowania, powoduje wymuszenie synchronizacji i wstrzymanie wyko- umożliwia ustawienie wartości domyślnych
nywania do momentu przetworzenia przez silnik graficzny nadpisywanego obszaru. dla niektórych atrybutów (normalnych, ko-
loru i koordynatów tekstury):

198 SDJ Extra 34 Biblia


OpenGL ES 1.x

void glNormal3{fx}(T nx, T ny, T nz); tów, powielany jest tylko jego indeks, a nie Wywołanie tej funkcji powoduje utworzenie
void glColor4{fx ub}(T red, T green, wszystkie atrybuty (więcej w ramce: Indek- n nazw i zapisanie ich w tablicy przekazanej
T blue, T alpha); sowanie wierzchołków). wskaźnikiem bufferNames (oczywiście w ge-
void glMultiTexCoord4{fx}(GLenum target, stii użytkownika jest, aby tablica ta miała od-
T s, T t, T r, T q); Vertex buffer objects, powiedni rozmiar). Nazwy te są od tej pory
ukłon w stronę hardware’u rozpoznawalne przez maszynę stanów i zazna-
Jeśli któraś z tablic nie została aktywowana OpenGL ES 1.1 w ramach lepszej integra- czone jako używane. Wywołanie to nie tworzy
przez wywołanie glEnableClientState , zo- cji z hardware’m wprowadza mechanizm jeszcze żadnych buforów, więc muszą one zo-
staną użyte wartości domyślne, dzięki cze- pozwalający na przechowywanie danych stać wygenerowane i aktywowane za pomocą:
mu nie musimy za każdym razem definio- wierzchołków po stronie serwera rende-
wać tego samego koloru wierzchołków czy ringu. Mechanizm ten, zwany vertex buf- void glBindBuffer(GLenum target,
przekazywać tę samą normalną na płaskiej fer objects, eliminuje konieczność ciągłego GLuint bufferName);
powierzchni (patrz ramka: Wykorzystanie przesyłania danych geometrii od klienta do
wartości domyślnych). serwera w każdej ramce aplikacji (jak ma to gdzie do target przekazujemy token GL _
miejsce przy użyciu vertex arrays w wersji ARRAY _ BUFFER (bufor tablicy wierzchołków
Rendering prymitywów 1.0). Ponadto pozwala sterownikom na we- – do obsługi vertex arrays) lub GL _ ELEMENT _
z wykorzystaniem vertex arrays wnętrzną konwersję danych do typów bar- ARRAY _ BUFFER (bufor do przechowywania
Po ustawieniu wskaźników na tablice wierz- dziej przyjaznych dla hardware’u, a także na indeksów), a bufferName jest uchwytem (na-
chołków można przystąpić do właściwego ry- lepszy rozkład ich w pamięci w celu wydaj- zwą) obiektu bufora. Funkcja ta tworzy nowy
sowania prymitywów. Za pomocą funkcji: nej adresacji. bufor pod daną nazwą lub wiąże już istnieją-
Obsługa VBO jest dość prosta i zaczyna się cy z wybranym celem (target). Jeżeli przeka-
void glDrawArrays(GLenum mode, GLint first, od utworzenia uchwytów (nazw) do obiek- zany identyfikator bufora jest większy od ze-
GLsizei count); tów bufforów: ra i nie jest skojarzony z żadnym VBO, two-
rzony jest nowy bufor o zerowej wielkości. W
przekazujemy wszystkie tablice do potoku void glGenBuffers( GLsizei n, przypadku gdy buferName wskazuje na ist-
renderującego, oczywiście wcześniej musi- GLuint* bufferNames ); niejący już obiekt, silnik ustawia go jako bie-
my zdefiniować co najmniej tablicę pozy-
cji wierzchołków. Jeśli któryś z atrybutów Listing 3. Wykorzystanie VBO do renderowania geometrii
nie ma zdefiniowanej tablicy, zostaną uży-
te wartości domyślne. Do rysowania zosta- // Assuming that we have two tables defined
nie użytych count elementów, zaczynając // GLshort verts[] = { … };
od elementu o indeksie first. Z kolei wartość // GLbyte normals[] = { … };
mode determinuje sposób rysowania prymi-
tywów (temat ten poruszony został w sek- // Create buffer handles
cji poświęconej prymitywom, patrz : Rysu- GLuint vboHandles[2];
nek 1): GL_POINTS, GL_LINES, GL_LINE_LOOP, glGenBuffers( 2, vboHandles );
GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_
STRIP, GL_TRIANGLE_FAN. // Load some vertex data into the first VBO
Należy zwrócić uwagę, iż wywołanie glBindBuffer( GL_ARRAY_BUFFER, vboHandles[0] );
glDrawArrays wymaga odpowiedniego uło- glBufferData( GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW );
żenia wierzchołków w tablicy, zgodnie z // Load vertices’ normals into the second VBO
wybranym sposobem definiowania prymi- glBindBuffer( GL_ARRAY_BUFFER, vboHandles[1] );
tywów, i może prowadzić do dużej redun- glBufferData( GL_ARRAY_BUFFER, sizeof(normals), normals, GL_STATIC_DRAW);
dancji danych. Dotyczy to w szczególno-
ści: GL_LINES, GL_TRIANGLES. Dlatego nale- glEnableClientState( GL_VERTEX_ARRAY );
ży używać tej metody głównie do rysowania glEnableClientState( GL_NORMAL_ARRAY );
punktów, pasków i wachlarzy trójkątów (GL_
TRIANGLE_STRIP, GL_TRIANGLE_FAN) lub li- // Setup attribute arrays pointers
nii. W pozostałych przypadkach dużo efek- glBindBuffer( GL_ARRAY_BUFFER, vboHandles[0] );
tywniejsze jest wykorzystanie indeksowania glVertexPointer( 2, GL_SHORT, 0, NULL );
wierzchołków za pomocą funkcji:
glBindBuffer( GL_ARRAY_BUFFER, vboHandles[1] );
void glDrawElements(GLenum mode, GLsize glNormalPointer( GL_BYTE, 0, NULL );
count, GLenum type, const GLvoid* idx);
// Setup global color attribute
gdzie mode odpowiada za typ rysowanych glColor4x( 1 << 16, 0, 0, 1 << 16 );
prymitywów, count określa liczbę indek-
sów do przetwarzania, type definiuje typ // Ignore first 2 vertices, draw 2 triangles
indeksu (GL _ UNSIGNED _ BYTE lub GL _ glDrawArrays( GL_TRIANGLE_FAN, 2, 4 );
UNSIGNED _ SHORT), a idx jest po prostu
wskaźnikiem do tablicy indeksów. Choć me- // Unbind VBO
toda ta używa dodatkowej tablicy do adreso- glBindBuffer( GL_ARRAY_BUFFER, 0 );
wania tablic wierzchołków, jeśli dany wierz-
chołek jest współdzielony przez kilka trójką-

www.sdjournal.org 199
Rozwiązania mobilne

żący dla danego celu (bez tworzenia nowe- do określania atrybutów wierzchołków (o tym GL_STATIC_DRAW lub GL_DYNAMIC_DRAW. W
go VBO). W innym przypadku, gdy wartość za chwilę), skojarzenie to zostanie zerwane. Po pierwszym przypadku informuje on sterownik,
uchwytu wynosi zero, maszyna odpina bieżą- utworzeniu nazw i właściwych buforów można iż dane przekazane do bufora nie będą zmienia-
cy bufor związany z podanym celem. Innymi przystąpić do wypełnienia ich danymi. Funkcja: ne (nadpisywane), co umożliwia ich wewnętrz-
słowy wartość zero jest zarezerwowana, nie ną optymalizację (lepsze rozłożenie w pamięci
odpowiada ona żadnemu domyślnemu bu- void glBufferData(GLenum target, itp.). Z kolei GL_DYNAMIC_DRAW oznacza, że za-
forowi, a jedynie służy do zaznaczenia, iż nie GLsizeiptr size, const GLvoid* data, wartość bufora może ulegać częstej modyfika-
zamierzamy używać VBO do obsługi dane- GLenum usage); cji – przy każdym odświeżeniu ramki, a nawet
go celu i wracamy do użycia pamięci klien- podczas jednego przebiegu renderingu (prze-
ta (czyli w rzeczywistości vertex arrays, ale o służy do wypełnienia aktywnego bufora sko- czytaj tip: Czy zawsze warto używać VBO?).
tym później). jarzonego z celem target danymi o wielko- GLES 1.1 pozwala również na nadpisanie
Usuwanie istniejących już buforów i zwal- ści size bajtów, zaczynając od adresu data. tylko części danych bieżącego VBO, służy do
nianie ich zasobów odbywa się przez wywo- Oznacza to alokację obszaru pamięci po tego funkcja:
łanie funkcji: stronie serwera i skopiowanie przekazanych
danych. Jeśli aktualny bufor zawierał już ja- void glBufferSubData(GLenum target,
void glDeleteBuffers(GLsizei n, kieś dane, są one automatycznie zwolnione i GLintptr offset, GLsizeiptr size,
const GLuint* bufferNames); nowy obszar powinien zostać zaalokowany const GLvoid* data);
(sprawdź ramkę: VBO różnice w implementa-
Funkcja ta zwalnia n buforów o identyfikato- cjach). W praktyce po wyjściu z tej funkcji Większość parametrów tej funkcji jest tożsa-
rach z tablicy bufferNames. Oznacza to, że za- pamięć aplikacji (pod adresem data) może ma glBufferData, na uwagę zasługuję brak
alokowana dla nich pamięć po stronie serwera zostać zwolniona, oczywiście jeśli nie chce- parametru usage, gdyż nie możemy zmie-
zostaje zwolniona, a nazwy zwrócone do po- my jej już wykorzystywać – np. modyfiko- nić sposobu użycia zaalokowanego już bufo-
nownego użycia (lista nazw, czyli obsługiwa- wać danych wierzchołków. ra (patrz ramka: Wydajny update VBO). Do-
nych buforów, jest ograniczona). Jeżeli który- Osobnego wyjaśnienia wymaga para- datkowo pojawia się tu offset, który wyzna-
kolwiek ze zwalnianych buforów był używany metr usage, który może przyjmować wartości cza odległość w bajtach (w obszarze bufora)
do początku bloku, który zamierzamy nad-
pisać. Wywołanie to oczywiście nie pozwala
na rozszerzenie rozmiaru bufora, dlatego je-
śli offset + size, przekracza jego wielkość,
generowany jest błąd GL _ INVALID _ VALUE,
a dane nie są w ogóle kopiowane.

Rysowanie prymitywów z użyciem VBO


Po zainicjalizowaniu obiektu bufora i zapisaniu
do niego danych można go wykorzystać do defi-
niowania dowolnych atrybutów wierzchołków
(patrz sekcja o vertex arrays). Podpięcie bufora
Rysunek 1. Rodzaje prymitywów i kolejność definiowania ich wierzchołków. Źródło: K.Pulli, T.Aarnio, do tablicy wierzchołków dokonuje się za pomo-
V.Miettinen, K.Roimela, J.Vaarala; Mobile 3D Graphics with OpenGL ES and M3G cą funkcji z rodziny vertex array pointer (glVer-
texPointer, glTexCoordPointer). Zmienia
się natomiast znaczenie ostatniego parame-
tru tych funkcji. Nie jest on interpretowany ja-
ko wskaźnik na dane wierzchołków w obszarze
pamięci klienta, lecz oznacza on offset w obsza-
rze aktualnie podpiętego bufora, od którego bę-
dą przekazywane dane. Innymi słowy określa
on ilość pomijanych danych w obszarze bufora
przy odczycie odpowiedniego atrybutu. Ozna-
cza to, że wywołanie:

void glNormalPointer(GLint size,


GLenum type, GLsizei stride,
GLvoid* pointer);

w GLES 1.1 może mieć różne znaczenie,


w zależności od tego, czy w danej chwili
do GL _ ARRAY _ BUFFER jest podpięty jakiś
VBO. Standardowe działanie tych funkcji
przywraca wypięcie aktywnego bufora tabli-
cy glBindBuffer(GL _ ARRAY _ BUFFER, 0).
Dlatego dobrą praktyką jest wyłączanie bu-
forów zaraz po ich użyciu.
Rysunek 2. Przykłady zastosowania systemów cząstek opartych na billboardach. Źródło: Podobnie jak w przypadku standardo-
opracowanie własne wych vertex arrays, wykorzystanie parame-

200 SDJ Extra 34 Biblia


OpenGL ES 1.x

tru stride pozwala na pakowanie danych o Mapowanie tekstur stawiają swe pierwsze kroki z GL’em, odwo-
wierzchołkach do jednego bufora, dzięki cze- Ze względu na wciąż ograniczone możliwo- łuję do red book’a – patrz Literatura: OpenGL
mu można wykorzystać jeden bufor do opisu ści konfiguracji potoku renderującego mapo- SuperBible). Warto jednak przypomnieć, że
kilku atrybutów. wanie tekstur odgrywa znaczącą rolę w GLES GLES 1.x nie ma wsparcia dla tekstur jedno-
Po związaniu buforów z odpowiednimi ta- 1.x. Koncepcyjnie wiele się tu nie zmieniło w i trójwymiarowych, dlatego też podaję tu sta-
blicami atrybutów można przystąpić do wła- porównaniu z jego desktopowym odpowied- łą GL _ TEXTURE _ 2D do parametru celu w
ściwego rysowania prymitywów. Odbywa się nikiem, jednak kilka detali wymaga krótkiego glBindTexture, dotyczy to wszystkich funkcji,
to dokładnie tak samo jak w przypadku zwy- omówienia, gdyż mogą być przyczyną wielu w których podajemy wymiarowość tekstury.
kłych tablic, za pomocą funkcji: nieprzespanych nocy podczas przesiadki z de- W GLES dane tekstury są przechowy-
sktopów. Zacznijmy jednak od początku. wane po stronie serwera (znów analogia do
void glDrawArrays(GLenum mode, GLint first, Mapowanie tekstur odbywa się na etapie ra- VBO), co oznacza , że za każdym razem, gdy
GLsizei count); steryzacji trójkątów, aczkolwiek niektóre im- zapisujemy bitmapę do obiektu tekstury,
plementacje odwlekają ten proces na koniec po- dokonuje się ich kopiowanie. Jest to operacja
Listing 3 powinien nieco bardziej rozjaśnić toku, aby uniknąć niepotrzebnej obróbki frag- potencjalnie czasochłonna, gdyż większość
meandry zastosowania vertex buffer objects. mentów odrzuconych przez test bufora głębo- implementacji dokonuje na tym etapie kon-
kości czy nożyc. Zarządzanie teksturami przy- wersji obrazu do natywnego formatu sprzy-
VBO i indeksowane tablice pomina obsługę VBO, bitmapy są przechowy- jającemu szybkiemu mapowaniu.
wierzchołków wane w obiektach tekstur, do których odwołu- Jakby tego było mało, samo kopiowa-
Obiekty buforów mogą być również używane je się, używając ich nazw (uchwytów). Prosty ze- nie danych do serwera jest już wąskim gar-
do indeksowania wierzchołków (a właściwie staw funkcji pozwala na tworzenie, aktywowa- dłem, dlatego należy poczynić starania,
tablic wszystkich atrybutów). Służą do tego nie (podpinanie) i usuwanie obiektów tekstury: aby raz załadowaną mapę wykorzystywać
specjalne typy VBO – bufory indeksów (GL_ jak najdłużej – rozwiązaniem mogą być
ELEMENT_ARRAY_BUFFER). Zarządzanie bufo- void glGenTextures( GLsizei n, atlasy tekstur. Funkcja:
rem indeksów odbywa się w analogiczny spo- GLuint* textureNames );
sób jak obsługa bufora tablicy. Jedyna różnica void glBindTexture( GL_TEXTURE_2D, void glTexImage2D( GL_TEXTURE_2D,
polega na tym, iż jako cel zastosowania bufora GLuint textureName ); GLint level, GLenum internalformat,
(target) zamiast GL_ARRAY_BUFFER podajemy void glDeleteTextures( GLsizei n, GLsizei width, GLsizei height,
GL_ELEMENT_ARRAY_BUFFER w funkcjach do je- const GLuint* texturesNames ); GLint border, GLenum format,
go obsługi: glBindBuffer, glBufferData, and GLenum type, const GLvoid* pixels );
glBufferSubData. Po przygotowaniu takiego Funkcje te działają tak samo jak ich deskto-
VBO można go wykorzystać do renderowania powe odpowiedniki, więc nie warto poświę- kopiuje dane bitmapy z pamięci aplikacji do
geometrii przez wywołanie glDrawElements cać im więcej uwagi (programistów, którzy obiektu tekstury po stronie serwera. Funkcja
(patrz sekcja dotycząca vertex arrays). Jed-
nakże i tu zmienia się nieco interpretacja pa- Tabela 2. Formaty tekstury i wspierane dla nich formaty pikseli
rametrów tej funkcji. Jeżeli do jednostki GL_ Format tekstury Format piksela
ELEMENT_ARRAY_BUFFER jest przypięty obiekt
GL_LUMINANCE GL_UNSIGNED_BYTE
bufora, to ostatni parametr glDrawElements
wyznacza offset w obszarze bufora, od którego GL_ALPHA GL_UNSIGNED_BYTE
ma się rozpocząć indeksowanie prymitywów: GL_LUMINANCE_ALPHA GL_UNSIGNED_BYTE
GL_RGB GL_UNSIGNED_BYTE
void glDrawElements(GLenum mode, GL_UNSIGNED_SHORT_5_6_5
GLsize count, GLenum type, GL_RGBA GL_UNSIGNED_BYTE
const GLvoid*offset); GL_UNSIGNED_SHORT_4_4_4_4
GL_UNSIGNED_SHORT_5_5_5_1
Aby przywrócić standardową obsługę tej
funkcji, należy odpiąć aktywny bufor in-
deksacji:

glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );

Rysowanie prymitywów z użyciem


glDrawElements i bufora indeksów pozwa-
la na wydajne indeksowanie wszystkich ak-
tywnych tablic atrybutów (co najmniej GL _ Rysunek 3. OpenGL ES w akcji, screeny z prac nad TigerWoods09 iPod. Iluzoryczny efekt oświetlenia
VERTEX _ ARRAY musi być ustawiona, w in- uzyskano, obliczając składową diffuse per vertex na etapie ładowania geometrii i przekazując ją do
koloru wierzchołków. Ilu billboardów użyto w tych scenach? Źródło: opracowanie własne
nym przypadku prymitywy nie zostaną
skonstruowane). Oczywiście najlepsze efek-
ty osiągniemy, gdy do obsługi tych tablic zo- TIP: Atlasy tekstur
staną wykorzystane również obiekty bufo- Atlasy tekstur są powszechnie używaną metodą pakowania kilku obrazów w jedną większą
rów. Listing 4 prezentuje prosty przykład in- mapę. Oszczędności z nimi związane nie wynikają jedynie z bardziej efektywnego wykorzy-
deksowania wierzchołków z użyciem VBO. stania pamięci tekstury, minimalizacja wielkości niewykorzystanych obszarów tekstury zwią-
zanych z ograniczeniem POT. Zyskujemy również na przełączaniu między mapami, a nawet
Prezentowany kod rysuje cztery trójkąty, na szybszym dostępie do ich danych, gdyż nowoczesne układy graficzne mogą przetrzymy-
których dane załadowano do bufora wierz- wać cały atlas w pamięci podręcznej.
chołków i koordynatów tekstury.

www.sdjournal.org 201
Rozwiązania mobilne

ta, choć pochodzi z wersji desktopowej GL’a, tości parametrów internalformat i format bela 2). Szerokość i wysokość tekstury muszą
ma jednak sporo ograniczeń. Mobilny GL nie muszą być zawsze takie same (brak konwersji zawsze być potęgą dwójki (power of two – w
wspiera obramówek tekstury, stąd też border formatów tekstury), natomiast type, czyli for- skrócie POT). W GLES nie istnieje bezpośred-
może przyjmować tylko wartość zero. War- mat pikseli, został mocno okrojony (patrz: Ta- ni sposób na stworzenie tekstury z obrazu, któ-
ry nie spełnia tego warunku (brak wsparcia dla
Listing 4. Przykład użycia VBO do indeksowania wierzchołków GL _ PACK _ ROW _ LENGTH i GL _ PACK _ ROW _
LENGTH). Można jednak obejść to ogranicze-
// Assuming that vboHandles[0] is a handle to vertex positions buffer, nie, kopiując dane bitmapy do większego bufo-
// vboHandles[1] points to texture coordinates vbo. ra o rozmiarach POT, a następnie ładując go do
obiektu tekstury. Sposób ten ma jednak istot-
// indexTable forms a list of indexes to vertex attributes defined ną wadę: obraz o rozmiarach 129x257 będzie
// in mentioned buffers reprezentowany przez teksturę 256x512, czy-
// GLubyte indexTable[] = { 0, 1, 2, … }; li prawie czterokrotnie większą, co się wiąże z
transferem dużej porcji danych i mocno nad-
// Use VBO for vertex positions miarowym zużyciem pamięci. Inną meto-
glEnableClientState( GL_VERTEX_ARRAY ); dą jest logiczny podział bitmapy na mniejsze
glBindBuffer( GL_ARRAY_BUFFER, vboHandles[0] ); fragmenty o rozmiarach POT, obszary te ko-
glVertexPointer( 2, GL_BYTE, 0, NULL ); piujemy do tymczasowego bufora, a następ-
nie do obiektu tekstury za pomocą wywołania
// Set client-side active texture unit glTexSubImage2D. Jest szansa, że oszczędzimy
glClientActiveTexture( GL_TEXTURE0 ); nieco na transferze danych. Jednak w obu wa-
// Use VBO for texture coordinates definition riantach dość szybko może się okazać, iż nie
glEnableClientState( GL_TEXTURE_COORD_ARRAY ); dysponujemy odpowiednią ilością pamięci vi-
glBindBuffer( GL_ARRAY_BUFFER, vboHandles [1] ); deo do załadowania dużych obiektów tekstu-
glTexCoordPointer( 2, GL_BYTE, 0, NULL ); ry. Najlepszym rozwiązaniem jest upakowywa-
nie kilku obrazów do jednej tekstury w ten spo-
// Load vertex indices into newly created VBO sób, aby maksymalnie wykorzystać zaalokowa-
GLuint vboIndHandle; ny dla niej obszar. Można to robić już na etapie
glGenBuffers( 1, &vboIndHandle ); eksportu obrazów do specjalnie przygotowane-
go formatu. Format ten nazywany jest często
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vboIndHandle ); atlasem tekstur (patrz ramka: Atlasy tekstur).
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(indexTable), &indexTable, GL_STATIC_ Domyślnie rozmiar wiersza bitmapy prze-
DRAW ); kazywanej do funkcji glTexImage2D musi
być wielokrotnością słowa (word), co w prak-
// Bind VBO used for indexing tyce oznacza, iż musi zawierać parzystą wie-
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, vboIndHandle ); lokrotność 4 bajtów. Zachowanie to można
// Draw four triangles using 12 indexes zmienić, używając funkcji:
glDrawElements( GL_TRIANGLES, 12, GL_UNSIGNED_BYTE, NULL );
void glPixelStorei( GL_UNPACK_ALIGNMENT,
// Unbind all buffer objects GLint param );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); Jeśli wiersze bitmapy zawierają niewyrówna-
ny ciąg bajtów, wartości param ustala się na
jeden (brak wyrównania), aczkolwiek może
to powodować odczuwalne spowolnienie na
niektórych platformach (głównie sprzęto-
UWAGA: 16-bitowe formaty tekstury wych). Pozostałe dozwolone wartości to: 2
Przy ładowaniu danych tekstury (glTexImage2D) należy z dużą ostrożnością używać for-
matów pikseli innych niż GL _ UNSIGNED _ BYTE . Formaty dwubajtowe (GL _ UNSIGNED _ (wyrównanie do 2 bajtów), 4 (słowo), 8 (wy-
SHORT _ x) będą odczytywane przez bibliotekę, używając natywnego wskaźnika na równanie do 8 bajtów).
unsigned short, a rozkład bajtów (endianess) na wykorzystywanej platformie może odbie-
gać od formatu (byte-order) danych zapisanych w bitmapie, to jest natywnego układu uży- Jeszcze więcej różnic
wanego przez narzędzia eksportujące obrazy. W takim przypadku należy zamienić miejsca- Warto jeszcze wspomnieć o kilku ogranicze-
mi każde dwa bajty w ładowanej bitmapie przed uploade’m do obiektu tekstury.
niach, z którymi możemy się spotkać na nie-
których specyficznych platformach. Choć nie
są one już tak oczywiste, mogą powodować
spore komplikacje, szczególnie w przypadku
UWAGA: Definiowanie materiałów subtelne różnice przenoszenia kodu z desktopów lub pisania
GLES nie pozwala na określenie własności materiałów osobno dla przednich i tylnych ścianek
geometrii, stąd też użycie tokena GL _ FRONT _ AND _ BACK we wszystkich wersjach funkcji wieloplatformowej aplikacji:
glMaterial.
Składowe materiałów inne niż ambient i diffuse w wersji mobilnej GL’a nie mogą być definio- • zdarza się, że implementacja nie pozwa-
wane per vertex, choć było to możliwe w wersji desktopowej (brak możliwości lokalnej kon- la na realokację i rozszerzanie obiektu
troli rozbłysków). Jednak wiele efektów oświetleniowych może być symulowanych przez
tekstury, raz zaalokowana przestrzeń
użycie multi-texturing’u. W mobilnej wersji pominięto również mechanizm indeksowania ko-
lorów. dla danej nazwy nie może zostać zwol-
niona bez resetowania GL’a. Oznacza

202 SDJ Extra 34 Biblia


OpenGL ES 1.x

to, że kolejne upload’y bitmapy pod da- chołka. W rezultacie kolory wierzchołków DIFFUSE, GL _ SPECULAR, GL _ EMISSION.
ną nazwą tekstury muszą mieć rozmiar implikują finalną barwę fragmentów po- Natomiast params to wskaźnik na tablicę
mniejszy lub równy pierwszej alokacji; wstałych z danego prymitywu (oczywiście koloru tej składowej (RGBA). Inna wersja
• specyfikacja OpenGL ES nie nakłada w sytuacji wyłączonego oświetlenia). Gene- tej funkcji pozwala ustawić intensywność
restrykcji co do dokładności interpola- rowanie fragmentów może przyjąć tu dwie rozbłysku (shininess):
cji koordynatów tekstury, przez co nie- formy, w zależności od aktywnego trybu
które implementacje mogą wykonywać cieniowania: void glMaterial{fx}( GL_FRONT_AND_BACK,
tylko szybką interpolację w przestrze- GL_SHININESS, T param );
ni ekranu, pomijając w ten sposób znie- void glShadeModel(GLenum mode);
kształcenie perspektywy (perspective cor- gdzie param przyjmuje wartości z zakre-
rection). Jeśli efekty są rażące, można sto- W przypadku trybu GL _ FLAT, wszystkie su 0-128.
sować większą taselację trójkątów (Rysu- fragmenty powstałe z rysowanego prymity- Oprócz ustalenia materiału dla całej ryso-
nek 4); wu przyjmują kolor jego ostatniego wierz- wanej geometrii, GLES pozwala także na de-
• potencjalnie kosztowna interpolacja chołka. Z kolei tryb GL _ SMOOTH (cieniowa- finiowanie składowych ambient i diffuse dla
mip-map może być niedostępna, a zastę- nie Gouraud’a) pozwala na interpolację ko- każdego wierzchołka z osobna. Wymaga to
puje ją wybór najbliższej mip-map'y. loru fragmentów (również koloru będące- aktywacji stanu:
go wypadkową oświetlenia i użytych ma-
Rozszerzenie Draw Texture teriałów), dzięki czemu uzyskujemy płynne glEnable( GL_COLOR_MATERIAL );
Pomimo wielu ograniczeń, dowodem na to, przejścia koloru między wierzchołkami.
iż konsorcjum standaryzacyjne OpenGL ES Prosty model cieniowania, z użyciem jedy- Od tej pory kolory wierzchołków przekazy-
kierowało się przede wszystkim wydajnością nie kolorów, nie sprawdza się, jeśli dążymy wane przez tablicę kolorów (glColorPoin-
biblioteki, jest wprowadzone w wersji 1.1 do osiągnięcia bardziej subtelnych efektów ter) będą używane do określenia tych skła-
rozszerzenie glDrawTexOES. Występująca w odwzorowujących oświetlenie. Włączenie dowych (zobacz też ramkę: Definiowanie ma-
kilku wariantach funkcja: oświetlenia, glEnable(GL_LIGHTING), impli- teriałów subtelne różnice).
kuje konieczność określenia materiałów dla Model oświetlenia odziedziczony z wer-
void glDrawTex{sifx}OES(T x, T y, T z, rysowanych geometrii. Funkcja: sji OpenGL 1.3 pozostał praktycznie nie-
T width, T height); zmieniony, również tutaj mamy możli-
void glDrawTex{sifx}vOES(const T * coords); void glMaterial{fx}v( GL_FRONT_AND_BACK, wość ustawiania świateł punktowych,
GLenum pname, const T* params ); kierunkowych i stożkowych. Wywołanie
wykorzystuje najszybszy dla danej platformy funkcji:
sposób rysowania dwuwymiarowych prosto- definiuje składowe materiału, które bę-
kątów. W wersjach software’owych może to dą użyte w równaniu oświetlenia. Paramet glLight{x,f}v(GLenum light, GL_POSITION,
być nawet bezpośrednie blit’owanie do bufo- pname określa nazwę składowej materiału const T* pos);
ra ramki. Natomiast implementacje sprzęto- (po więcej informacji odwołuję do OpenGL
we korzystają raczej z pasków trójkątów. Roz- SuperBible, patrz Literatura): GL _ AMBIENT, ustawia pozycję światła we współrzęd-
szerzenie to jest szczególnie użyteczne do ry- GL _ DIFFUSE, GL _ AMBIENT _ AND _ nych homogenicznych (cztery składowe x,
sowania elementów GUI aplikacji i pozwala
na bardzo proste rysowanie teksturowanych
prostokątów. Pozycja prostokąta x, y (określa-
na jako położenie lewego-dolnego narożnika)
i jego rozmiary (width, height) zdefiniowane
są już we współrzędnych ekranowych. Para-
metr z przyjmuje wartości od 0 do 1 i określa Rysunek 4. Taselacja trójkątów
głębokość rysowanego prostokąta, co pozwala
na łatwe układanie kilku warstw GUI.

Kolory, materiały, oświetlenie ��������������


Kolorowanie wierzchołków w GLES niewie- ��������������

le się różni od desktopowego pierwowzoru.


Jak już wspomniano przy omawianiu tablic
������ ���������
wierzchołków i VBO, definicję kolorów dla �������������
������ ��������
poszczególnych wierzchołków przesyła się za
pomocą funkcji:
���
�������
������
void glColor4{fx ub}( T red, T green, T
blue, T alpha );
void glColorPointer( GLint size, �������� ������������
�����������
GLenum type, GLsizei stride, ������ ����������
GLvoid* pointer );

Mając na uwadze, iż pierwsza z nich usta-


wia domyślny kolor prymitywów, które nie
mają zdefiniowanej tablicy kolorów, a druga Rysunek 5. Programowalny potok graficzny w OpenGL ES 2.0. Źródło: A.Munshi, D.Ginsburg,
funkcja definiuje kolory dla każdego wierz- D.Shreiner; OpenGL ES 2.0 Programming Guide

www.sdjournal.org 203
Rozwiązania mobilne

y, z, w). O typie światła decyduje tu czwar- void glLightModel{fx}v( GL_LIGHT_MODEL_ dzeniem rozpoczęła prace ukierunkowa-
ta składowa podawana w tablicy pozycji, AMBIENT, const T* ne na lepszą integrację API z akcelerato-
jeżeli jest ona równa zero, światło inter- rgba ) rami grafiki. Efektem tego było pojawie-
pretowane jest jako kierunkowe (umiesz- void glLightModel{fx}( GL_LIGHT_MODEL_TWO_ nie się w roku 2007 nowej wersji standar-
czone nieskończenie daleko w kierun- SIDE, T(1) ) du – OpenGL ES 2.0. Najnowsza wersja
ku określonym przez x, y, z), a w równa- GL’a czerpie swe korzenie z desktopowego
niu oświetlenia nie uwzględnia się współ- Gdy zdecydujemy się na użycie modelu odpowiednika OpenGL 2.0 i jest ukierun-
czynników wygaszania (GL _ CONSTANT _ oświetlenia obu stron trójkątów, do oblicza- kowana tylko na urządzenia wyposażone
ATTENUATION, GL _ LINEAR _ ATTENUATION, nia ich koloru jest używana ta sama normal- w programowalne układy graficzne (telefo-
GL _ QUADRATIC _ ATTENUATION). Świa- na (nie możemy zdefiniować osobnych nor- ny, PDA, instrumenty lotnicze, nawigacyj-
tło punktowe definiuje się, podając w = malnych dla przednich i tylnych ścian trój- ne itp.). GLES 2.0 wprowadza świeże spoj-
1, pozycja światła jest wtedy uwzględnia- kąta). Różnica polega na tym, iż w przypad- rzenie na potok graficzny, w którym eta-
na przy obliczaniu oświetlenia i obliczane ku ścianek odwróconych tyłem do obser- py przetwarzania wierzchołków i kolorów
jest wygaszanie (zobacz też ramkę: Pozycja watora normalne są odwracane (przeciwny fragmentów są definiowane (programowa-
światła – względna czy bezwzględna). Stoż- zwrot) zanim zostaną uwzględnione w rów- ne) przez programistę aplikacji (patrz: Ry-
kowe źródła światła definiuje się, ustawia- naniu oświetlenia. sunek 5). W związku z tą zmianą, standard
jąc parametry GL _ SPOT _ DIRECTION i składa się z dwóch uzupełniających się spe-
GL _ SPOT _ CUTOFF, tak więc nic się tutaj Przyszłość cyfikacji:
nie zmieniło w stosunku do desktopów. OpenGL ES – kierunki rozwoju
Przykład pozycjonowania świateł w scenie Ostatnie lata zaowocowały błyskawicznym • OpenGL ES 2.0 API – biblioteka do
przedstawia Listing 5. postępem w sferze urządzeń przenośnych, obsługi standardowego potoku graficz-
Należy wspomnieć, że GLES udostępnia większość nowych smart phone’ów wyposa- nego;
również globalny model światła otaczającego żona została w specjalizowane układy gra- • OpenGL ES Shading Language
oraz możliwość oświetlania przednich i tyl- ficzne. Grupa Khronos, przewidując roz- (OpenGL ES SL) – specyfikacja języka
nych ścianek trójkątów: wój sytuacji rynkowej, ze sporym wyprze- programowania potoku (vertex shaders,
fragment shaders).
TIP: Pozycja światła – względna czy bezwzględna Choć nowa specyfikacja dziedziczy wie-
Należy pamiętać, iż pozycja światła jest transformowana przez bieżącą macierz GL _
MODELVIEW, wobec czego można nim manipulować jak zwykłym punktem w przestrzeni le cech po swoim desktopowym pierwo-
3D. Jeżeli w trakcie ustawiania jego pozycji macierz model-view jest macierzą jednostko- wzorze, również i tu nie zapomniano o
wą, światło będzie zawsze podążać za obserwatorem. Listing 5 prezentuje kilka sposobów ograniczeniach platformy docelowej. Aby
lokalizacji oświetlenia. uszczuplić wielkość binariów mobilnej bi-
blioteki, zrezygnowano z redundantnych
funkcji, ograniczając ich zestaw do najbar-
Listing 5. Pozycjonowanie różnych typów świateł w scenie dziej pożądanego minimum pozwalające-
go na osiągnięcie tych samych rezultatów
#define Int2Fx( a ) ( ( a ) << 16 ) (wciąż nie znajdziemy tu display lists czy
GLfixed lightPos0[] = { 0, 0, 0, Int2Fx(1) }; trybu immediate). Dla osiągnięcia maksy-
GLfixed lightPos1[] = { 0, 0, Int2Fx(1), 0 }; malnej wydajności shader’ów i redukcji po-
GLfixed lightPos2[] = { Int2Fx(2), 0, 0, Int2Fx(1) }; boru mocy (zużycia baterii) wprowadzono
glMatrixMode( GL_MODELVIEW ); kilka trybów precyzji. Nareszcie lepsza in-
glLoadIdentity(); tegracja z hardware’em pozwala na progra-
mowanie efektów zarezerwowanych do-
// Light0 is directional light shining towards -Z (negative) camera axis. tychczas tylko dla dużych platform. Na-
glEnable( GL_LIGHT0 ); leży spodziewać się, że w przyszłości bę-
glLightxv(GL_LIGHT0, GL_POSITION, lightPos0 ); dzie pojawiać się coraz więcej urządzeń
ze wsparciem dla GLES 2.0, które już te-
// Light1 - point light always located in camera position raz można spotkać w sprzedaży (Samsung
glEnable( GL_LIGHT1 ); Omnia HD).
glLightxv(GL_LIGHT1, GL_POSITION, lightPos1 ); Wprowadzenie programowalnego po-
toku graficznego otwiera nowe możliwo-
// Camera transformations ści tworzenia realistycznych efektów gra-
glRotatex( Int2Fx(30), 0, Int2Fx(1), 0 ); ficznych, a kompilacja shader’ów w trakcie
glTranslatex( 0, 0, -Int2Fx(20) ); wykonywania programu pozwala na dużą
elastyczność i zwiększa przenośność kodu.
// Setup point light in word coordinates Niestety nie wszystko wygląda tak różowo
glEnable( GL_LIGHT3 ); jakby się zdawało. Kompilacja programów
glLightxv(GL_LIGHT3, GL_POSITION, lightPos2 ); vertex’ów i fragment’ów wydłuży czasy łado-
wania aplikacji, a wynikowy kod maszyno-
wy może nie być tak wydajny jak pre-kom-
// Render entire scene here pilowane (na desktopie) binaria. Może to
// DrawScene(); doprowadzić do sytuacji, w której przeno-
śna aplikacja (czy wieloplatformowy fra-
mework) będzie musiała obsługiwać wiele

204 SDJ Extra 34 Biblia


OpenGL ES 1.x

wersji pre-kompilowanych shader’ów. Ze może być sporym wyzwaniem, nie tylko Podsumowanie
względu na wydajność niektóre implemen- dla początkujących programistów. Choć W tekście artykułu zostały przedstawione
tacje GLES 2.0 będą wspierały tylko formy podjęto już próby rozwiązania proble- kluczowe aspekty wykorzystania OpenGL
binarne. Kompilacja off-line będzie wyma- mów przenośności i kompatybilności ko- ES w programowaniu aplikacji mobilnych.
gała od developerów obsługi wielu narzę- du (OpenC na Symbian'ie, Lightblue pod Niestety, opisanie całego standardu wyma-
dzi od różnych producentów (MakeBina- BREW), wciąż brakuje uniwersalnego gałoby napisania sporej wielkości książki,
ryShader, BinaryShader.lib dostarczane API. Na szczęście również w tej materii dlatego ilość podstawowych informacji sta-
przez AMD czy PVRUniSCo od Imagina- grupa Khronos podjęła się próby wypro- rałem się ograniczyć do niezbędnego mini-
tion Technologies). wadzenia standardu. W lutym 2008 roku mum (sekcja dotycząca prymitywów). Ze
Niestety, kompilacja shader’ów nie zosta- opublikowano specyfikację OpenKODE względu na dużą dostępność materiałów na
nie jedynym problemem stojącym przed 1.0, która wprowadza zestaw API (włą- temat desktopowego GL’a starałem się tu ra-
programistami aplikacji mobilnych. Rynek czając w to OpenGL ES i EGL’a) do komu- czej uchwycić tylko najważniejsze różnice
ten jest mocno sfragmentowany i do ko- nikacji z docelowym systemem. KODE pomiędzy OpenGL ES 1.x a jego desktopo-
mercyjnego sukcesu potrzeba wsparcia dla Core przykrywa większość funkcji zwią- wym pierwowzorem, kładąc szczególny na-
wielu OS’ów i różnych środowisk (BREW, zanych z alokacją pamięci, dostępem do cisk na problemy związane z wydajnością.
Symbian, Windows Mobile, Linux itd.). plików i sieci czy obsługą zdarzeń. Pozo- Pomimo wielu różnic pomiędzy tymi stan-
Brak wspólnego API czy warstwy abstrak- staje tylko poczekać, jak do nowych stan- dardami, znalazło się też miejsce na wyja-
cji, która przykrywa zarządzanie zasoba- dardów ustosunkują się producenci sprzę- śnienie kilku wspólnych funkcji, których za-
mi systemowymi, jest istotną barierą wej- tu i OS’ów. stosowanie może przysporzyć pewnych kło-
ścia. Choć GLES pozwala na zunifikowa- Reasumując, w niedalekiej przyszłości bę- potów początkującym programistom GL’a
nie obsługi potoku graficznego, to zadania dziemy świadkami dążenia do unifikacji roz- (vertex arrays, VBO, oświetlenie). Niektó-
takie jak: wiązań i naturalnego zbliżenia mobilnych re z nich pojawiły się tu także ze wzglę-
urządzeń do ich desktopowych odpowiedni- du na rozbieżności w ich implementacji na
• dostęp do pliku; ków. Odczucia wizualne, dźwiękowe, szyb- różnych platformach. Na koniec opracowa-
• obsługa urządzenia dźwiękowego; kość przetwarzania i możliwości aplikacji nia przedstawiłem krótki zarys OpenGL ES
• instrukcje wejścia/wyjścia (obsługa kon- mobilnych przypominają sytuację na rynku 2.0, a także innych przyszłościowych stan-
trolera); desktopów z przed kilku lat, ale już teraz do- dardów, które mogą zmienić oblicze mobil-
• dostęp do zegara systemowego; czekaliśmy się przenośnych wersji niektórych nych aplikacji.
• obsługa zdarzeń i przerwań. wielkich tytułów. Prowadzone są nawet pra- Mam głęboką nadzieję, iż powyższe opra-
ce nad standaryzacją formatów wymiany con- cowanie posłuży Czytelnikom jako krótki
są wciąż domeną systemów operacyj- tent’u graficznego, czego dowodem może być zbiór porad i wskazówek, pomocnych przy
nych. Pisanie własnej warstwy abstrakcji COLLADA. pisaniu własnych aplikacji, a chciałbym je de-
dykować zmarłej, w okresie mej pracy nad ar-
tykułem, babci Anieli.
W Sieci Choć programowanie grafiki na urządze-
niach przenośnych stawia wiele wyzwań na-
• http://www.khronos.org/developers/resources/opengles/ – dostęp do wielu implementacji wet przed doświadczonym programistą, wi-
OpenGL ES, przykładowe programy, tutoriale; dok własnej aplikacji 3D na niewielkim ekra-
• http://www.vincent3d.com/ – strona domowa open source’owej biblioteki GLES;
nie malutkiego komputera szybko zrekompen-
• http://glutes.sourceforge.net/ – strona domowa mobilnej wersji znanej biblioteki GLUT;
• http://www.zeuscmd.com/tutorials/opengles/index.php – kilkadziesiąt tutoriali dla stawia- suje Waszą pracę.
jących swe pierwsze kroki z OpenGL ES;
• http://www.lighthouse3d.com/opengl/billboarding/ – świetny zestaw tutoriali na temat
tworzenia różnego typu billboardów;
• http://jet.ro/files/The_neglected_art_of_Fixed_Point_arithmetic_20060913.pdf – ciekawy
artykuł w temacie matematyki stałoprzecinkowej;
• http://www.glbenchmark.com – aktualna lista urządzeń ze wsparciem dla mobilnego GL’a,
KRYSTIAN KOSTECKI
a także testy wydajności, specyfikacja dostępnych rozszerzeń i wersji renderer'a. Pracuje na stanowisku Kierownika Działu Tech-
nicznego w firmie Gamelion wchodzącej w skład
Grupy BLStream. Na co dzień zajmuje się two-
rzeniem wysoko-poziomowych modułów wspo-
magających rendering, animację postaci, symu-
Literatura lacje fizyczne i efekty specjalne na potrzeby gier
• D.Astle, D.Durnil, OpenGL ES Game Development. Course Technology, 2004; 3D. Pasjonuje się wdrażaniem rozwiązań mate-
• K.Pulli, T.Aarnio, V.Miettinen, K.Roimela, J.Vaarala, Mobile 3D Graphics with OpenGL ES matycznych do symulacji zachowań, kontroli
and M3G. Morgan Kaufmann, 2007; animacji i rozwiązywania problemów algoryt-
• A.Munshi, D.Ginsburg, D.Shreiner, OpenGL ES 2.0 Programming Guide. Addison-Wesley, micznych. Grupa BLStream powstała, by efek-
2008;
tywniej wykorzystywać potencjał dwóch szyb-
• D.Shreiner, J.Neider; OpenGL Programming Guide, The Official Guide to Learning OpenGL.
Addison-Wesley 2007; ko rozwijających się producentów oprogramo-
• Khronos, OpenGL ES Native Platform Graphics Interface (Version 1.0). Khronos Group, wania – BLStream i Gamelion. Firmy wchodzą-
2003; ce w skład grupy specjalizują się w wytwarza-
• Richard S.Wright, OpenGL and Mobile Devices. Dr. Dobbs Journal 2006/05; niu oprogramowania dla klientów korporacyj-
• Richard S.Wright, OpenGL and Mobile Devices: Round 2. Dr. Dobbs Journal 2008/07; nych, w rozwiązaniach mobilnych oraz produk-
• S. Zerbst, O. Duvel, 3D Game Engine Programming. Course Technology, 2004;
• Richard S.Wright, OpenGL Super Bible, 4th Edition. Addison-Wesley, 2007; cji i testowaniu gier.
• K. Hawkins, D.Astle, A. LaMothe, OpenGL Game Programming. Course Technology, 2002. Kontakt z autorem:
krystian.kostecki@game-lion.com

www.sdjournal.org 205
Rozwiązania mobilne

Flash Lite
Tworzenie mobilnej aplikacji w technologii Flash

Flash Lite pomimo kilku lat na rynkach całego świata nadal pozostaje
nieodkrytym środowiskiem. Jest to jednak rozwiązanie dające ogromne
możliwości, dlatego warto się mu bliżej przyjrzeć.

borze urządzenia testującego, w naszym przypad-


Dowiesz się: Powinieneś wiedzieć: ku jest to 240x320 pikseli, która jest obecnie naj-
• Jak stworzyć prostą grę w środowisku Flash • Podstawy technologii Flash; bardziej powszechną dla wyświetlaczy komórko-
Lite 2.0; • Podstawy ActionScript 2.0. wych. Na tak przygotowanym dokumencie mo-
• Jak właściwie zaprojektować rozwiązania żemy zacząć tworzyć aplikację. Przejdźmy do sa-
graficzne. mego procesu tworzenia gry. Pierwszym kro-
kiem będzie stworzenie warstw, w których bę-
dziemy przechowywać poszczególne elementy
rozwiązań graficzno-programistycznych, która oraz obsługujący je kod. Dla gry 3 kubki stwórz-
jest ogromnym ułatwieniem w procesie projek- my 8 warstw przedstawionych na Rysunku 3. Na
Poziom trudności towania efektywnych i efektownych rozwiązań. ścieżce ruchu (timeline) dodajmy w każdej war-
stwie 4 klatki, które będą odpowiedzialne za po-
Tworzenie gry – pierwsze kroki szczególne ekrany w przygotowywanej aplikacji:
W tej części opiszemy krok po kroku, w jaki spo-

F
lash Lite to wersja popularnego odtwa- sób stworzyć prostą grę w środowisku Flash Lite • inicjalizacja gry;
rzacza animacji internetowych dostoso- 2.0 przy pomocy programu Adobe Flash CS4. • animacja wstępna;
wanego do możliwości platformy mobil- Na potrzeby tego artykułu powstała wszystkim • rozgrywka;
nej. Początki komórkowej wersji sięgają 2004 ro- dobrze znana rozgrywka o nazwie 3 kubki. • koniec.
ku, kiedy to jeszcze firma Macromedia ekspery- Zaczniemy od stworzenia nowego pliku. Z
mentowała z prototypowymi rozwiązaniami te- menu File wybieramy opcję New, a następnie W procesie inicjalizacji gry programista mu-
go typu. Po przejęciu tej organizacji przez Ado- Flash File (Mobile), co obrazuje Rysunek 1. Zo- si zadbać o to, aby aplikacja była wyświetlana
be Systems kontynuowano pracę nad Flash Li- staniemy przeniesieni do centrum obsługi urzą- w trybie pełnoekranowym oraz aby gracz in-
te począwszy od wersji 1.0 wdrożonej w czerw- dzeń mobilnych Adobe Device Central CS4. tuicyjnie obsługiwał aplikację za pomocą głów-
cu 2005 roku w japońskiej sieci NTT Docomo, Tam musimy wybrać odpowiednie parametry, nych klawiszy sterujących w komórce. Oprócz
gdzie do tej pory jest to jedna z dominujących w naszym przypadku będzie to Flash Lite 2.0 podstawowej nawigacji obsługiwanej przez kla-
technologii na tym rynku. W pozostałych re- oraz ActionScript 2.0. Kolejną niezbędną rze- wisze kierunkowe (joystick/D-Pad) istotną ro-
gionach najbardziej popularne wersje tego od- czą jest wybór urządzenia, na którym będziemy lę odgrywają również klawisze funkcyjne, tzw.
twarzacza to 1.1, który odpowiada zaawanso- testować aplikację. Z biblioteki umieszczonej po SoftKeys. Znajdują się przeważnie pod ekra-
waniem Flash Player 4, oraz Flash Lite 2.0, będą- lewej stronie wybieramy urządzenie, na potrze- nem komórki, po lewej i prawej stronie joystic-
cy odpowiednikiem internetowego odtwarzacza by obecnego przykładu będzie to Nokia E75. W k'a. W przypadku modeli Nokia przyjmuje się
w wersji 7. Obie edycje występują w większości trakcie tworzenia naszej aplikacji możemy zmie- standard, w którym lewy SoftKey służy do po-
modeli telefonów Nokia i Sony Ericsson, będą- niać dowolnie obszar roboczy oraz testować go- twierdzania bądź wywoływania menu pod-
cych w globalnej dystrybucji od 2006 r. Najnow- towe pliki na różnych urządzeniach. Rysunek ręcznego, a prawy służy do przechodzenia o po-
sze modele fińskiego producenta posiadają za- 2 przedstawia Adobe Device Central, który po- ziom w górę w strukturze aplikacji [Back] bądź
instalowany odtwarzacz w wersji 3.0, który ma zwala nam na testowanie naszej gry, symulując do opcji wyjścia [Quit]. Mając na względzie wy-
możliwości zbliżone do Flash Player 8. Ta naj- warunki, z jakimi spotkać się może gracz, na co godę użytkowania, podpisujmy na każdym z
nowsza technologia komórkowa pozwala mię- dzień obcując z naszym dziełem. Możemy m.in. poziomów aplikacji, do czego służą owe przyci-
dzy innymi na odtwarzanie klipów wideo w ściemniać i rozjaśniać ekran, nałożyć na ekran ski funkcyjne. Umożliwi to swobodne porusza-
standardach H.264, On2 VP6 oraz Sorenson. refleksy świetlne słonecznego dnia, obracać ko- nie się w hierarchii naszego produktu. W na-
W naszym artykule skoncentrujemy się na mórkę o 90 stopni, włączyć automatyczne wyga- szym przykładzie, widocznym na Rysunku 4,
prostym przykładzie gry przygotowanej dla od- szenie ekranu po dowolnej ilości sekund. podpisy do klawiszy SoftKey zostały umiesz-
twarzacza Flash Lite 2.0. Przygotowanie aplika- Kolejnym krokiem będzie ustawienie właści- czone dla czytelności na jasnej, drewnianej bel-
cji we wcześniejszej wersji umożliwia dotarcie wości dla aplikacji. Prędkość wyświetlania anima- ce przy dolnej krawędzi ekranu.
do większej ilości telefonów, a przy tym nie ma cji nie powinna przekraczać 32 klatek na sekun-
problemu z odtwarzaniem tych plików w naj- dę, gdyż komórki mają problemy z wydajnością Integracja kodu i grafiki
nowszych komórkach z FL 3.0. Zaprezentujemy przy wyższych parametrach odtwarzania. Roz- Inicjalizacja aplikacji pokazana jest na Listin-
tworzenie gry mobilnej, wykazując integrację dzielczość jest ustalona automatycznie przy wy- gu 1. Kod jest umieszczony w pierwszej klat-

206 SDJ Extra 34 Biblia


Flash Lite

ce utworzonej przez nas warstwy ActionScript.


Warto zwrócić również uwagę na typ skryptów, Listing 1. Inicjalizacja
które sterują procesami komórki, takimi jak ja- // ustawienie aplikacji w trybie pełnego ekranu
kość wyświetlania grafiki (SetQuality), wibra- fscommand2("FullScreen", true);
cja (StartVibrate), ustalenie klawiszy funkcyj- // ustawienie wysokiej jakości grafiki dla odtwarzanej aplikacji
nych (SetSoftKeys). Wszelkie działania tego fscommand2("SetQuality", "high");
typu odbywają się przez komendy FSCommand i // obsługa przycisków funkcyjnych komórki
FSCommand2.Warto dokładnie zapoznać się z tym fscommand2("SetSoftkeys", "LeftSoftKey", "RightSoftKey ");
zestawem ActionScript podczas opracowywania Key.removeListener(myListener);
własnych rozwiązań w tej technologii mobilnej. var myListener:Object = new Object();
Kiedy już mamy zainicjowaną aplikację, mo- myListener.onKeyDown = function() {
żemy przejść do drugiego ekranu, gdzie będzie var keyCode = Key.getCode();
się znajdować animacja wprowadzająca zawod- if (keyCode == ExtendedKey.SOFT1) {
nika do gry. W naszym przypadku jest to ani-
mowanie chowania monety pod kubek. Po wy- /* w zależności od tego, w której klatce będziemy się znajdować zostanie wywołane
konaniu animacji gra przechodzi do klatki nr inne zdarzenie. Obsługa klawisza SOFT1 będzie wywołana w
3, w której zadeklarowana jest cała rozgrywka. klatkach 3 i 4. */
Teraz na warstwie Button dodajemy na plan-
szy przycisk odpowiedzialny za działanie klawi- switch(_root._currentframe){
szy joystick’a. Do tego celu rysujemy na planszy
obiekt – niech to będzie prostokąt, za pomocą /* Lewy SoftKey w klatce 3 odpowiada za funkcję EnterKeyPress sprawdzającą czy pod
narzędzia Rectangle Tool [R]. Utworzony obiekt kubkiem znajduje się moneta */
konwertujemy na Button, wybierając z menu
Modify/Convert to symbol, co widać na Rysun- case 3 : EnterKeyPress(); break;
ku 5. Do tak utworzonego przycisku dodajemy
skrypt przedstawiony na Listingu 2. // Lewy SoftKey w klatce 4 odpowiada za restart aplikacji
Zasadnicza część rozgrywki odbywa się w case 4 : _root.gotoAndPlay(1); break;
klatce trzeciej, ale zanim przejdziemy do pisa- }
nia kodu, omówmy, jakie elementy będą nam }
potrzebne do stworzenia gry. Zacznijmy od war- if (keyCode == ExtendedKey.SOFT2) {
stwy Background, w której umieszczamy tło apli- switch(_root._currentframe){
kacji. Po dodaniu tła blokujemy warstwę, aby
nie dokonać przypadkowych zmian w niej, co /* w pierwszej klatce prawy przycisk SOFT2 odpowiada za przejście na następną
przedstawiamy na Rysunku 6. klatkę gdzie zaczyna się animacja */
W warstwie Background ustawiamy grafikę case 1 : play(); break;
tła dla gry w postaci faktury desek narysowanej
odręcznie za pomocą narzędzia Brush Tool [B]. /* w klatkach 3 i 4 SOFT2 odpowiada za wyjście z gry */
Jest to warstwa ustawiona najniżej, co skutkuje case 3 : fscommand2("quit"); break;
tym, że wszystkie elementy z wyższych warstw case 4 : fscommand2("quit"); break;
będą nad naszym tłem. }
Dodatkowo ustalamy kolor tła naszej aplika- }
cji. Aby tego dokonać, musimy kliknąć prawym };
przyciskiem myszy na pusty, szary obszar poza Key.addListener(myListener);
sceną. Pojawi nam się podręczne menu, z które-
go wybieramy Document properties. Z tych opcji /* ustawienie ilości ‘żyć’ dla gracza oraz numeru planszy */
wybieramy Background color (ikonka kwadratu, var level:Number = 1;
wyświetlająca aktualny kolor podłoża – domyśl- var lives:Number = 3;
nie jest nim biały). Rozwinie nam się paleta kolo- stop();
rystyczna, nad którą znajduje się pole tekstowe z
wartością aktualnego koloru (dla białego wartość
ta to #FFFFFF). Zmieniamy standardową biel na
kolor jasny brązowy o wartości #D4A76E.
Na warstwie Ball rysujemy przedmiot, który
będzie przesuwany pod kubkami. Nasza gra bę-
dzie utrzymana w konwencji pirackiej tawerny,
więc pod kubkami znajdować się będzie złota
moneta. Konwertujemy utworzoną grafikę do
MovieClip'a, nadajemy nazwę MyBall. Następ-
na warstwa Arrow odpowiedzialna będzie za
wyświetlenie animowanego wskaźnika, którym
użytkownik będzie wskazywał kubek z monetą
– w tym przypadku będzie to zaciśnięta dłoń z
wyprostowanym palcem wskazującym. Naryso-
waną dłoń konwertujemy do MovieClip'a i na- Rysunek 1. Tworzenie nowego pliku

www.sdjournal.org 207
Rozwiązania mobilne

zywamy MyArrow , co obrazuje Rysunek 7. We- we na listwie czasowej w klatkach pierwszej, wszystkim czytelną na małych wyświetlaczach
wnątrz MovieClip'a MyArrow konwertujemy ósmej i szesnastej. Zaznaczamy wszystkie klat- czcionkę – w tym przypadku jest to Trebuchet
obiekt wskazującej dłoni na MC o nazwie My- ki i klikamy na nich prawym przyciskiem my- MS. Zmieniamy wartość koloru tekstów z do-
ArrowIside, następnie stawiamy klatki kluczo- szy, wybierając opcję automatycznego ruchu myślnej na #432F01, która bardziej pasuje do
Create Motion Tween. Odznaczamy zaznaczenie stylu pozostałych grafik. Flash CS4 daje nam też
Listing 2. Kod niewidzialnego przycisku do i wskazujemy kursorem klatkę kluczową nu- możliwość wyboru pomiędzy kilkoma opcjami
sterowania wskaźnikiem mer osiem, zaznaczamy MyArrowIside i prze- wyświetlania czcionek. W rozwijalnej liście w
suwamy go w linii prostej o kilka pikseli w gó- przyborniku, tuż pod ikonkami justowania tek-
on (keyPress "<Enter>") { rę. Wychodzimy z MovieClip'a na scenę głów- stu, mamy dostępne następujące opcje:
switch(_root._currentframe){ ną. Tym sposobem zrobiliśmy animację wska-
case 1 : play(); break; zującej dłoni. Każdemu z utworzonych obiek- • Use Device Fonts – Flash Lite Player wy-
/* w klatce 3 przycisk będzie tów nadajemy unikalną nazwę Instance Name w świetli czcionkę, która jest standardowym
odpowiedzialny za wywołanie funkcji zakładce Properties, dzięki czemu będziemy mo- krojem danego modelu telefonu;
sprawdzającej zawartość kubka */ gli się do niego odwołać w prosty sposób z ko- • Bitmap text – tekst jest pozbawiony anty-
case 3 : _root.EnterKeyPress(); du AS. I tak stworzone obiekty nazywamy ko- aliasing'u, nie polecamy tej opcji;
break; lejno arrow_mc i ball_mc. Rysunek 7 pokazu- • Anti-Alias for Animation – włącza anty-
/* restart gry – zerowanie ‘żyć’ je okienko Properties z wpisaną nazwą instancji aliasing czcionki, metoda rekomendowana
i planszy oraz powrót do wstępnego – arrow_mc. przy tekstach animowanych;
menu gry */ W warstwie ResultAnimation tworzy- • Anti-Alias for Readibility – metoda
case 4 : { my animacje, które będą wyświetlane przy uwzględniająca anty-aliasing poprawiający
_root.gotoAndPlay(1); prawidłowym/błędnym wyborze kubka czytelność fontu, w tym przypadku sko-
level = 1; przez użytkownika. Animacje konwertujemy rzystamy właśnie z niej;
lives = 3; do MovieClip’ów i nadajemy Instance Name • Custom Anti-Alias – w tym przypadku sa-
break; odpowiednio good_mc oraz bad_mc. Utwo- mi ustalamy opcję anty-aliasing'u.
} rzone MC ustawiamy poza sceną, aby były
} nie widoczne dla gracza w czasie rozgryw- Jeśli nie korzystamy z opcji Use Device Fonts,
} ki, będziemy je animować w momencie wy- pamiętajmy również o załączeniu czcionki do
// obsługa wskaźnika przy ruchu w lewo boru kubka. każdego dynamicznego pola tekstowego. Jeśli
on (keyPress "<Left>") { Kolejna warstwa – Top odpowiedzialna bę- tego nie zrobimy, to aplikacja nie będzie w sta-
if(_root._currentframe == 3) dzie za wyświetlanie poziomu, na którym się nie wyświetlić danego kroju pisma i zastąpi je
if (arrow_mc.Position != 1) { aktualnie znajduje gracz oraz ilości żyć, które domyślną czcionką urządzenia. Aby zamie-
arrow_mc.Position--; mu pozostały. W tym celu tworzymy na sce- ścić font, kliknijmy przycisk Embed, znajdują-
arrow_mc._x -= 70; nie dwa teksty statyczne oraz dwa teksty dyna- cy się tuż obok rozwijalnej listy opcji wyświe-
} miczne, co obrazują Rysunki 8 i 9. Dla wersji dy- tlania fontów. Pojawi się okienko Character Em-
} namicznych w zakładce Options ustawiamy Va- bedding, w którym możemy zaznaczyć, które
// obsługa wskaźnika przy ruchu w prawo riable dla pierwszego pola na level, a dla drugie- zestawy znaków zamierzamy dołączyć do dy-
on (keyPress "<Right>") { go lives. Dzięki tej operacji będziemy mogli od- namicznego pola tekstowego. Wybierzmy tyl-
if(_root._currentframe == 3) woływać się do utworzonych dynamicznych tek- ko te, które będą używane. W tym przypadku
if (arrow_mc.Position != 3) { stów bezpośrednio z poziomu kodu za pomocą jest to zestaw cyfr Numerals 0-9. Każdy zestaw
arrow_mc.Position++; zmiennych level i lives. to dodatkowy rozmiar zwiększający plik SWF.
arrow_mc._x += 70; Należy pamiętać o zmianie rodzaju tekstu w Starajmy się również trzymać jednego kroju.
} przyborniku na DynamicText, która jest przed-
} stawiona na Rysunku 10. Z właściwości tekstu
w panelu Properties wybieramy ładną i przede

Rysunek 3. Warstwy aplikacji

Rysunek 2. Adobe Device Central – narzędzie do efektywnego testowania tworzonych aplikacji Rysunek 4. Animacja początkowa

208 SDJ Extra 34 Biblia


Flash Lite

Czcionki są elementami, które znacząco wpły- l+Enter]. W Adobe Device Central developer ma te, gdyż trudno sobie wyobrazić gry i animacje
wają na rozmiar finalnego pliku. możliwość testowania programu na różnych te- Flash bez dobrze przygotowanej grafiki.
lefonach, co dobrze widać w lewej części Rysun-
Programowanie i testowanie ku 11. Po wyborze interesującego nas telefonu Im mniej tym lepiej
Do tak przygotowanej sceny możemy zacząć pro- w ekranie EMULATOR mamy podgląd apli- To złota zasada w tej technologii. Nie należy
gramowanie rozgrywki, przedstawione szczegó- kacji. W tym miejscu możemy testować funk- przesadzać z nadmiarem i skomplikowaniem
łowo na Listingu 3. Dodatkowo dla MovieCli- cjonalność naszej gry, sprawdzić czy klawisze są wizualnych obiektów oraz animacji. Obciąży to
p’ów wyświetlających nam, czy prawidłowo wy- prawidłowo podpięte oraz czy aplikacja działa
braliśmy kubek, ustawiamy skrypt przenoszący w trybie pełnoekranowym. Dodatkowo po pra-
nas ponownie do losowania kubków, bądź do wej stronie znajduje się zakładka MEMORY, w
ekranu końcowego aplikacji w przypadku wyko- której możemy obserwować zachowanie się pa-
rzystania wszystkich żyć. Listing 4 przedstawia mięci telefonu w trakcie działania poszczegól-
proste rozwiązanie zamykające rozgrywkę. W nych etapów gry. Adobe Device Central dosto-
ostatniej klatce animacji wyświetlamy podsumo- sowuje pamięć emulowanego telefonu w zależ-
wanie gry, w którym gracz otrzymuje informację, ności od wybranego modelu. Dodatkową opcją
do jakiego poziomu dotarł. Do tego posłuży nam jest sprawdzenie działania aplikacji w trzech try-
pole Text Tool. Ustawiamy jego parametry na Dy- bach jakości: High, Medium i Low. Do tego celu
namic Text,oraz nadajemy wartość Variable = wyświetlamy zakładkę Device Performance, gdzie
level. W ten sposób pole automatycznie pobie- mamy możliwość swobodnego manipulowania Rysunek 7. Nadawanie nazw obiektom
rze wartość zmiennej level do tego pola. tymi opcjami.
Tak stworzoną aplikację uruchamiamy w Techniczne aspekty mamy już za sobą, skup-
Adobe Device Central, wybierając z menu głów- my się więc na istotnych elementach dotyczą-
nego Flash CS4 zakładkę Control/Test Movie [Ctr- cych projektowania w środowisku Flash Li-
Rysunek 8. Punktacja – teksty statyczne

Rysunek 9. Punktacja – teksty dynamiczne

Rysunek 5. Konwersja obiektu

Rysunek 10. Właściwości tekstów


Rysunek 6. Blokada nieużywanych warstw dynamicznych

www.sdjournal.org 209
Rozwiązania mobilne

bowiem stosunkowo słaby (ok. 200 – 400 Mhz) w podręcznej pamięci telefonu (tzw. heap me- 4. Stworzona grafika jasnej, drewnianej deski zo-
procesor komórki, którego nie wspomagają, jak mory) dla innych, niezbędnych procesów gry. stała wykorzystana u góry ekranu, pod tekstami
w przypadku PC, karta graficzna czy pamięć Flash bazuje również na symbolach, którymi wyświetlającymi punkty i życie, następnie jej ko-
RAM. Dlatego elementy gry powinny być pro- są Graphics (symbole graficzne) jak i MovieCli- pia za pomocą opcji Menu/Transform/FlipVertical
ste i wyraźne. Widoczność jest tu również prio- p’y. Raz stworzony obiekt przechowywany jest odbita lustrzanie względem osi X i umieszczona
rytetem. Biorąc pod uwagę standardy wyświe- w bibliotece pliku FLA. Dzięki temu możemy przy dolnej krawędzi ekranu jako pole dla opisa-
tlaczy mobilnych (176x208 pikseli w przypad- użyć dowolnej ilości duplikatów tego samego nia klawiszy SoftKeys.
ku starszych modeli i 240x320 w przypadku symbolu na scenie, a program będzie musiał je- Dobrym nawykiem jest również tworzenie
nowszych), wyeliminujmy wszelkie szczegó- dynie przeliczyć oryginał, znajdujący się w pa- grafiki w oparciu o proste kształty, złożone z jak
ły obiektów, które pozostaną niezauważone dla nelu biblioteki [Library]. Starajmy się nie dokła- najmniejszej ilości punktów. Każdy punkt to ko-
użytkownika. Czy rzeczywiście główny bohater dać kolejnych obiektów bez potrzeby i wykorzy- lejne bajty oraz dodatkowy wysiłek dla mobilne-
gry musi mieć guziki na koszuli, która zajmuje 5 stywać do wielu zastosowań te, które już stwo- go procesora.
na 5 pikseli ekranu? Rezygnując z tego typu de- rzyliśmy. To także oszczędność pamięci i roz- Aby zoptymalizować naszpikowany punkta-
tali, zaoszczędzimy nie tylko na rozmiarze pliku, miaru. W przykładzie 3 kubki takie rozwiąza- mi kształt możemy posłużyć się opcją ich auto-
ale również pozostawimy sporo cennego miejsca nie również ma miejsce – widać to na Rysunku matycznego usuwania. W tym celu należy za-
znaczyć kształt, a następnie wybrać z menu po-
lecenie Optimize (Modify/Shape/Optimize), tak
jak na Rysunku 12. Na ekranie pojawi się okien-
ko Optimize Curves, które przedstawia Rysunek
13. Za pomocą dostępnego suwaka ustawiamy
parametr Optimization Strength na pożądany po-
ziom. W naszym przypadku zredukujemy ilość
punktów o 68%. Kliknięcie [OK] spowoduje
optymalizację krzywej – część punktów zosta-

Rysunek 13. Okienko optymalizacji

Rysunek 11. Testowanie aplikacji w Adobe Device Central

Rysunek 14. Dostępne efekty graficzne dla


obiektów

Rysunek 12. Automatyczna optymalizacja obiektów graficznych Rysunek 15. Optymalizacja animacji dla Flash Lite

210 SDJ Extra 34 Biblia


Flash Lite

Listing 3. Engine gry

center = 0; };
// zmienna whichRoll odpowiedzialna za zliczanie ilości /* funkcja odpowiedzialna za mieszanie kubków */
mieszań kubka function roll3(cup1:MovieClip,cup2:MovieClip) {
whichRoll = 0; if (center == 0) {
// oneEnter sprawdza czy kubki są w danym momencie animowane /* przemieszczaj kubki na scenie */
(1=true, 0=false) if (cup1._x<cup2._x) {
oneEnter = 1; cup1._y+=speed;
// pozycja, na której aktualnie znajduje się moneta cup1._x+=speed;
ball_mc.Position = 1; cup2._y-=speed;
// pozycja kursora do wskazywania kubka cup2._x-=speed;
arrow_mc.Position = 1; } else {
arrow_mc._visible = false; center = 1;
arrow_mc._x = 33; }
// zmienna blokująca możliwość sprawdzania zawartości kubków }
w czasie mieszania if (center == 1) {
var canKeyPress:Boolean = false if (cup1._y>130) {
// zmienna nextBlend odpowiada za wybór, które kubki będą cup1._y-=speed;
animowane cup1._x+=speed;
nextBlend = (random(300)%3)+1; cup2._y+=speed;
// dynamiczne tworzenie 3 kubków na scenie cup2._x-=speed;
var kub1:MovieClip = attachMovie("MyKubek","cupx1",103,{_x: oneEnter=0;
20,_y:130}); } else
var kub2:MovieClip = attachMovie("MyKubek","cupx2",102,{_x: /* warunek sprawdzający czy zamiana kubków dobiegła końca */
90,_y:130}); if(center ==1 && cup1._y == 130 && oneEnter==0)
var kub3:MovieClip = attachMovie("MyKubek","cupx3",101,{_x: {
160,_y:130}); center = 0;
/* wylosowanie następnego zestawu kubków do zamiany */
kub1.NR = 1; nextBlend = (random(300)%3)+1;
kub2.NR = 2; /* zwiększenie licznika losowań */
kub3.NR = 3; whichRoll++;
/* włączamy ciągłe odtwarzanie klatki, czego skutkiem będzie oneEnter=1;
powtarzanie losowania na zadanych przez nas warunkach do /* dla uproszczenia wykonywania animacji ustaw kubki w
momentu utraty ‘żyć’ przez zawodnika */ pozycji w jakiej znajdowały się podczas startu tej klatki */
if(cup1.NR == 1 && cup2.NR== 2){
onEnterFrame = function (){ cup1._x = 20;
switch(level){ cup2._x = 90;
/* ustawienie prędkości po dojściu do poszczególnych if(ball_mc.Position == 1) ball_mc.Position = 2;
poziomów w rozgrywce */ else
case 1 : speed = 3; break; if(ball_mc.Position == 2) ball_mc.Position = 1;
case 3 : speed = 5; break; return;
case 6 : speed = 7; break; }
} else
switch(nextBlend){ if(cup1.NR == 1 && cup2.NR == 3){
/* w zależności od wylosowanej liczby odtwarzanie jednego z cup1._x = 20;
trzech możliwych mieszań kubków */ cup2._x = 160;
case 1 : roll3(kub1,kub2,kub3); break; if(ball_mc.Position == 1) ball_mc.Position = 3;
case 2 : roll3(kub1,kub3,kub2); break; else
case 3 : roll3(kub2,kub3,kub1); break; if(ball_mc.Position == 3) ball_mc.Position = 1;
case 0 : { return;
arrow_mc._visible = true; }
delete this.onEnterFrame; else
break; if(cup1.NR == 2 && cup2.NR == 3){
} cup1._x = 90;
} cup2._x = 160;
/* ilość zamian kubków zależy od poziomu, na którym się if(ball_mc.Position == 2) ball_mc.Position =3;
znajdujemy, a osiągnięcie danego poziomu zatrzymuje else
OnEnterFrame */ if(ball_mc.Position == 3) ball_mc.Position =2;
if(whichRoll == level){ return;
nextBlend=0; }
canKeyPress = true; }
} }

www.sdjournal.org 211
Rozwiązania mobilne

nie usunięta. Istnieje także ręczna metoda po- ga wprawy i cierpliwości, jednak daje pełną kon- 14. Ze szczególną ostrożnością powinno się do-
zbycia się punktów za pomocą narzędzia Subse- trolę nad ilością punktów i finalnym kształtem. dawać efekt przezroczystości (Alpha). Zbyt czę-
lectional Tool. Jest to ikonka z białą strzałką znaj- W przypadku gdy musimy użyć skompliko- ste jego stosowanie może doprowadzić do obcią-
dującą się w palecie narzędzi lub przypisana do wanego graficznie elementu, zastosowanie ma- żenia pamięci telefonu i spowolnić proces od-
skrótu klawiszowego [A]. Za jej pomocą wy- py bitowej jest wyjściem z sytuacji. Bitmapa co twarzania pliku SWF.
bieramy kształt, wraz z nim podświetlą się jego prawda zwiększy rozmiar swf'a, jest jednak ła-
wszystkie punkty składowe. Następnie nie od- twiejsza do przetworzenia przez Flash Lite Play- Animacje i optymalizacje
znaczając obiektu, klikamy w te, których chce- er'a, niż wektorowy kształt, który jest tworzony Flash to nie tylko potężne narzędzie graficzne,
my się pozbyć (z przyciskiem [Shift] mamy moż- od podstaw w odtwarzaczu i na bieżąco przeli- ale również środowisko animacyjne. Automa-
liwość dodawania kolejnych punktów do zazna- czany. Program Flash CS4 Professional oferuje tyzacja ruchu obiektów i kształtów (motion twe-
czenia), i usuwamy je za pomocą przycisku [De- nam również kilka efektów, które możemy za- en i shape tween) czyni proces animacji niezwy-
lete]. Użycie pierwszego sposobu optymalizacji stosować dla tworzonych symboli graficznych. kle łatwym. W przypadku wersji Lite ze wzglę-
obiektu jest prostsze i szybsze, za to mniej do- Są one dostępne w rozwijanej liście Color Effect dów optymalizacyjnych nie powinno się jednak
kładne, bo automatyczne. Drugi sposób wyma- w panelu Properties, przedstawione na Rysunku stosować przesadnej ilości tween'ów oraz animo-
wać dużych elementów graficznych wypełnia-
jących sporą część ekranu. Bazowanie na pro-
stych iluzjach ruchu sprawdzi się tu znacznie
lepiej niż konstruowanie wieloelementowych,
skomplikowanych animacji. Umieszczanie kil-
ku tween'ów naraz jest znacznie gorszym po-
mysłem, niż odtwarzanie ich po kolei, jeden po
drugim. Rysunek 15 obrazuje, jak wydajnie po-
sługiwać się tween'ingiem. Pomocna w uspraw-
nieniu aplikacji jest również zamiana automa-
tycznego ruchu na animację poklatkową. Za-
znaczmy na listwie czasowej niebieskie klatki
automatycznego ruchu, następnie kliknijmy na
nich prawym klawiszem myszy – z rozwijalne-
go menu wybierzmy opcję Convert to Keyframes,
tak jak na Rysunku 16. W tym celu można też
użyć klawisz [F6]. Flash utworzy na odcinku se-
lekcji klatki kluczowe. Jednak dalej klatki te są w
kolorze niebieskim, a więc musimy jeszcze od-
znaczyć im opcję Motion Tween. Zaznaczamy

Rysunek 16. Zamiana klatek automatycznego ruchu na ruch poklatkowy

Rysunek 17. Odznaczenie automatycznego ruchu dla utworzonych klatek kluczowych poklatkowej Rysunek 18. Automatyczne zaznaczanie
animacji zbędnych obiektów z biblioteki

212 SDJ Extra 34 Biblia


Flash Lite

ją i ponownie posługujemy się prawym przyci- krawędzi okienka Biblioteki. Tym sposobem pamiętajmy, aby nie popadać w skrajności.
skiem myszy, z menu wybierając tym razem Re- zaoszczędziliśmy kilobajtów na i tak niewyko- Zdroworozsądkowe podejście w zabawie z
move Tween – Rysunek 17 obrazuje ten krok. Au- rzystywanych obiektach, które zaśmiecały plik, Flash Lite'em polega na znalezieniu kompro-
tomatyczny ruch możemy wyłączyć również w i uporządkowaliśmy jego strukturę. Nie należy misu pomiędzy szybkim i prawidłowym dzia-
panelu Properities, wybierając z rozwijalnej listy również stosować Scen (Window/Other panels/ łaniem gry oraz ciekawą i czytelną szatą gra-
Tween pozycję None. Scene), które są prawdziwymi pożeraczami baj- ficzną. W osiągnięciu tego celu nie rezygnuj-
Eksportując finalną wersję gry, usuńmy tów. Lepiej poukładać zawartość aplikacji w mo- my z wszystkich dobrodziejstw tej platformy
wszystkie niepotrzebne linijki kodu, komenta- vie clip'ach i skryptowo je odgrywać. – eksperymentujmy oraz przede wszystkim
rze, warstwy, puste klatki. Pozostawmy tylko to, testujmy nasz produkt na możliwie najwięk-
co niezbędne jest do prawidłowego funkcjono- Podsumowanie szej ilości telefonów komórkowych. Modele
wania naszej gry. Jeśli w bibliotece trzymaliśmy Flash Lite to środowisko niezwykle ciekawe, różnią się między sobą parametrami, dlatego
warstwy i obiekty w katalogach, pozbądźmy się dające wiele możliwości, a jednocześnie przy- taka konfrontacja z docelowymi urządzenia-
tej struktury, gdyż foldery niepotrzebnie zwięk- jazne dla twórców wszelkiego rodzaju con- mi pozwoli nam się zorientować w rzeczywi-
szają rozmiar finalnego pliku. tent’u. Biorąc pod uwagę nasze wskazówki, stych możliwościach platformy.
Skorzystajmy z funkcji biblioteki zaznaczają-
cej nieużywane obiekty – z menu Window wy-
bierzmy zakładkę Library, bądź skorzystajmy ze GRZEGORZ TRUBIŁOWICZ
skrótu [Ctrl+L]. Pojawi się okno biblioteki. Tak CEO firmy IKS, pasjonat marketingu i nowych technologii.
jak na Rysunku 18, kliknijmy na ikonkę w pra-
wym górnym rogu okienka Library i z rozwijal- MATEUSZ PIETREK
nych opcji zaznaczmy Select Unused Items. Na- Creative Director IKS’a, doświadczony projektant i animator.
stępnie usuńmy niepotrzebne obiekty za po-
mocą ikonki kosza, znajdującej się przy dolnej KAROL BEDNARZ
Junior Flash Developer w firmie IKS, specjalista Flash Lite.

W Sieci IKS Mobile to dział firmy IKS odpowiedzialny za międzynarodowe sukcesy gier i rozwiązań mobilnych.
• ht tp://w w w.adobe.com/products/ Eksperci od technologii Flash Lite, którą zajmują się nieprzerwanie od 2005 roku. Gry „Crazy Matches”,
flashlite/; „Tropictos” oraz „Cubic Republic” były docenione przez profesjonalne jury i międzynarodową publicz-
• http://www.adobe.com/devnet/devices/; ność m.in. w konkursach IMGA, IGF, Flash Lite Developer Challenge. Więcej informacji na www.IKSmo-
• http://www.flashlite.info.pl. bile.com oraz www.iks.pl.
Kontakt z autorami: office@iksmobile.com

Listing 3cd. Engine gry

} 1 i wyświetl animację good_mc */


/* funkcja wywoływana po wciśnięciu przycisku LeftSoftKey if (arrow_mc.Position == ball_mc.Position) {
i Enter w czasie rozgrywki, odpowiedzialna jest za level++;
sprawdzenie zawartości kubka */ good_mc._x = 60;
function EnterKeyPress(){ good_mc.gotoAndPlay(1);
if(canKeyPress==true){ /* złe wytypowanie kubka powoduje utratę życia i wyświetlenie
canKeyPress = false; animacji z klipu bad_mc*/
ball_mc._x = 34 + ((ball_mc.Position - 1) * 70); } else {
if(ball_mc.Position == 1) lives--;
_root.kub1.gotoAndPlay("anim"); bad_mc._x = 60;
if(ball_mc.Position == 2) bad_mc.gotoAndPlay(1);
_root.kub2.gotoAndPlay("anim"); }
if(ball_mc.Position == 3) }
_root.kub3.gotoAndPlay("anim"); }
/* w przypadku prawidłowego wskazania kubka zwiększ poziom o stop();

Listing 4. Zakończenie rozgrywki

/* usunięcie kubków z planszy, żeby nie były widoczne na uproszczenia ‘gameOver’ */


ekranie końcowym ani przy ponownym if (_root.lives == 0) {
wyświetleniu animacji startującej _root.gotoAndStop("gameOver");
grę */
_root.kub1.removeMovieClip(); /* poprawne wytypowanie kubka wraca gracza do animacji w
_root.kub2.removeMovieClip(); klatce drugiej, po której następuje
_root.kub3.removeMovieClip(); kolejna runda gry */
} else {
/* w momencie błędnego wytypowania kubka przechodzimy _root.gotoAndPlay(2);
do klatki 5, którą nazywamy dla }

www.sdjournal.org 213
Felieton

Raport
większości
Technologie mobilne otwierają nowe możliwości – być może jeszcze bar-
dziej rewolucyjne niż zjawisko Internetu. W odróżnieniu od Sieci – tort
jest dzielony na innych zasadach, i to przez znacznie większą ilość graczy.

J
ednym z najważniejszych towarów naszych czasów jest informa- we i SMS) sprawiła, że ich rozwój w Europie był bardzo szybki i w nie-
cja. Transformacje społeczeństw odbywają się w kierunku coraz których krajach penetracja tych usług osiągnęła nawet 150%. Nieste-
sprawniejszego posługiwania się wszelkimi danymi. Kluczem do ty, tak się nie stało w przypadku 3G. Operatorzy, próbujący odbić so-
sukcesu jest umiejętność artykułowania pytań na sposób cyfrowy oraz bie bardzo kosztowne licencje i infrastrukturę, obecnie preferują tak-
odszukiwania i kategoryzacji odpowiedzi na nie. To motywuje do roz- tykę ograniczania klientom dostępu do szerszych treści (spoza oferty
wijania wszelkich technologii zwiększających możliwości przesyłania i dostarczanej przez nich samych, ang. walled garden). Przykładem mo-
przetwarzania informacji. Obserwujemy postępujące zapotrzebowa- gą być limity transferu. Dodatkowo opłaty za korzystanie z usług uza-
nie na usprawnienie dystrybucji informacji, co ma umożliwić dociera- leżniane są od stopnia ich wykorzystania. W przypadku 2G wprowa-
nie do maksymalnej liczby odbiorców. Rośnie zapotrzebowanie na no- dzenie opłat stałych (ang. flat rate) oraz znaczące obniżenie cen (wy-
we formy prezentacji, które będą zrozumiałe dla możliwie najszersze- muszone również regulacjami na poziomie europejskim) doprowa-
go grona użytkowników. Celem jest dostarczanie coraz większej ilości dziło do zwiększenia wolumenu sprzedaży. W efekcie przełożyło się
usług, które będą osiągalne dla rosnącej populacji. Takie drogi rozwo- to na zwiększenie zysków.
ju umożliwiają ograniczenie zjawiska e – wykluczenia. I nie zawsze mo- Taktyka przyjęta przez operatorów odbija się na popularności
że chodzić o naszą wygodę. To może ratować życie. usług 3G. W Polsce szacuje się, że jedynie jedna piąta ludności znajdu-
je się w zasięgu UMTS ([2]). Są to głównie obszary dużych aglomera-
Mobilne czy stacjonarne? cji miejskich. Dlatego Unia Europejska aktywnie działa na rzecz zmia-
Dostarczanie usług drogą cyfrową kojarzyło się dotychczas głównie z ny takiego stanu rzeczy. Wysiłki idą głównie w kierunku uporządko-
drogą Internetu stacjonarnego. Jeżeli jednak przyjrzymy się szczegó- wania częstotliwości oraz regulowania rozliczeń między operatorami.
łowym danym, to okaże się, że na świecie średnio na 100 ludzi Inter- Z jednej strony chodzi tu o nacisk na rządy krajów członkowskich w
netu używa 20 osób. W tym samym czasie subskrybentów telefonii celu udostępnienia nowych pasm i ograniczenia kosztów ich licencji,
mobilnej jest aż 50. W poszczególnych regionach dysproporcje te są z drugiej – o regulowanie i uproszczanie rozliczeń między operatora-
jeszcze większe – na przykład w Afryce prawie sześciokrotnie więcej mi sieci. Krokiem w tym kierunku jest odgórne ograniczanie taryf ro-
ludzi ma dostęp do telefonu komórkowego niż do Internetu (zob. [1]). amingowych czy niechybna regulacja opłat za zakończenie połączeń
Również dynamika wzrostu użytkowników Internetu jest dużo niższa, między sieciami (ang. Mobile Termination Rates, MTR) (zob.[4]).
niż ilość klientów przybywających operatorom komórkowym. Ceny W obliczu licznych interwencji UE, wydaje się, że główną prze-
połączeń głosowych i SMS'ów regularnie spadają, również w sensie szkodą w popularyzacji dostępu szerokopasmowego przez komór-
koszyków usług (zestawienie oparte na procentowym udziale połą- kę są regulacje administracyjne oraz modele biznesowe przyjęte
czeń w ramach sieci abonenta, poza nią, do sieci stacjonarnych, w przez operatorów. Wszelkie granice technologiczne są stosunkowo
szczycie i poza nim, w weekendy itp.). Nic dziwnego, że to właśnie łą- szybko przesuwane. Dla przykładu: na wystawie CTIA Ericsson zade-
cza ruchome zyskują coraz większą popularność jako medium stoso- monstrował rozwiązanie oparte na HSPA, pozwalające na przesyła-
wane do dostarczania usług. nie danych z szybkością 56 Mb/s.

m-Internet m-Telewizja
Zgodnie z ostatnim raportem UKE (zob. [2]) w Polsce z dostępu do In- Ograniczona pojemność łączy ruchomych sprawia, że strumieniowa
ternetu za pośrednictwem sieci ruchomych (modem bezprzewodo- transmisja telewizji w sieciach komórkowych (ang. unicast) jest sto-
wy, np. Orange Free, iPlus, Blueconnect czy Play Online) do końca 2008 sunkowo mało atrakcyjna. Możliwości tej technologii są dosyć ogra-
roku skorzystało około miliona osób. Dodatkowo za pomocą komór- niczone, co wpływa ujemnie na ilość użytkowników mogących jed-
ki z siecią łączyło się około 2 mln osób. Mimo dynamicznego wzrostu, nocześnie korzystać z takich usług. Alternatywą jest wykorzystanie
forma ta stanowi jedynie 3% obrotu europejskich operatorów ([3]). trybu rozsiewczego (ang. multicast lub ang. broadcast) pozwalające-
Jest to zapewne spowodowane zbyt wolnym rozwojem technologii go na dostarczanie programu telewizyjnego do dużej ilości termina-
3G zapewniających odpowiedni transfer dla usług Web 2.0. Współ- li. W ten sposób komunikacja serwer – terminal zastępowana jest try-
praca rządów i konsorcjów europejskich w ramach 2G (usługi głoso- bem serwer – wiele terminali, co znacznie efektywniej wykorzystu-

214 SDJ Extra 34 Biblia


Felieton

je pasmo. Spektrum możliwych rozwiązań jest tu szerokie – od pro- We Włoszech np. najpopularniejsze są serwisy sportowe i wiadomo-
tokołu FLO (głównie USA), wdrożonego w Korei Południowej T-DMB, ści. Spodziewany jest również popyt na klipy muzyczne, kreskówki
japońskiego 1seg czy wreszcie zalecanego przez Komisję Europejską – choć nie jest pewne, czy te dziedziny nie zostaną zdominowane
(KE) standardu DVB-H. Te rozwiązania wymagają najczęściej nieste- przez tryb na żądanie (ang. on demand).
ty budowania osobnej infrastruktury. Koszty można jednak ograni-
czać, używając trybów opartych na kanałach 3G, na przykład z uży- m-Reklama
ciem technologii MBMS. Jedną z cech wyróżniających technologie mobilne jest osobistość
W Polsce testy DVB-H rozpoczęły się w 2006 roku. Konkurs na czę- terminali. Jest to niezaprzeczalna zaleta w porównaniu do kompu-
stotliwości 470-790 Mhz został rozstrzygnięty w marcu tego roku. terów stacjonarnych czy laptopów. Komputer jest najczęściej wy-
Więcej punktów uzyskała spółka INFO-TV-FM. Jej ofertę oceniono korzystywany przez kilka osób. Komórka ma zazwyczaj swojego
wyżej niż złożoną przez Mobile TV – spółkę utworzoną przez Polską jednego właściciela. Komputer to adres IP w sieci, telefon – to kon-
Telefonię Cyfrową, P4, Polkomtel oraz PTK Centertel. kretna osoba, wymieniona z imienia, nazwiska, wieku i zawodu w
W trakcie formułowania konkursu nie obyło się bez sporów po- umowie podpisanej z operatorem. Z punktu widzenia określania
między Urzędem Komunikacji Elektronicznej (UKE) i Krajową Radą grupy docelowej jest to atrakcyjne. Łącząc takie informacje z da-
Radiofonii i Telewizji (KRRiT). UKE zakwalifikowała telewizję mobil- nymi zawartymi w bilingach, można profilować ofertę, kierując
ną jako usługę telekomunikacyjną, a nie telewizyjną (radiodyfuzyj- się sposobem użytkowania telefonu. Łatwiej wtedy określać, jakie
ną). Taka usługa świadczona przez operatora multipleksu nie pod- usługi mogą zainteresować użytkownika. Zwiększa to skuteczność
legałaby pod ustawę o radiofonii i telewizji (por. [5]). Oznaczało to przy jednoczesnym ograniczeniu nakładów na reklamę. Dla przy-
wyłączenie KRRiT z obowiązku wydawania zgody na rozpoczęcie kładu, w USA około 50% użytkowników odpowiada na otrzymane
nadawania i kontroli przekazywanych treści. Dodatkowe uwarun- SMS-em treści reklamowe.
kowania konkursu ograniczały również potencjalne zyski nadaw- Treści reklamowe to głównie SMS'y (również Premium), MMS'y,
ców poprzez np. faworyzowanie operatorów deklarujących więk- fotokody. Coraz większą popularność zyskuje marketing przez łą-
szą liczbę partnerów, którzy będą współtworzyć propozycję pro- cze bluetooth, ale również banery na stronach mobilnych, rekla-
gramową. Ostatecznie osiągnięte porozumienie pozostawia opera- ma w wyszukiwarkach czy w końcu reklama w telewizji mobilnej.
torowi kontrolę nad zawartością multipleksu bez konieczności sta- To rynek rosnący w szybkim tempie. W 2007 roku szacowany na
rania się o koncesję na treści, które się na nim znajdą. Taką konce- 2.7 mld dolarów, w 2012 roku ma zwiększyć się do prawie 20 mld
sję będą musieli zdobyć nadawcy radiowi i telewizyjni dostarczają- dolarów.
cy treści ([6]). Może to zapowiadać bardzo szybki start tej usługi (co Istnieje jednak prawdopodobieństwo, że największa zaleta ko-
zresztą jest wpisane w warunki konkursu). Znacząco przedłużono mórki – jej osobistość – obróci się przeciwko reklamodawcom. Re-
również czas, na jaki operator uzyskuje prawo do zarządzania mul- klamy przesyłane na telefon, bardziej niż w przypadku kompute-
tipleksem – do 2025 roku. Wynika to z kalkulacji, że zwrot inwesty- rów stacjonarnych, mogą być uznane za ingerencję w prywatność.
cji jest spodziewany po 7 latach, a następne 15 lat daje możliwość Zgodnie z kwalifikacją za spam uznaje się wszelkie treści, które są
osiągnięcia zysków. niezależne od tożsamości adresata i gdy zachodzi podejrzenie, że
Powyższe rozważania dają już pewne wyobrażenie stopnia nadawca odniesie z ich tytułu korzyści (zob. [7]). Będzie to przed-
skomplikowania modeli biznesowych towarzyszących telewizji miotem najbliższej nowelizacji prawa telekomunikacyjnego. Kary
mobilnej. Graczy jest tu jednak znacznie więcej, jak chociażby: za rozsyłanie takich niezamówionych treści (lub treści z zafałszo-
waną tożsamością nadawcy) mogą sięgać nawet 100 tysięcy zło-
• twórcy/właściciele treści (ang. content producer, owner), któ- tych. Przy okazji należy wyjaśnić kwestię zakresu zgody (wyraża-
rzy produkują programy; reklamodawcy; nej podczas np. zawierania umowy z operatorem) na przetwarza-
• agregatorzy treści (ang. content providers) – kupują treść od nie danych osobowych. Czy implikuje ona zgodę na otrzymywa-
twórców treści, łączą, dodają do nich reklamy itp.,dostarcza- nie treści reklamowych? Podobnie, czy skorzystanie z usługi np.
jąc gotowy strumień zawierający audio i wideo; pobierania logo na komórkę, automatycznie uprawnia usługo-
• operatorzy datacastu (ang. datacast operator) – odpowiedzialni dawcę do wysyłania reklam (tzw. zgoda dorozumienna, [7])?
za dodawanie elektronicznej informacji o strumieniu (ang. Elec-
tronic Service Guide - ESG, np. informacja o identyfikatorach ser-
wisów w strumieniu, sposobów ich lokalizacji, zawartości);
Źródła
• operatorzy sieci rozgłoszeniowej (ang. broadcast network • [1] Dane: International Telecommunication Union (ITU), www.itu.int;
operator) – zajmujący się aspektami technicznymi transmisji, • [2] Analiza cen usług dostępu do Internetu operatorów sieci ru-
dostarczają infrastrukturę; chomych, UKE, Marzec 2009;
• operatorzy sieci komórkowej (ang. mobile network operator); • [3] Sprawozdanie okresowe na temat jednolitego europejskiego
rynku łączności elektronicznej w 2008 r. (sprawozdanie nr 14),
• operatorzy usługi – odpowiedzialni za sprzedaż usług pod
Komisja Wspólnot Europejskich, Marzec 2009;
banderą marki, kontakty z klientami etc. • [4] Mobile goes Internet: key challenges for mobile ubiquity in
Europe's single market, Viviane Reding, February 2008;
Podstawowym problemem jest tu określenie, kto komu płaci i za • [5] Telewizja w komórce dzieli rynek, Magdalena Lemańska , Łu-
co. Powoduje to, że nawet w krajach przodujących w tym temacie kasz Dec, Rzeczpospolita, Kwiecień 2008;
penetracja rynku nie przekracza 10% (Korea Południowa, w Euro- • [6] Ruszył konkurs na operatora telewizji mobilnej, Interia PL/
PAP, październik 2008;
pie nie więcej niż 2%). Dodatkowo operatorzy są świadomi, że te- • [7] Za uciążliwe SMS-y można będzie zapłacić nawet 100 tys.
lewizja mobilna nie będzie substytutem dla telewizji stacjonarnej kary, Gazeta Prawna, 6 kwietnia 2009;
(analogowej czy cyfrowej), a jedynie jej uzupełnieniem. Ograniczo- • [8] Vital Wave Consulting. mHealth for Development: The Op-
na wielkość wyświetlacza oraz żywotność baterii terminali sprawia- portunity of Mobile Technology for Healthcare in the Develo-
ją, że zgodnie z badaniami będziemy skłonni do oglądania jej przez ping World. Washington D.C. and Berkshire, UK: UN Founda-
tion-Vodafone Foundation, Partnership, 2009;
około 20 minut dziennie, głównie w środkach komunikacji publicz-
• [9] Apple traci monopol na aplikacje iPhone'a, Yukari Iwatani
nej lub np. w ramach przerwy obiadowej. Będzie to raczej ogląda- Kane, The Wall Street Journal, Dziennik, 14-15 marzec 2009;
nie okazjonalne, gdy nie chcemy przegapić ulubionego programu.

www.sdjournal.org 215
Felieton

Oczywiście nastawienie do reklam może ulec zmianie, jeżeli w za- że mają one na celu obniżanie jej kosztów – tak po stronie pacjen-
mian za ich odbieranie dostaniemy darmowe minuty lub SMS'y (np. tów (np. brak konieczności pojawiania się w szpitalu), jak i służb me-
36i6, usługa Chill Bill). dycznych. Także w przypadku tego typu usług będziemy mieli do czy-
nienia ze stosunkowo dużą ilością uczestników modelu biznesowe-
m-Zdrowie go. Dostarczanie takich usług wymaga współdziałania rządów, ich
Omawiając najszybciej rozwijające się usługi ruchome, nie można agend odpowiedzialnych za opiekę zdrowotną, biznesu dostarcza-
pominąć usług medycznych dostarczanych tą drogą. Dla przykładu, jącego odpowiednie urządzenia i aplikacje obsługujące usługi oraz
obecnie większość centrów medycznych oferuje zamawianie i przy- platformy umożliwiające ich działanie. Tylko synchronizacja ich wy-
pominanie o wizytach lekarskich za pomocą SMS'ów. Zalety usług ru- siłków połączona z (nie ukrywajmy) możliwością osiągnięcia zysków
chomych sprawiają, że stanowią one wielką szansę na poprawienie będzie sprzyjała rozwojowi usług, które już niejednokrotnie dowiodły
warunków życia w krajach rozwijających się. swojej skuteczności.
Charakterystyka tych regionów to przede wszystkim brak wykwa-
lifikowanej kadry medycznej, utrudnienia w dostępie do placówek Aplikacje i platformy
medycznych, szpitali, izolacja małych społeczności (poprzez prze- Dostęp do powyższych usług wymaga tworzenia nowych plat-
szkody naturalne i etniczne) czy wreszcie ubóstwo. Opublikowa- form i aplikacji. Jest to kolejny trend warty wspomnienia co naj-
ny niedawno raport Organizacji Narodów Zjednoczonych na temat mniej ze względu na 2 produkty: AppStore oraz Android.
wspomagania usług medycznych w krajach rozwijających się (zob. Pisanie aplikacji na iPhona stało się już niemal zjawiskiem socjo-
[8]) specyfikuje następujące podstawowe kierunki rozwoju medycz- logicznym, głównie ze względu na krążące historie o krociowych
nych usług mobilnych: zyskach. Kluczem jest tu oryginalny pomysł, którym uda się zarazić
odpowiednią ilość użytkowników (głównie chodzi o rynek ame-
• edukacja i szerzenie świadomości; rykański). Kilka dolarów za aplikację pomnożą przez tysiące po-
• zdalne zbieranie informacji; brań. Popularność tego biznesu przekroczyła wszelkie oczekiwa-
• zdalne monitorowanie; nia – AppStore właśnie przekroczy miliard pobrań. Wydaje się, że
• komunikacja i edukacja pracowników medycznych; wszyscy są zadowoleni: programiści mogą szybko zarobić, Apple
• śledzenie ognisk epidemii; pobiera z tego nawet 30% (zob. [9]), użytkownicy cieszą się nowy-
• wspomaganie diagnostyki i leczenia. mi możliwościami swojej zabawki uzyskanymi za cenę hamburge-
ra. Pomysł Appla z certyfikacją aplikacji pozwolił na zdobycie po-
Edukacja zdaje się tu być jednym z kluczowych zadań. Niezależ- kaźnego źródła dochodu – szacowanego na nawet 800 milionów
nie od dostępnych środków, to właśnie zapobieganie jest najod- dolarów w tym roku (zob. [9]). Jest to całkowicie inny model niż ten
powiedniejszym działaniem w sensie długofalowym. Przykładem przyjęty np. przez Microsoft na systemach Windows Mobile. Nie ma
niech będą SMS'owe quizy na temat wiedzy o AIDS czy wiado- tam ograniczeń co do publikowania aplikacji, nawet jeżeli są one
mości zawierające informacje o możliwościach uzyskania pomo- konkurencyjne do tych oferowanych przez sam koncern.
cy. Szacuje się, że np. w RPA nawet 25% ludności może być zarażo- Z drugiej jednak strony, wymaganie certyfikacji oraz ograniczenia
na HIV, z czego jedynie 3% jest tego świadoma. Dzięki opisywane- w dostępie do funkcjonalności dostarczanej przez platformę iPhone
mu powyżej projektowi Masiluleke, ilość telefonów na linię pomo- mogą z czasem zacząć działać na jej niekorzyść (znów taktyka walled
cy wzrosła trzykrotnie (zob. [8]). garden). Narzucone restrykcje mogą zniechęcać do tworzenia aplika-
Aby jednak takie akcje były skuteczne, konieczne jest dostarcze- cji, które pozwoliłyby na potraktowanie iPhona jako elementu platfor-
nie właściwych treści do odpowiednich grup. Wymaga to groma- my usługowej, a nie jedynie terminala użytkownika. Dostrzegł to i wy-
dzenia informacji medycznych. Droga elektroniczna wydaje się tu korzystał Google, postanawiając udostępnić swoją platformę mobil-
najtańszym i najłatwiejszym rozwiązaniem. Pracownikom medycz- ną Android na zasadach kodu otwartego. Użyta licencja Apache Licen-
nym oszczędza się w ten sposób wysiłku na wypełnianie papierków. se 2.0 pozwala na tworzenie pochodnych komercyjnych. Niestety nie
Umożliwia to szybką identyfikację ognisk i śledzenie zmian zasięgu mamy tu do czynienia z produktem w pełni otwartym, ale na pewno
chorób. Posiadanie takich informacji w oczywisty sposób zwiększa jest to krok w tę stronę.
efektywność środków zaradczych.
Coraz szersze zastosowanie (i to nie tylko w krajach rozwijają- Podsumowanie
cych się) znajduje zdalne monitorowanie pacjentów (telemonito- W dziedzinie technologii mobilnej sukces jest jedynie w części po-
ring). Pozwala to sprawdzać stan pacjenta przebywającego poza chodną zapotrzebowania. Domena ta to arena interdyscyplinarnej
jednostką medyczną. Prostsze rozwiązania to np. rozdawanie cho- gry na styku interesów biznesu, społeczeństw i polityki. W tym kon-
rym telefonów, na które dzwonią pracownicy opieki medycznej, py- kretnym przypadku nie zawsze jest to jednak hamulec rozwoju. Nie-
tając o stan zdrowia czy przypominając o zażyciu odpowiednich le- rzadko można dostrzec zastosowania, których pozytywne skutki bę-
karstw. W bardziej zaawansowanych przypadkach łączność rucho- dą mieć społeczny charakter, globalny zasięg i długofalowe skutki.
ma jest wykorzystywana do przesyłania danych medycznych z urzą- Osiągnięcie pożądanych efektów wymaga współdziałania znacznie
dzeń diagnostycznych umieszczonych w domu pacjenta (waga, ci- większej ilości partnerów niż w przypadku innych dziedzin. Niemniej,
śnieniomierz, glukozometr i inne). Dane zbierają i analizują kompu- potencjalne zyski ze stosowania technologii mobilnych sprawiają, że
tery w centrach medycznych, alarmując odpowiedniego lekarza w dostarczane przez nie alternatywne rozwiązania są warte co najmniej
przypadku zagrożenia zdrowia. rozważenia w praktycznie każdym przejawie działalności.
Kolejną zaletą dostarczania informacji medycznej drogą mobilną Artykuł wyraża prywatne poglądy autora, które niekoniecznie
jest możliwość dostępu do najbardziej aktualnych danych medycz- odzwierciedlają stanowisko Silicon & Software Systems Ltd. (S3)
nych, epidemiologicznych czy praktyk postępowania wspomagają-
cych pierwszą pomoc, diagnozowanie i leczenie. Pomaga to w niesie- ARKADIUSZ MERTA
niu skutecznej pomocy w miejscu, gdzie pacjent przebywa (ang. po- Autor od 11 lat zajmuje się zagadnieniami projektowania i realizacji opro-
int of care, POC). gramowania. Aktualnie jest pracownikiem Silicon & Software Systems Pol-
Wymienione powyżej praktyki mają na celu głównie zwiększenie ska zatrudnionym na stanowisku menadżera projektów.
efektywności dostarczania pomocy medycznej. Oczywistym jest fakt, Kontakt z autorem: arkadiusz.merta@s3group.com

216 SDJ Extra 34 Biblia


Felieton

Ruch jest wszystkim [cel niczym]


Co napędza rozwój technologi mobilnych? Czy to nasze potrzeby, czy
potrzeba zwiększenia naszej efektywności.

W
edług Słownika Języka Polskiego, termin mobilny ozna- Może to więc kura była pierwsza? Może technologie mobilne są
cza: takiego, który łatwo daje się wprawić w ruch, kogoś marchewką na miarę XXI wieku? A zachłyśnięcie się nimi wcale nie
zdolnego do sprawnego, elastycznego działania oraz wynika z naszych potrzeb. Może to jedynie sprytna manipulacja
często zmieniającego miejsce pobytu lub miejsce pracy. Wszyst- mająca na celu osiąganie coraz lepszych wyników, ograniczając
kie te trzy określenia znajdują swoje odzwierciedlenie w techno- puste przebiegi? Jeżeli przyjmiemy taką spiskową tezę, to również
logiach zwanych mobilnymi. Są one ukierunkowane na usuwanie ilość potrzeb zaspokajanych przez komórkę wydaje się pułapką.
barier ograniczających możliwość wykonywania pracy (lub dostę- W ten sposób bowiem telefon jawi się jako narzędzie uniwersal-
pu do informacji) niezależnie od miejsca czy posiadanego urzą- ne, bez którego nie jesteśmy się w stanie obejść. Wtedy nawet nie-
dzenia dostępowego. Elastyczność uzyskiwana w ten sposób po- wesoły push-mail z firmy w piątek wieczorem czy telefon od szefa
zwala na zwiększenie zasięgu i efektywności prowadzonych dzia- podczas weekendowego grilla da się jakoś przeżyć.
łań. Bezpośrednio przekłada się to na zwiększenie zysków. Przedstawione powyżej źródła popularności technik mobilnych
Rozważając technologie mobilne, ograniczamy się zazwyczaj przekładają się na dość szeroki wachlarz potencjalnych zastosowań.
do doraźnych i przyszłych kwestii technicznych. Szukamy sposo- Wydaje się, że na znaczeniu będą zyskiwać usługi stymulujące uczu-
bów wykorzystania istniejących rozwiązań do stawianych przed na- cie wolności (np. szerokopasmowy dostęp, współdzielenie zasobów)
mi wyzwań. Szczególnie staramy się przewidzieć przyszłe potrzeby, i bezpieczeństwa (np. lokalizacyjne, medyczne), a jednocześnie po-
które wyznaczą kierunki rozwoju rynków. Takie myślenie o przyszło- zwalające na pozostanie w kontakcie z resztą społeczności (np. mo-
ści jest konieczne, aby zabezpieczyć ciągłość naszego przedsięwzię- bilne portale społecznościowe). Niełatwo będzie nam zrezygnować
cia. Proces ten polega na analizie wielu czynników i jest tyle skom- z takich udogodnień.
plikowany, co dający różne rezultaty (co widać w wynikach finanso- Z drugiej strony konieczne jest wspomaganie nowych modeli biz-
wych spółek). Pewne wskazówki dla niego można uzyskać, szukając nesowych pozwalających na zaangażowanie większej ilości podmio-
powodów popularności, ekspansji technologii mobilnych. W tym tów – dostawców produktów i usług, oraz ściślejsze powiązanie ich
celu należy dla odmiany spojrzeć wstecz i niekoniecznie na kwestie z abonentami. Komórka stanie się przedmiotem zainteresowania za-
związane bezpośrednio z techniką. równo operatorów jak i dostawców treści czy reklamodawców. W tym
Zanim ktokolwiek pomyślał o szerokopasmowym dostępie do In- celu telefon musi ewoluować w kierunku terminala, który umożliwi
ternetu w telefonie komórkowym, zanim powstała pierwsza komór- dostarczanie usług konwergentnych: głosowych oraz przesyłu da-
ka, ba – zanim wykonano pierwszą rozmowę telefoniczną – ludzie nych. Informacja, nawet ta zajmująca sporo miejsca czy transmitowa-
przemieszczali się. Robią to nadal i będą to robić zawsze. Różne są te- na na żywo, musi stać się lepiej osiągalna.
go motywy. W krajach rozwijających się dominuje chęć poprawy wa- Technologie mobilne to jednak trudny biznes, w którym sukces jest
runków życia, rozwiniętych – ciekawość (turystyka), zniewolonych uzależniony od kapryśnych gustów użytkowników. Operatorzy kon-
– przekonania polityczne, wyznaniowych – religia. Swoboda prze- centrują się bardziej na walce o nowego klienta niż na utrzymaniu
pływu osób, towarów, kapitału i usług to dla nas podstawowe prze- już posiadanych. Ostra konkurencja obniża różnice cenowe, co do-
jawy wolności. Sugeruje to, że w tym konkretnym wypadku prasta- datkowo sprzyja migracji klientów. Jedna promocja czy udany mo-
ry spór o pierwszeństwo jajka przed kurą (czy też odwrotnie) został del terminala mogą wywrócić układ sił na rynku. Wraz z populary-
rozstrzygnięty. Przemieszczanie się leży w naszej naturze. Potrzebuje- zacją dostępu do Internetu przez komórkę, operatorzy mogą stra-
my tej swobody, żeby czuć się wolnymi i mającymi wpływ na własne cić część kontroli nad terminalami. Podobnie nadmierne wykorzysty-
życie. Potrzebujemy również możliwości stowarzyszania się (afiliacji), wanie technologii mobilnych w imię zwiększenia efektywności, może
poznawania, informowania, zabawy, ale i bezpieczeństwa czy naby- w końcu wywołać uczucie naruszania sfery prywatności pracownika.
wania. Stworzyliśmy zatem odpowiednie technologie, które pozwa- To z kolei prowadzi do permanentnego stresu, którego objawami są
lają na zaspokojenie takich potrzeb. Można stąd uznać, że istota po- znużenie, zniechęcenie czy bierność. Osiągnięty efekt będzie dokład-
pularności i rozwoju technologii mobilnych płynie z naszej głębi i od- nie odwrotny do zamierzonego.
zwierciedla naturalne potrzeby czy zachcianki. Dokąd zmierzają technologie mobilne? Ilość potencjalnych obszarów
Z drugiej jednak strony, rozwój społeczeństw wymaga ciągłego aplikacji stale się zwiększa, podobnie zainteresowanie nimi. Postęp w tej
podnoszenia efektywności pracy. Coraz bardziej wyrafinowane ma- dziedzinie jest błyskawiczny. Pozostaje pytanie: czy ten ruch ma jakiś
szyny sprawiają, że siła fizyczna czy ilość pracowników tracą na zna- konkretny cel, czy jego sukces upatrywany jest w … samym ruchu?
czeniu. Liczy się sprawność w pozyskiwaniu i interpretacji danych. Artykuł wyraża prywatne poglądy autora, które niekoniecznie
Dostęp do nich jest konieczny nie tylko w biurze, ale coraz częściej odzwierciedlają stanowisko Silicon & Software Systems Ltd. (S3)
u klienta czy w drodze do niego. I to niezależnie od tego, czy mamy
przy sobie laptopa, PDA czy jedynie telefon. Miejsce pracy nie ozna- ARKADIUSZ MERTA
cza już budynku czy biurka, ale miejsce, w którym praca jest faktycz- Autor od 11 lat zajmuje się zagadnieniami projektowania i realizacji opro-
nie wykonywana, gdzie tworzy się pieniądz. W imię lepszego jutra, gramowania. Aktualnie jest pracownikiem Silicon & Software Systems Pol-
społeczeństwa są kreowane na informacyjne. Jako takie nie mogą ska zatrudnionym na stanowisku menadżera projektów.
wyrzec się swojego podstawowego nośnika: dostępu do informacji. Kontakt z autorem: arkadiusz.merta@s3group.com

www.sdjournal.org 217
Zamówienie prenumeraty
Roczna
prenumerata Prosimy wypełniać czytelnie i przesyłać faksem na numer:

tylko 199,-
00 48 22 877 20 70
lub listownie na adres:
EuroPress Polska Sp. z o.o.
ul. Jana Kazimierza 46/54
01-248 Warszawa
Polska
Software Developer’s Journal Extra „Biblia Progra- E-Mail: software@europress.pl
misty” jest kwartalnikiem głównie dla programistów,
którzy liczą, że dostarczymy im gotowe rozwiąza-
Przyjmujemy też zamównienia telefoniczne:
nia, oszczędzając im czasu i pracy. Jesteśmy czyta-
ni przez tych, którzy chcą być na bieżąco informowa- 00 48 22 877 20 80
ni o najnowszych osiągnięciach w dziedzinie IT i nie
chcą, żeby jakiekolwiek istotne wydarzenia umknęły Jeżeli chcesz się dowiedzieć o formach płatności, wejdź na stronę:
ich uwadze. Aby zadowolić naszych czytelników, pre- www.europress.pl lub napisz na e-mail: software@europress.pl
zentujemy zarówno najnowsze rozwiązania, jaki star-
sze, sprawdzone technologie.
Imię i nazwisko ...............................................................................

Nazwa firmy.....................................................................................
Zadzwoń
+48 22 877 20 80 Dokładny adres ..............................................................................

lub .........................................................................................................
zamów
mailowo! Telefon ............................................................................................

E–mail .............................................................................................

ID kontrahenta ................................................................................

Numer NIP firmy .............................................................................

Fax (wraz z nr kierunkowym) .........................................................

□ automatyczne przedłużenie prenumeraty

UWAGA!

Zmiana danych
kontaktowych

Obsługa
prenumeraty
1. Telefon
+48 22 877 20 80 Ilość Od
2. Fax Ilość zama- numeru
+48 22 877 20 70 Tytuł nume- wianych pisma Cena
2. Online rów prenu- lub mie-
software@europress.pl merat siąca
Software Develo-
199
per’s Journal 4
PLN
Extra
3. Adres
EuroPress Polska Sp. z o.o.
ul. Jana Kazimierza 46/54
01-248 Warszawa

You might also like