Professional Documents
Culture Documents
SDJ Extra 34 Biblia
SDJ Extra 34 Biblia
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
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ą.
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
www.sdjournal.org 7
SDJ Extra 34 Biblia
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
www.sdjournal.org 9
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-
www.sdjournal.org 11
Programowanie Windows Mobile
RIL API
w sercu telefonu
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-
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-
���������
�����������������
�������������
��������������������������
�������������������������
�������������
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.
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.
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
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; }
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.
RAPI
Współpraca komputerów desktop z Windows CE i Mobile
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
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
#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; } }
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-
www.sdjournal.org 27
Programowanie Windows Mobile
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-
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
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-
ł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.
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-
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
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:
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-
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:
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();
}
namespace SDJStartWithWindowsMobile
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
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.
www.sdjournal.org 43
Programowanie 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.
www.sdjournal.org 45
Programowanie Windows Mobile
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
Porównanie wersji
Należy również zdawać sobie sprawę z istot-
nych różnic między SQL Server Compact ���������������
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
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-
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.
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
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
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
www.sdjournal.org 55
Programowanie Windows Mobile
• 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
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.
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
Obsługa akcelerometru
na platformie Series60
Wykorzystaj w pełni możliwości telefonu komórkowego!
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
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
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
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
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
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()
#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
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
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().
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 );
}
}
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
: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)
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()
,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-
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-
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)
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.
SDL na Symbianie
Cztery porty
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ł
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`
#include <SDL.h>
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-
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>
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-
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-
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.
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-
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?
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.
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
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;
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
• 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
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
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:
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-
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
• 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-
• 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.
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.
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-
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:
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:
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
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
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ąć
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.
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-
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
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-
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
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ą-
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:
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):
www.sdjournal.org 119
Programowanie Android
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).
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
Google Android
Programowanie interfejsu użytkownika pod Android OS
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)
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
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" .../>
www.sdjournal.org 127
Programowanie Android
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
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
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-
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;
}
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
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-
- (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];
}
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];
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++.
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-
(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.
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-
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
• 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.
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
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
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
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:
• 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
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-
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:
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-
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;
}
}
switch(enemy[i].getType())
{
www.sdjournal.org 165
Programowanie JavaME
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.
www.sdjournal.org 167
Programowanie JavaME
Testowanie aplikacji
na platformie J2ME
Zbieranie informacji debugowych w ograniczonym
środowisku uruchomieniowym
• 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
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
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
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
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
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ą-
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
���������������
���� ���� ���� ����
�����������
�����������������������������������������
������������
���� ���� ���� ����
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-
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;
• solid;
• composite solid. Rysunek 1. Rodzaje obiektów przechowywanych w SDO_GEOMETRY
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’; � �
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
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ń:
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,
������������������������� �����������
��������������
�������������������
������������������������������
����������������
������������������������� ������ �������������������������������������������
��������������������������������
��������
�����
������������������� ������
����������������������
����������������������
�������
���������
���������������� ������������������������������������
�����������������������������������
������ ������
�����������������
���������������������������������������������
������������������ �����������������
�����������������������������������������������
�������������������������� ���������������
�������������� ������������������������
����������������
• 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-
ł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.
Open GL ES 1.x
Fakty i mity
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-
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
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-
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):
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.
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 );
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
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.
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
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ć.
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-
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 2. Adobe Device Central – narzędzie do efektywnego testowania tworzonych aplikacji Rysunek 4. Animacja początkowa
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
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 12. Automatyczna optymalizacja obiektów graficznych Rysunek 15. Optymalizacja animacji dla Flash Lite
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 17. Odznaczenie automatycznego ruchu dla utworzonych klatek kluczowych poklatkowej Rysunek 18. Automatyczne zaznaczanie
animacji zbędnych obiektów z biblioteki
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
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-
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
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 ................................................................................
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